Diese Frage wird von mir motiviert, kryptografische Algorithmen (z. B. SHA-1) in C / C ++ zu implementieren, tragbaren plattformunabhängigen Code zu schreiben und undefiniertes Verhalten gründlich zu vermeiden .
Angenommen, ein standardisierter Krypto-Algorithmus fordert Sie auf, Folgendes zu implementieren:
b = (a << 31) & 0xFFFFFFFF
wo a
und b
sind vorzeichenlose 32-Bit-Ganzzahlen. Beachten Sie, dass wir im Ergebnis alle Bits verwerfen, die über den niedrigstwertigen 32 Bits liegen.
Als erste naive Annäherung könnten wir annehmen, dass diese int
auf den meisten Plattformen 32 Bit breit ist, also würden wir schreiben:
unsigned int a = (...);
unsigned int b = a << 31;
Wir wissen, dass dieser Code nicht überall funktioniert, da er int
auf einigen Systemen 16 Bit, auf anderen 64 Bit und möglicherweise sogar 36 Bit breit ist. Mit stdint.h
können wir diesen Code jedoch mit dem folgenden uint32_t
Typ verbessern :
uint32_t a = (...);
uint32_t b = a << 31;
Also sind wir fertig, oder? Das habe ich mir jahrelang gedacht. ... Nicht ganz. Angenommen, auf einer bestimmten Plattform haben wir:
// stdint.h
typedef unsigned short uint32_t;
Die Regel für die Ausführung von arithmetischen Operationen in C / C ++ lautet: Wenn der Typ (z. B. short
) schmaler als ist int
, wird er erweitert, int
wenn alle Werte passen können oder auf unsigned int
andere Weise.
Angenommen, der Compiler definiert short
als 32 Bit (signiert) und int
als 48 Bit (signiert). Dann diese Codezeilen:
uint32_t a = (...);
uint32_t b = a << 31;
wird effektiv bedeuten:
unsigned short a = (...);
unsigned short b = (unsigned short)((int)a << 31);
Beachten Sie, dass zu a
befördert wird, int
weil alle ushort
(dh uint32
) in int
(dh int48
) passen .
Aber jetzt haben wir ein Problem: Das Verschieben von Nicht-Null-Bits in das Vorzeichenbit eines vorzeichenbehafteten Integer-Typs ist ein undefiniertes Verhalten . Dieses Problem trat auf, weil wir uint32
befördert wurden int48
- anstatt befördert zu werden uint48
(wo Linksverschiebung in Ordnung wäre).
Hier sind meine Fragen:
Ist meine Argumentation richtig und ist dies theoretisch ein legitimes Problem?
Ist dieses Problem sicher zu ignorieren, da auf jeder Plattform der nächste Ganzzahltyp doppelt so breit ist?
Ist es eine gute Idee, sich gegen diese pathologische Situation richtig zu verteidigen, indem Sie die Eingabe wie folgt vormaskieren?:
b = (a & 1) << 31;
. (Dies muss auf jeder Plattform korrekt sein. Dies kann jedoch dazu führen, dass ein geschwindigkeitskritischer Krypto-Algorithmus langsamer als erforderlich ist.)
Erläuterungen / Änderungen:
Ich akzeptiere Antworten für C oder C ++ oder beides. Ich möchte die Antwort für mindestens eine der Sprachen wissen.
Die Vormaskierungslogik kann die Bitrotation beeinträchtigen. Beispielsweise wird GCC
b = (a << 31) | (a >> 1);
zu einem 32-Bit-Bitrotationsbefehl in Assemblersprache kompiliert . Wenn wir jedoch die Linksverschiebung vormaskieren, ist es möglich, dass die neue Logik nicht in Bitrotation übersetzt wird, was bedeutet, dass jetzt 4 Operationen anstelle von 1 ausgeführt werden.