"Strlen (s1) - strlen (s2)" ist niemals kleiner als Null

77

Ich schreibe gerade ein C-Programm, das häufige Vergleiche der Zeichenfolgenlängen erfordert, daher habe ich die folgende Hilfsfunktion geschrieben:

Ich habe festgestellt, dass die Funktion auch dann true zurückgibt, wenn s1sie kürzer als ist s2. Kann jemand bitte dieses seltsame Verhalten erklären?

Adrian Monk
quelle
27
Das ist eine Fortran-66-artige Schreibweise return strlen(s1) > strlen(s2);.
Jonathan Leffler
11
@ 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 Alex Lockwoods Antwort ziemlich zufrieden sind . Ich bin mir nicht sicher, was es noch braucht, um das Kopfgeld zu gewinnen! :)
eggyal
11
Es war ein Unfall, ich wusste nicht, was für ein Kopfgeld lol war. -_- Irgendwie peinlich ...
Adrian Monk
5
Ich denke, es ist gut für Alex Lockwood, weil seine großartige Antwort mehr Aufmerksamkeit erhalten wird ... also stimmen alle Alex Lockwoods Antwort zu !! : D
Adrian Monk
5
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 erhält. Er hat unwissentlich seine 100 Reputationspunkte verloren, lass ihn etwas zurückbekommen.
Krishnabhadra

Antworten:

175

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 strlenRückgabetyp size_t(eine vorzeichenlose Menge) zurückgegeben wird. Wenn s1kürzer als ist s2, sollte die Differenz strlen(s1) - strlen(s2)negativ sein, wird jedoch zu einer großen vorzeichenlosen Zahl, die größer als ist 0. So,

gibt zurück, 1auch wenn s1kürzer als ist s2. Verwenden Sie stattdessen diesen Code, um Ihre Funktion zu reparieren:

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:

Da der zweite Operand ohne Vorzeichen ist, wird der erste implizit in ohne Vorzeichen umgewandelt, und daher entspricht der Ausdruck dem Vergleich.

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 wird length:

Diese Funktion soll zeigen, wie leicht Fehler durch implizites Casting von signiert zu nicht signiert auftreten können. Es scheint ganz natürlich, Parameter lengthals vorzeichenlos zu übergeben. Wer würde schon mal eine negative Länge verwenden wollen? Das Stoppkriterium i <= length-1scheint auch ziemlich intuitiv zu sein. Wenn jedoch mit einem Argument lengthgleich ausgeführt wird, 0führt die Kombination dieser beiden zu einem unerwarteten Ergebnis.

Da der Parameter lengthvorzeichenlos ist, wird die Berechnung 0-1mit 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 zuzugreifen a.

Der Code kann entweder durch Deklarieren lengthals intoder durch Ändern des Tests der forSchleife festgelegt werden i < 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.

Alex Lockwood
quelle
46
Ein weiteres gutes Beispiel dafür, wie das Schreiben weniger macht das Programm mehr richtig.
Kerrek SB
3
@TimThomas Es muss auf die eine oder andere Weise gewirkt werden, und das Wirken ohne Vorzeichen in signiert würde Informationen verlieren, dh die Hälfte des Wertebereichs.
user207421
7
Streng genommen wird die Subtraktion zwischen zwei size_tWerten 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 der result > 0Teil, in dem resultder size_tWert aus der Subtraktion der beiden Größen angegeben ist.
Jonathan Leffler
9
Es wirft nicht , es konvertiert . Der Begriff Cast bezieht sich nur auf einen expliziten Cast-Operator, der aus einem Typnamen in Klammern besteht. Ein Cast-Operator gibt explizit eine Konvertierung an. Eine Konvertierung kann entweder explizit oder implizit sein.
Keith Thompson
2
Ich finde negative Ganzzahlen in meinem Code so selten, dass ich den umgekehrten Ansatz verfolge und verwende, es unsigned intsei 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.
Joshua Green
25

strlenGibt a zurück, size_tdas a typedeffür einen unsignedTyp ist.

Damit,

Alle unsignedWerte sind größer oder gleich 0. Versuchen Sie, die Variablen zurück Umwandlung von strlenzu long int.

pmg
quelle
ptrdiff_t ist die richtige tragbare Besetzung. Es ist üblich, dass long int auf 64-Bit-Systemen eine 32-Bit-Ganzzahl mit Vorzeichen ist (auf 64-Bit-Systemen sind es die Zeiger, die 64-Bit sind). Tatsächlich verwenden sowohl Visual C ++ als auch gcc für x86 und x86_64 32-Bit-Longs.
Herr Fooz
3
Ich dachte, es ptrdiff_tsei für die Subtraktion von Zeigern, nicht für die Subtraktion von size_tWerten ...
Herr Lister
4
Es gibt keinen POSIX-Typ für "Subtraktion von size_tWerten"; C definiert es als einfach, size_tda es ein integraler Typ ist und die Typen übereinstimmen. Man könnte argumentieren, dass das so ist off_t, aber das ist eigentlich für Datei-Offsets. Das Beste, was Sie tun können, ist der Grund dafür, dass size_tdie Plattform einen beliebigen Zeigerwert darstellen kann, da sie zum Indizieren von Bytes verwendet werden kann 0. Daher ptrdiff_tmuss die gleiche Anzahl von Bits wie vorhanden sein size_t, sodass es einfach die signedVersion von ist size_t.
Mike DeSimone
1

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_tz

Wenn Sie dies tun, möchten Sie sicher sein, dass der size_tWert in a passt ptrdiff_t(das ein Mantissenbit weniger hat).

Herr Fooz
quelle