Kompilieren des folgenden Codes:
double getDouble()
{
double value = 2147483649.0;
return value;
}
int main()
{
printf("INT_MAX: %u\n", INT_MAX);
printf("UINT_MAX: %u\n", UINT_MAX);
printf("Double value: %f\n", getDouble());
printf("Direct cast value: %u\n", (unsigned int) getDouble());
double d = getDouble();
printf("Indirect cast value: %u\n", (unsigned int) d);
return 0;
}
Ausgänge (MSVC x86):
INT_MAX: 2147483647
UINT_MAX: 4294967295
Double value: 2147483649.000000
Direct cast value: 2147483648
Indirect cast value: 2147483649
Ausgänge (MSVC x64):
INT_MAX: 2147483647
UINT_MAX: 4294967295
Double value: 2147483649.000000
Direct cast value: 2147483649
Indirect cast value: 2147483649
In der Microsoft-Dokumentation wird der vorzeichenbehaftete ganzzahlige Maximalwert bei Konvertierungen von double
bis nicht erwähnt unsigned int
.
Alle oben genannten Werte INT_MAX
werden abgeschnitten, 2147483648
wenn eine Funktion zurückgegeben wird.
Ich verwende Visual Studio 2019 , um das Programm zu erstellen. Dies passiert auf gcc nicht .
Mache ich etwas falsch Gibt es einen sicheren Weg, um double
zu konvertieren unsigned int
?
c
visual-c++
casting
x86
floating-point
Matheus Rossi Saciotto
quelle
quelle
INT_MIN
Antworten:
Ein Compiler-Fehler ...
Aus der von @anastaciu bereitgestellten Assembly wird der direkte Cast-Code aufgerufen, der
__ftol2_sse
die Nummer in eine signierte Long- Nummer umzuwandeln scheint . Der Routinenname ist,ftol2_sse
weil dies eine sse-fähige Maschine ist - aber der Float befindet sich in einem x87-Gleitkommaregister.; Line 17 call _getDouble call __ftol2_sse push eax push OFFSET ??_C@_0BH@GDLBDFEH@Direct?5cast?5value?3?5?$CFu?6@ call _printf add esp, 8
Die indirekte Besetzung dagegen tut es
; Line 18 call _getDouble fstp QWORD PTR _d$[ebp] ; Line 19 movsd xmm0, QWORD PTR _d$[ebp] call __dtoui3 push eax push OFFSET ??_C@_0BJ@HCKMOBHF@Indirect?5cast?5value?3?5?$CFu?6@ call _printf add esp, 8
Dadurch wird der doppelte Wert in die lokale Variable eingefügt und dort gespeichert. Anschließend wird er in ein SSE-Register geladen und
__dtoui3
aufgerufen. Dies ist eine Konvertierungsroutine mit doppeltem zu vorzeichenlosem int ...Das Verhalten der Direktbesetzung entspricht nicht C89; Es entspricht auch keiner späteren Überarbeitung - selbst C89 sagt ausdrücklich Folgendes:
Ich glaube, das Problem könnte eine Fortsetzung davon aus dem Jahr 2005 sein - früher gab es eine Konvertierungsfunktion,
__ftol2
die wahrscheinlich für diesen Code funktioniert hätte, dh sie hätte den Wert in eine signierte Nummer -2147483647 konvertiert, die die korrekte erzeugt hätte Ergebnis bei Interpretation einer vorzeichenlosen Zahl.Leider
__ftol2_sse
ist dies kein Drop-In-Ersatz für__ftol2
, da es - anstatt nur die niedrigstwertigen Wertbits unverändert zu verwenden - den Fehler außerhalb des Bereichs signalisiert, indem esLONG_MIN
/ zurückgibt0x80000000
, das, hier als vorzeichenlos lang interpretiert, nicht bei ist alles was erwartet wurde. Das Verhalten von__ftol2_sse
wäre gültig fürsigned long
, da die Konvertierung eines doppelten Wertes>LONG_MAX
insigned long
ein undefiniertes Verhalten hätte.quelle
Nach der Antwort von @ AnttiHaapala habe ich den Code mithilfe der Optimierung getestet
/Ox
und festgestellt, dass dadurch der Fehler behoben wird, der__ftol2_sse
nicht mehr verwendet wird://; 17 : printf("Direct cast value: %u\n", (unsigned int)getDouble()); push -2147483647 //; 80000001H push OFFSET $SG10116 call _printf //; 18 : double d = getDouble(); //; 19 : printf("Indirect cast value: %u\n", (unsigned int)d); push -2147483647 //; 80000001H push OFFSET $SG10117 call _printf add esp, 28 //; 0000001cH
Durch die Optimierungen wurde
getdouble()
eine konstante Ausdrucksbewertung hinzugefügt und hinzugefügt, sodass zur Laufzeit keine Konvertierung erforderlich ist und der Fehler behoben wird .Aus Neugier habe ich einige weitere Tests durchgeführt, nämlich das Ändern des Codes, um die Umwandlung von Float in Int zur Laufzeit zu erzwingen. In diesem Fall ist das Ergebnis immer noch korrekt. Der Compiler verwendet bei der Optimierung
__dtoui3
beide Konvertierungen://; 19 : printf("Direct cast value: %u\n", (unsigned int)getDouble(d)); movsd xmm0, QWORD PTR _d$[esp+24] add esp, 12 //; 0000000cH call __dtoui3 push eax push OFFSET $SG9261 call _printf //; 20 : double db = getDouble(d); //; 21 : printf("Indirect cast value: %u\n", (unsigned int)db); movsd xmm0, QWORD PTR _d$[esp+20] add esp, 8 call __dtoui3 push eax push OFFSET $SG9262 call _printf
Wenn Sie jedoch Inlining verhindern,
__declspec(noinline) double getDouble(){...}
wird der Fehler zurückgebracht://; 17 : printf("Direct cast value: %u\n", (unsigned int)getDouble(d)); movsd xmm0, QWORD PTR _d$[esp+76] add esp, 4 movsd QWORD PTR [esp], xmm0 call _getDouble call __ftol2_sse push eax push OFFSET $SG9261 call _printf //; 18 : double db = getDouble(d); movsd xmm0, QWORD PTR _d$[esp+80] add esp, 8 movsd QWORD PTR [esp], xmm0 call _getDouble //; 19 : printf("Indirect cast value: %u\n", (unsigned int)db); call __ftol2_sse push eax push OFFSET $SG9262 call _printf
__ftol2_sse
wird in beiden Konvertierungen aufgerufen, wodurch die Ausgabe2147483648
in beiden Situationen erfolgt, @zwol-Verdächtigungen waren korrekt.Zusammenstellungsdetails:
In Visual Studio:
Die Deaktivierung
RTC
in Project->
Properties->
Code Generationund Einstellung Basic Runtime Checks auf Standard .Aktivieren der Optimierung in Project
->
Properties->
Optimizationund Setzen der Optimierung auf / Ox .Mit Debugger im
x86
Modus.quelle
getDouble
und / oder ihn ändern, um einen Wert zurückzugeben, den der Compiler nicht als konstant beweisen kann.Niemand hat sich den Asm für MS angesehen
__ftol2_sse
.Aus dem Ergebnis können wir schließen, dass es wahrscheinlich von x87 in signiertes
int
/ konvertiert wurdelong
(beide 32-Bit-Typen unter Windows), anstatt sicher inuint32_t
.x86 FP -> Integer-Anweisungen, die das Integer-Ergebnis überlaufen, werden nicht nur umgebrochen / abgeschnitten: Sie erzeugen das, was Intel als "Integer Indefinite" bezeichnet, wenn der genaue Wert im Ziel nicht darstellbar ist: High-Bit gesetzt, andere Bits gelöscht. dh
0x80000000
.(Wenn die ungültige FP-Ausnahme nicht maskiert ist, wird sie ausgelöst und es wird kein Wert gespeichert. In der Standard-FP-Umgebung werden jedoch alle FP-Ausnahmen maskiert. Aus diesem Grund können Sie für FP-Berechnungen anstelle eines Fehlers eine NaN erhalten.)
Dies beinhaltet sowohl x87-Anweisungen wie
fistp
(unter Verwendung des aktuellen Rundungsmodus) als auch SSE2-Anweisungen wiecvttsd2si eax, xmm0
(unter Verwendung der Kürzung in Richtung 0, dast
bedeutet das Extra ).Es ist also ein Fehler beim Kompilieren
double
->unsigned
Konvertieren in einen Aufruf von__ftol2_sse
.Randnotiz / Tangente:
Auf x86-64 kann FP -> uint32_t kompiliert werden
cvttsd2si rax, xmm0
, um in ein 64-Bit-Ziel mit Vorzeichen konvertiert zu werden, und das gewünschte uint32_t in der unteren Hälfte (EAX) des ganzzahligen Ziels erzeugen.Es ist C und C ++ UB, wenn das Ergebnis außerhalb des Bereichs 0..2 ^ 32-1 liegt. Es ist also in Ordnung, dass große positive oder negative Werte die niedrige Hälfte von RAX (EAX) Null aus dem ganzzahligen unbestimmten Bitmuster verlassen. (Im Gegensatz zu Integer-> integer Conversions Moduloreduktion des Wertes ist nicht garantiert. Ist das Verhalten einer negativen Doppel in unsigned int Gießen definiert in der C - Standard? Unterschiedliches Verhalten auf ARM vs. x86 . Um es klar, nichts in der Frage ist undefiniertes oder sogar implementierungsdefiniertes Verhalten. Ich möchte nur darauf hinweisen, dass Sie FP-> int64_t verwenden können, um FP-> uint32_t effizient zu implementieren. Dazu gehört x87
fistp
Im Gegensatz zu SSE2-Anweisungen, die 64-Bit-Ganzzahlen im 64-Bit-Modus nur direkt verarbeiten können, kann ein 64-Bit-Ganzzahlziel auch im 32-Bit- und 16-Bit-Modus geschrieben werden.quelle