Ich schreibe gerade ein C-Programm, das häufige Vergleiche der Zeichenfolgenlängen erfordert, daher habe ich die folgende Hilfsfunktion geschrieben:
int strlonger(char *s1, char *s2) {
return strlen(s1) - strlen(s2) > 0;
}
Ich habe festgestellt, dass die Funktion auch dann true zurückgibt, wenn s1
sie kürzer als ist s2
. Kann jemand bitte dieses seltsame Verhalten erklären?
return strlen(s1) > strlen(s2);
.Antworten:
Was Ihnen begegnet ist, ist ein besonderes Verhalten, das in C auftritt, wenn Ausdrücke verarbeitet werden, die sowohl vorzeichenbehaftete als auch vorzeichenlose Mengen enthalten.
Wenn eine Operation ausgeführt wird, bei der ein Operand signiert und der andere nicht signiert ist, konvertiert C das signierte Argument implizit in ein nicht signiertes und führt die Operationen unter der Annahme aus, dass die Zahlen nicht negativ sind. Diese Konvention führt häufig zu nicht intuitivem Verhalten für Vergleichsoperatoren wie
<
und>
.Beachten Sie in Bezug auf Ihre Hilfsfunktion, dass die Differenz und der Vergleich beide mit vorzeichenloser Arithmetik berechnet werden , da der
strlen
Rückgabetypsize_t
(eine vorzeichenlose Menge) zurückgegeben wird. Wenns1
kürzer als ists2
, sollte die Differenzstrlen(s1) - strlen(s2)
negativ sein, wird jedoch zu einer großen vorzeichenlosen Zahl, die größer als ist0
. So,return strlen(s1) - strlen(s2) > 0;
gibt zurück,
1
auch wenns1
kürzer als ists2
. Verwenden Sie stattdessen diesen Code, um Ihre Funktion zu reparieren:return strlen(s1) > strlen(s2);
Willkommen in der wundervollen Welt von C! :) :)
Zusätzliche Beispiele
Da diese Frage in letzter Zeit viel Aufmerksamkeit erhalten hat, möchte ich einige (einfache) Beispiele nennen, um sicherzustellen, dass ich die Idee vermitteln kann. Ich gehe davon aus, dass wir mit einer 32-Bit-Maschine arbeiten, die die Zweierkomplementdarstellung verwendet.
Das wichtige Konzept, das beim Arbeiten mit vorzeichenlosen / vorzeichenbehafteten Variablen in C zu verstehen ist, besteht darin, dass vorzeichenbehaftete Werte implizit in vorzeichenlose Werte umgewandelt werden , wenn in einem einzelnen Ausdruck eine Mischung aus vorzeichenlosen und vorzeichenbehafteten Größen vorhanden ist .
Beispiel 1:
Betrachten Sie den folgenden Ausdruck:
-1 < 0U
Da der zweite Operand ohne Vorzeichen ist, wird der erste implizit in ohne Vorzeichen umgewandelt, und daher entspricht der Ausdruck dem 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 durch Parameter angegeben wirdlength
: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 zeigen, wie leicht Fehler durch implizites Casting von signiert zu nicht signiert auftreten können. Es scheint ganz natürlich, Parameter
length
als vorzeichenlos zu übergeben. Wer würde schon mal eine negative Länge verwenden wollen? Das Stoppkriteriumi <= length-1
scheint auch ziemlich intuitiv zu sein. Wenn jedoch mit einem Argumentlength
gleich ausgeführt wird,0
führt die Kombination dieser beiden zu einem unerwarteten Ergebnis.Da der Parameter
length
vorzeichenlos ist, wird die Berechnung0-1
mit vorzeichenloser Arithmetik durchgeführt, was einer modularen Addition entspricht. Das Ergebnis ist dann UMax . Der<=
Vergleich wird auch unter Verwendung eines vorzeichenlosen Vergleichs durchgeführt, und da jede Zahl kleiner oder gleich UMax ist , gilt der Vergleich immer. Daher versucht der Code, auf ungültige Elemente des Arrays zuzugreifena
.Der Code kann entweder durch Deklarieren
length
alsint
oder durch Ändern des Tests derfor
Schleife festgelegt werdeni < length
.Fazit: Wann sollten Sie Unsigned verwenden?
Ich möchte hier nichts zu kontroverses sagen, aber hier sind einige der Regeln, die ich oft einhalte, 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 2 dargestellt).
Verwenden Sie diese Option , wenn Sie modulare Arithmetik ausführen.
Verwenden Sie diese Option , wenn Sie Bits zur Darstellung von Mengen verwenden. Dies ist häufig praktisch, da Sie damit logische Rechtsverschiebungen ohne Vorzeichenerweiterung durchführen können.
Natürlich kann es Situationen geben, in denen Sie sich gegen diese "Regeln" entscheiden. In den meisten Fällen erleichtert das Befolgen dieser Vorschläge die Arbeit mit Ihrem Code und ist weniger fehleranfällig.
quelle
size_t
Werten durchgeführt, die garantiert ohne Vorzeichen sind, und vorzeichenlose arithmetische Umhüllungen modulo die entsprechende Zweierpotenz. Der einzige Ort, an dem eine vorzeichenbehaftete / vorzeichenlose Konvertierung möglich ist, ist derresult > 0
Teil, in demresult
dersize_t
Wert aus der Subtraktion der beiden Größen angegeben ist.unsigned int
sei denn, es gibt einen konkreten Grund, dies nicht zu tun. Dies hat den Vorteil, dass alle Operationen genau definiert sind (sogar "Wrap-Around"), obwohl es zugegebenermaßen Sorgfalt erfordern kann, wenn mit einigen Ungleichungen umgegangen wird.strlen
Gibt a zurück,size_t
das atypedef
für einenunsigned
Typ ist.Damit,
(unsigned) 4 - (unsigned) 7 == (unsigned) - 3
Alle
unsigned
Werte sind größer oder gleich0
. Versuchen Sie, die Variablen zurück Umwandlung vonstrlen
zulong int
.quelle
ptrdiff_t
sei für die Subtraktion von Zeigern, nicht für die Subtraktion vonsize_t
Werten ...size_t
Werten"; C definiert es als einfach,size_t
da es ein integraler Typ ist und die Typen übereinstimmen. Man könnte argumentieren, dass das so istoff_t
, aber das ist eigentlich für Datei-Offsets. Das Beste, was Sie tun können, ist der Grund dafür, dasssize_t
die Plattform einen beliebigen Zeigerwert darstellen kann, da sie zum Indizieren von Bytes verwendet werden kann0
. Daherptrdiff_t
muss die gleiche Anzahl von Bits wie vorhanden seinsize_t
, sodass es einfach diesigned
Version von istsize_t
.Die Antwort von Alex Lockwood ist die beste Lösung (kompakte, klare Semantik usw.).
Manchmal ist es sinnvoll, explizit in eine signierte Form von
size_t
:ptrdiff_t
zreturn ptrdiff_t(strlen(s1)) - ptrdiff_t(strlen(s2)) > 0;
Wenn Sie dies tun, möchten Sie sicher sein, dass der
size_t
Wert in a passtptrdiff_t
(das ein Mantissenbit weniger hat).quelle