Vor kurzem haben wir seltsames Verhalten in altem Code entdeckt. Dieser Code hat seit Ewigkeiten funktioniert, aber auf einigen Plattformen (XBox 360, PowerPC) mit Compileroptimierungen auf max. Normalerweise würde ich ein undefiniertes Verhalten vermuten.
Der Code sieht ungefähr so aus:
%Vor%Es ist Teil eines Emulators, daher sollte die fragliche Operation nicht zu seltsam sein. Normalerweise würde ich erwarten, dass dies nur die unteren 16 Bits berücksichtigt und diese auf 32 Bits vorzeichen lässt. Offensichtlich war dies das Verhalten, das es seit Ewigkeiten hatte. Auf x86_64 gibt GCC mir dieses Ergebnis:
%Vor%Nach dem, was ich vom Standard verstehen konnte, ist es jedoch nicht möglich, einen vorzeichenlosen Wert in einen vorzeichenbehafteten umzuwandeln, falls es nicht möglich ist, den Wert des vorzeichenlosen Typs mit dem Typ mit Vorzeichen darzustellen.
Könnte der Compiler dann annehmen, dass der vorzeichenlose Wert im Bereich von [0, 32767]
liegen müsste, da jeder andere Wert undefiniert wäre? In diesem Fall würde eine Umwandlung in int16_t
und eine weitere Umwandlung in int32_t
nichts bewirken. Wäre es für den Compiler in diesem Fall legal, den Code in einen einfachen Schritt zu übersetzen?
Eine Konvertierung zwischen zwei Integertypen ist niemals undefiniert.
Aber einige Integer-Konvertierungen sind Implementierungen definiert.
Bei ganzzahligen Umwandlungen sagt C:
(C99, 6.3.1.3p3) "Ansonsten ist der neue Typ signiert und der Wert kann nicht darin dargestellt werden; entweder ist das Ergebnis implementierungsdefiniert oder es wird ein implementierungsdefiniertes Signal ausgelöst."
Was ist gcc
in diesem Fall hier dokumentiert?
"Für die Umwandlung in einen Typ der Breite N wird der Wert modulo 2 ^ N reduziert, um innerhalb des Bereichs des Typs zu sein; kein Signal wird ausgelöst"
Wie ouah heißt, führt die Konvertierung eines Bereichs außerhalb des Bereichs zu einem implementierungsdefinierten Ergebnis (oder ermöglicht ein implementierungsdefiniertes Signal angehoben werden).
Zum Beispiel wäre es für eine Implementierung völlig legal zu sagen, dass eine Konvertierung eines Bereichs außerhalb des Bereichs in int16_t
nur die unteren 15 Bits des Werts beibehält und immer das Vorzeichenbit auf 0 setzt. Daher würde es Ihre Funktion sign_extend16()
einfach als return val & 0x7fff;
interpretieren.
Eine Implementierung kann Ihre Funktion jedoch nicht so interpretieren, dass sie val
unverändert zurückgibt - die implementierungsdefinierte Konvertierung in int16_t
muss zu einem Wert im Bereich% führen co_de%, also muss das Endergebnis irgendwo in int16_t
oder [0, 32767]
liegen.
Beachten Sie auch, dass die [4294934528, 4294967295]
Besetzung dort völlig überflüssig ist.
Zwei Alternativen, die nicht auf implementierungsdefinierten Konvertierungen beruhen, sind (beachten Sie die Änderung des Argumenttyps von int32_t
):
... aber leider scheint der GCC-Optimierer nicht zu bemerken, dass dies nur eine Vorzeichenerweiterung der unteren 16 Bits ist.
Tags und Links c integer-overflow