“strlen(s1) – strlen(s2)” ist nie kleiner als Null

Lesezeit: 7 Minuten

Benutzeravatar von Adrian Monk
Adrian Mönch

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?

  • 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

Benutzeravatar von Alex Lockwood
Alex Lockwood

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 s2der 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 awobei 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 0fü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 intoder 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.

  • Ein weiteres schönes Beispiel für das Schreiben weniger macht das Programm mehr Korrekt.

    – Kerrek SB

    6. Mai 2012 um 22:34 Uhr

  • @TimThomas Es muss auf die eine oder andere Weise gecastet werden, und das Casting von unsigned to signed würde Informationen verlieren, dh die Hälfte des Wertebereichs.

    – Benutzer207421

    7. Mai 2012 um 0:06 Uhr

  • Genau genommen wird die Subtraktion zwischen zwei durchgeführt size_t Werte, die garantiert vorzeichenlos sind, und vorzeichenlose Arithmetik umschließt modulo die entsprechende Zweierpotenz. Der einzige Ort, an dem eine Konvertierung mit Vorzeichen/Vorzeichen möglich ist, ist in der result > 0 Teil, wo result ist der size_t Wert aus der Subtraktion der beiden Größen.

    – Jonathan Leffler

    28. Mai 2012 um 0:59 Uhr

  • Das tut es nicht gießenes konvertiert. Der Begriff gießen 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

    28. Mai 2012 um 2:35 Uhr

  • Ich finde negative Ganzzahlen in meinem Code so selten, dass ich den umgekehrten Ansatz verfolge und verwende unsigned int es sei denn, es gibt einen konkreten Grund, dies nicht zu tun. Dies hat den Vorteil, dass alle Operationen gut definiert sind (sogar “Wrap-Around”), obwohl es zugegebenermaßen Sorgfalt erfordern kann, wenn mit einigen Ungleichheiten umgegangen wird.

    – Joshua Green

    3. Juni 2012 um 11:48 Uhr


Benutzeravatar von pmg
pmg

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.

  • ptrdiff_t ist die richtige portable Besetzung. Es ist üblich, dass long int auf 64-Bit-Systemen eine vorzeichenbehaftete 32-Bit-Ganzzahl 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

    2. Juni 2012 um 0:32 Uhr

  • ich dachte ptrdiff_t war für die Subtraktion von Zeigern, nicht für die Subtraktion von size_t Werte…

    – Herr Lister

    2. Juni 2012 um 5:06 Uhr

  • Es gibt keinen POSIX-Typ für “Subtraktion von size_t Werte”; C definiert es als einfach size_t da es sich um einen ganzzahligen Typ handelt und die Typen übereinstimmen. Sie könnten argumentieren, dass das so ist off_t, aber das ist eigentlich für Datei-Offsets. Das Beste, was Sie tun werden, ist, das zu begründen size_t benötigt wird, um jeden Index zu halten, den die Plattform handhaben kann, dann kann es auch jeden Zeigerwert darstellen, da es zum Indizieren von Bytes verwendet werden könnte 0. Daher ptrdiff_t muss die gleiche Anzahl von Bits wie sein size_twodurch es einfach die signed Version von size_t.

    – Mike DeSimone

    2. Juni 2012 um 11:40 Uhr


Benutzeravatar von Herrn Fooz
Herr Fuoz

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_tz.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).

1417990cookie-check“strlen(s1) – strlen(s2)” ist nie kleiner als Null

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy