Ich schreibe gerade ein C-Programm, das häufige Vergleiche von Zeichenfolgenlängen erfordert, also habe ich die folgende Hilfsfunktion geschrieben:
int strlonger(char *s1, char *s2) {
return strlen(s1) - strlen(s2) > 0;
}
Mir ist aufgefallen, dass die Funktion auch dann true zurückgibt, wenn s1
hat eine kürzere Länge als s2
. Kann mir bitte jemand dieses seltsame Verhalten erklären?
Sie sind auf ein merkwürdiges Verhalten gestoßen, das in C auftritt, wenn Ausdrücke behandelt werden, die sowohl vorzeichenbehaftete als auch vorzeichenlose Größen enthalten.
Wenn eine Operation ausgeführt wird, bei der ein Operand vorzeichenbehaftet und der andere vorzeichenlos ist, konvertiert C das vorzeichenbehaftete Argument implizit in ein vorzeichenloses Argument und führt die Operationen unter der Annahme aus, dass die Zahlen nicht negativ sind. Diese Konvention führt oft zu einem nicht intuitiven Verhalten von relationalen Operatoren wie z <
und >
.
Beachten Sie in Bezug auf Ihre Hilfsfunktion, dass da strlen
gibt den Typ zurück size_t
(eine vorzeichenlose Größe), die Differenz und der Vergleich werden beide unter Verwendung von vorzeichenloser Arithmetik berechnet. Wann s1
ist kürzer als s2
der Unterschied strlen(s1) - strlen(s2)
sollte negativ sein, wird aber stattdessen zu einer großen Zahl ohne Vorzeichen, die größer als ist 0
. Daher,
return strlen(s1) - strlen(s2) > 0;
kehrt zurück 1
selbst wenn s1
ist kürzer als s2
. Um Ihre Funktion zu reparieren, verwenden Sie stattdessen diesen Code:
return strlen(s1) > strlen(s2);
Willkommen in der wunderbaren Welt von C! 🙂
Weitere Beispiele
Da diese Frage in letzter Zeit viel Aufmerksamkeit erhalten hat, möchte ich ein paar (einfache) Beispiele geben, nur um sicherzustellen, dass ich die Idee rüberkomme. Ich gehe davon aus, dass wir mit einer 32-Bit-Maschine arbeiten, die die Zweierkomplementdarstellung verwendet.
Das wichtige Konzept, das Sie verstehen müssen, wenn Sie mit vorzeichenbehafteten/vorzeichenbehafteten Variablen in C arbeiten, ist Folgendes Wenn in einem einzelnen Ausdruck eine Mischung aus vorzeichenlosen und vorzeichenbehafteten Größen vorhanden ist, werden vorzeichenbehaftete Werte implizit in vorzeichenlose umgewandelt.
Beispiel 1:
Betrachten Sie den folgenden Ausdruck:
-1 < 0U
Da der zweite Operand vorzeichenlos ist, ist es der erste implizit gegossen zu unsigned, und daher ist der Ausdruck äquivalent zum Vergleich,
4294967295U < 0U
was natürlich falsch ist. Dies ist wahrscheinlich nicht das Verhalten, das Sie erwartet haben.
Beispiel #2:
Betrachten Sie den folgenden Code, der versucht, die Elemente eines Arrays zu summieren a
wobei die Anzahl der Elemente per Parameter angegeben wird length
:
int sum_array_elements(int a[], unsigned length) {
int i;
int result = 0;
for (i = 0; i <= length-1; i++)
result += a[i];
return result;
}
Diese Funktion soll demonstrieren, wie leicht Fehler durch implizites Casting von signiert nach unsigniert entstehen können. Es scheint ganz natürlich, Parameter zu übergeben length
wie unsigniert; schließlich, wer würde jemals eine negative Länge verwenden wollen? Das Abbruchkriterium i <= length-1
wirkt auch recht intuitiv. Allerdings, wenn mit Argument ausgeführt length
gleicht 0
führt die Kombination dieser beiden zu einem unerwarteten Ergebnis.
Seit Parameter length
ist vorzeichenlos, die Berechnung 0-1
wird unter Verwendung von vorzeichenloser Arithmetik durchgeführt, was der modularen Addition entspricht. Das Ergebnis ist dann Umax. Das <=
Der Vergleich wird auch unter Verwendung eines vorzeichenlosen Vergleichs durchgeführt, und da jede Zahl kleiner oder gleich ist Umax, der Vergleich gilt immer. Daher versucht der Code, auf ungültige Elemente des Arrays zuzugreifen a
.
Der Code kann entweder durch Deklaration behoben werden length
ein … sein int
oder durch Ändern des Tests der for
Schleife zu sein i < length
.
Fazit: Wann sollten Sie Unsigned verwenden?
Ich möchte hier nichts zu Kontroverses sagen, aber hier sind einige der Regeln, an die ich mich oft halte, wenn ich Programme in C schreibe.
-
NICHT verwenden, nur weil eine Zahl nicht negativ ist. Es ist leicht, Fehler zu machen, und diese Fehler sind manchmal unglaublich subtil (wie in Beispiel Nr. 2 veranschaulicht).
-
TUN verwenden, wenn modulare Arithmetik durchgeführt wird.
-
TUN verwenden, wenn Bits zur Darstellung von Mengen verwendet werden. Dies ist oft praktisch, da Sie damit logische Verschiebungen nach rechts ohne Vorzeichenerweiterung durchführen können.
Natürlich kann es Situationen geben, in denen Sie sich entscheiden, gegen diese “Regeln” zu verstoßen. Aber in den meisten Fällen wird die Befolgung dieser Vorschläge Ihren Code einfacher zu handhaben und weniger fehleranfällig machen.
strlen
gibt a zurück size_t
die ein typedef
für ein unsigned
Typ.
So,
(unsigned) 4 - (unsigned) 7 == (unsigned) - 3
Alle unsigned
Werte sind größer als oder gleich 0
. Versuchen Sie, die von zurückgegebenen Variablen zu konvertieren strlen
zu long int
.
Die Antwort von Alex Lockwood ist die beste Lösung (kompakt, klare Semantik usw.).
Manchmal ist es sinnvoll, explizit in eine signierte Form von zu konvertieren size_t
: ptrdiff_t
z.B
return ptrdiff_t(strlen(s1)) - ptrdiff_t(strlen(s2)) > 0;
Wenn Sie dies tun, möchten Sie sicher sein, dass die size_t
Wert passt in a ptrdiff_t
(das ein Mantissenbit weniger hat).
Das ist eine Fortran-66-artige Art zu schreiben
return strlen(s1) > strlen(s2);
.– Jonathan Leffler
28. Mai 2012 um 0:55 Uhr
@TimThomas: Warum bieten Sie das Kopfgeld für diese Frage an? Sie sagen, dass es nicht genug Aufmerksamkeit erhalten hat, aber es scheint, dass Sie mit der Antwort von Alex Lockwood recht zufrieden sind. Ich bin mir nicht sicher, was es noch braucht, um das Kopfgeld zu gewinnen! 🙂
– eigelb
28. Mai 2012 um 18:55 Uhr
Es war ein Unfall, ich wusste nicht, was ein Kopfgeld ist, lol. -_- Irgendwie peinlich…
– Adrian Mönch
29. Mai 2012 um 4:29 Uhr
Ich denke, es ist gut für Alex Lockwood, weil seine großartige Antwort mehr Aufmerksamkeit bekommen wird … also alle Stimme Alex Lockwoods Antwort zu! 😀
– Adrian Mönch
29. Mai 2012 um 4:30 Uhr
Ich denke, es ist besser für @TimThomas, das Kopfgeld bis zum letzten zulässigen Datum offen zu halten, damit auch seine Frage etwas Aufmerksamkeit erregt. Er hat unwissentlich seine 100 Reputationspunkte verloren, lassen Sie ihn etwas zurückbekommen.
– Krishnabhadra
1. Juni 2012 um 6:43 Uhr