ProgrammierungSystemprogrammierer in C

Erklären Sie die Besonderheiten der Typumwandlung zwischen vorzeichenbehafteten und vorzeichenlosen Zahlen (signed/unsigned) in C, welche Fallstricke dabei auftreten können, wie man unerwartete Folgen bei der Arithmetik und dem Vergleich von signed/unsigned Typen vermeiden kann und wie dies die Portabilität von Programmen beeinflusst?

Bestehen Sie Vorstellungsgespräche mit dem Hintsage-KI-Assistenten

Antwort

In C funktioniert die automatische Typumwandlung nach dem Prinzip der "üblichen arithmetischen Umwandlungen". Bei der Beteiligung an einem Ausdruck von Zahlen mit unterschiedlichem Vorzeichen (signed/unsigned) erfolgt die Umwandlung nach den folgenden Regeln:

  • Wenn einer der Operanden unsigned und der andere signed ist, wird signed automatisch in unsigned umgewandelt.
  • Dies kann zu unerwarteten Überläufen führen, insbesondere beim Vergleich oder bei arithmetischen Operationen.
  • Die Größe der Typen hat ebenfalls Einfluss: Wenn unsigned eine größere Bitbreite hat, wird signed zu unsigned umgewandelt.

Beispiel für gefährliche Arithmetik:

int a = -1; // signed unsigned int b = 1; printf("%d\n", a < b); // immer falsch, da a in ein sehr großes unsigned umgewandelt wird

Ergebnis: -1, das in unsigned umgewandelt wird, wird zu einer sehr großen positiven Zahl.

Wichtige Hinweise:

  • Immer explizit Typen umwandeln, wenn Verwirrung über das Vorzeichen möglich ist.
  • Auf die Größen der Typen (int, long, uint32_t usw.) achten, damit Umwandlungen vorhersehbar erfolgen.
  • Die Logik der Arbeit mit signed und unsigned Variablen trennen, insbesondere bei Grenzkontrollen und Arithmetik.

Fangfrage

Frage: Welches Ergebnis gibt der Ausdruck (int)(unsigned)-1 zurück?

Erwartet falsche Antwort: "-1, denn -1 und wird zu int umgewandelt."

Richtige Antwort: Im Ausdruck (unsigned)-1 erfolgt zuerst die Umwandlung von -1 zu unsigned (auf einer 32-Bit-Plattform ist dies 0xFFFFFFFF), dann zurück zu signed int, was ebenfalls von der Implementierung abhängt, aber oft wieder -1 ergibt (wenn two's complement verwendet wird). Es ist jedoch richtiger zu sagen: Das Ergebnis hängt von den Standards der Darstellung von signed Zahlen ab, aber in den meisten Implementierungen wird -1 herauskommen.

Beispiel:

int x = (int)(unsigned)-1; // x == -1 auf den meisten Plattformen

Beispiele für reale Fehler aufgrund mangelnden Wissens über die Feinheiten des Themas


Geschichte

Im String-Handler wurde eine Funktion zur Größenvergleich verwendet: Wenn die Länge des Strings negativ sein kann, gab das Programm einen Fehler aus. Die Länge war jedoch vom Typ size_t (unsigned), und der Vergleichscode if(length < 0) ergab immer false, was zu einer Endlosschleife und Speicherüberlauf führte.


Geschichte

Beim Parsen des Protokolls enthielten die Netzwerkpakete Felder als unsigned, während lokale Variablen signed waren. Aufgrund des Überlaufs von unsigned bei der Verarbeitung bestimmter Werte kam es zu falschen Berechnungen der Paketlänge, was zu einer Bufferoverflow-Sicherheitsanfälligkeit führte.


Geschichte

Das Datumsvergleichsmodul in den Logs speicherte das Datum als unsigned int, suchte jedoch den Datumsbereich in int. Einige Grenzwerte führten, anstatt die erwartete Ausnahme auszulösen, zu fehlerhaften Filtern von Einträgen und dem Verlust wichtiger Logs.