Korrekte Verwendung von strtol

Lesezeit: 10 Minuten

Korrekte Verwendung von strtol
Jimm

Das folgende Programm konvertiert eine Zeichenfolge in eine lange Zeichenfolge, gibt aber nach meinem Verständnis auch einen Fehler zurück. Ich verlasse mich darauf, dass wenn strtol String erfolgreich in long umgewandelt, dann der zweite Parameter in strtol sollte gleich NULL sein. Wenn ich die folgende Anwendung mit 55 ausführe, erhalte ich die folgende Meldung.

./convertToLong 55
Could not convert 55 to long and leftover string is: 55 as long is 55

Wie kann ich Fehler von strtol erfolgreich erkennen? In meiner Anwendung ist Null ein gültiger Wert.

Code:

#include <stdio.h>
#include <stdlib.h>

static long parseLong(const char * str);

int main(int argc, char ** argv)
{
    printf("%s as long is %ld\n", argv[1], parseLong(argv[1]));
    return 0;
 }

static long parseLong(const char * str)
{
    long _val = 0;
    char * temp;

    _val = strtol(str, &temp, 0);

    if(temp != '\0')
            printf("Could not convert %s to long and leftover string is: %s", str, temp);

    return _val;
}

  • Lesen Sie die Dokumentation erneut; Sie sollten auch Fehler wie Überlauf behandeln.

    – Kerrek SB

    5. Januar 2013 um 20:36 Uhr

  • Auch die richtige Fehlerprüfung für strto* Funktionen werden nicht durch Überprüfen des Ausgabezeigers ausgeführt. Dies sollte durch Prüfen auf einen Null-Rückgabewert und einen Satz erfolgen errno.

    Benutzer529758

    5. Januar 2013 um 20:37 Uhr

  • Warum benutzt du nicht std::stoi in C++? (Sie haben das C++-Tag hinzugefügt)

    – BatchyX

    5. Januar 2013 um 20:39 Uhr

  • @BatchyX, Es funktioniert nicht so gut für Zeichenfolgen wie “123abc” (wie der Konsens in meiner vorherigen Frage war). Das OP prüft, ob die gesamte Zeichenfolge konvertiert werden soll.

    – Chris

    5. Januar 2013 um 20:48 Uhr


  • @chris: Du kannst genau dasselbe mit machen std::stoi. Tatsächlich ist der Prototyp von stoi ist fast das gleiche wie strtolverwendet aber Ausnahmen, wo Ausnahmen fällig sind, anstelle eines Fehlerrückgabewerts mit globaler Fehlervariable hackery.

    – BatchyX

    5. Januar 2013 um 20:53 Uhr


1646964021 499 Korrekte Verwendung von strtol
Jonathan Leffler

Beachten Sie, dass Namen, die mit einem Unterstrich beginnen, für die Implementierung reserviert sind; Am besten vermeiden Sie die Verwendung solcher Namen in Ihrem Code. Somit, _val sollte gerecht sein val.

Die vollständige Spezifikation der Fehlerbehandlung für strtol() und seine Verwandten ist komplex, überraschend komplex, wenn man ihm zum ersten Mal begegnet. Eine Sache, die Sie absolut richtig machen, ist die Verwendung einer Funktion zum Aufrufen strtol(); Es ist wahrscheinlich nicht korrekt, es ‘roh’ im Code zu verwenden.

Da die Frage sowohl mit C als auch mit C++ gekennzeichnet ist, werde ich aus dem C2011-Standard zitieren; die entsprechende Formulierung finden Sie im C++-Standard selbst.

ISO/IEC 9899:2011 §7.22.1.4 Die strtol, strtoll, strtoul und strtoull Funktionen

long int strtol(const char * restrict nptr, char ** restrict endptr, int base);

¶2 […] Zuerst zerlegen sie die Eingabezeichenfolge in drei Teile: eine anfängliche, möglicherweise leere Folge von Leerzeichen (wie von der isspace-Funktion angegeben), eine Subjektsequenz, die einer Ganzzahl ähnelt, die in einer Basis dargestellt wird, die durch den Wert von base bestimmt wird, und eine abschließende Zeichenfolge aus einem oder mehreren nicht erkannten Zeichen, einschließlich des abschließenden Nullzeichens der Eingabezeichenfolge. […]

¶7 Wenn die Subjektsequenz leer ist oder nicht die erwartete Form hat, wird keine Konvertierung durchgeführt; der Wert von nptr wird in dem Objekt gespeichert, auf das gezeigt wird endptrunter der Vorraussetzung, dass endptr ist kein Nullzeiger.

Kehrt zurück

¶8 Die strtol, strtoll, strtoulund strtoull Funktionen geben den konvertierten Wert zurück, falls vorhanden. Wenn keine Konvertierung durchgeführt werden konnte, wird Null zurückgegeben. Wenn der korrekte Wert außerhalb des Bereichs darstellbarer Werte liegt, wird LONG_MIN, LONG_MAX, LLONG_MIN, LLONG_MAX, ULONG_MAX oder ULLONG_MAX zurückgegeben (je nach Rückgabetyp und Vorzeichen des Werts, falls vorhanden), und der Wert des Makros ERANGE ist darin gespeichert errno.

Denken Sie daran, dass keine standardmäßige C-Bibliotheksfunktion jemals festgelegt wird errno auf 0. Um zuverlässig zu sein, müssen Sie daher festlegen errno vor dem Anruf auf Null strtol().

Also dein parseLong() Funktion könnte so aussehen:

static long parseLong(const char *str)
{
    errno = 0;
    char *temp;
    long val = strtol(str, &temp, 0);

    if (temp == str || *temp != '\0' ||
        ((val == LONG_MIN || val == LONG_MAX) && errno == ERANGE))
        fprintf(stderr, "Could not convert '%s' to long and leftover string is: '%s'\n",
                str, temp);
        // cerr << "Could not convert '" << str << "' to long and leftover string is '"
        //      << temp << "'\n";
    return val;
}

Beachten Sie, dass dies bei einem Fehler 0 oder LONG_MIN oder LONG_MAX zurückgibt, je nachdem, was passiert strtol() ist zurückgekommen. Wenn Ihr aufrufender Code wissen muss, ob die Konvertierung erfolgreich war oder nicht, benötigen Sie eine andere Funktionsschnittstelle – siehe unten. Beachten Sie auch, dass Fehler gedruckt werden sollten stderr eher, als stdoutund Fehlermeldungen sollten mit einem Zeilenumbruch abgeschlossen werden \n; Wenn dies nicht der Fall ist, wird nicht garantiert, dass sie rechtzeitig erscheinen.

Nun, im Bibliothekscode möchten Sie wahrscheinlich kein Drucken, und Ihr aufrufender Code möchte möglicherweise wissen, ob die Konvertierung erfolgreich war oder nicht, sodass Sie möglicherweise auch die Schnittstelle überarbeiten. In diesem Fall würden Sie die Funktion wahrscheinlich so ändern, dass sie eine Erfolgs-/Fehleranzeige zurückgibt:

bool parseLong(const char *str, long *val)
{
    char *temp;
    bool rc = true;
    errno = 0;
    *val = strtol(str, &temp, 0);

    if (temp == str || *temp != '\0' ||
        ((*val == LONG_MIN || *val == LONG_MAX) && errno == ERANGE))
        rc = false;

    return rc;
}

die Sie verwenden könnten wie:

if (parseLong(str, &value))
    …conversion successful…
else
    …handle error…

Wenn Sie zwischen ‘nachgestelltem Junk’, ‘ungültiger numerischer Zeichenfolge’, ‘Wert zu groß’ und ‘Wert zu klein’ (und ‘kein Fehler’) unterscheiden müssen, verwenden Sie eine ganze Zahl oder enum anstelle eines booleschen Rückgabecodes. Wenn Sie nachgestellte Leerzeichen, aber keine anderen Zeichen zulassen möchten, oder wenn Sie keine führenden Leerzeichen zulassen möchten, müssen Sie in der Funktion mehr tun. Der Code erlaubt Oktal, Dezimal und Hexadezimal; Wenn Sie eine strikte Dezimalzahl wünschen, müssen Sie die 0 im Aufruf auf 10 ändern strtol().

Wenn Ihre Funktionen als Teil der Standardbibliothek maskiert werden sollen, sollten sie nicht gesetzt werden errno zu 0 dauerhaft, also müssten Sie den Code umschließen, um ihn beizubehalten errno:

int saved = errno;  // At the start, before errno = 0;

…rest of function…

if (errno == 0)     // Before the return
    errno = saved;

  • Der Standard erwähnt keine Einstellung errno == EINVAL für Werte von base außer 0 oder 2..36, aber es ist eine vernünftige Sache zu tun. Im Allgemeinen sollten Sie vorsichtig sein, wenn Sie versuchen, Fehlerbedingungen mit zu erkennen errno eher als die Rückkehr von einer Funktion; die Bibliothek einstellen kann errno auf einen Wert ungleich Null, selbst wenn die Funktion erfolgreich ist. (Wenn die Ausgabe unter Solaris kein Terminal wäre, würden Sie feststellen errno == ENOTTY nach einer erfolgreichen Operation.) Theoretisch strtol() umwandeln könnte "1" zu 1 und einstellen errno auf einen Wert ungleich Null, und dies wäre legitim, aber pervers (und erfolgreich).

    – Jonathan Leffler

    17. März 2014 um 14:26 Uhr

  • Gibt es einen Grund errno == ERANGE wird unbedingt geprüft, ob strtol ist zurückgekommen LONG_MIN/LONG_MAX oder nicht? (Aus dem Grund, den Sie im Kommentar angeben, kann eine Bibliotheksfunktion eingestellt werden errno auf Erfolg.)

    – Mafso

    28. September 2014 um 9:10 Uhr

  • In deinem Beispiel ist ein Fehler. val ist ein long int *aber Sie machen den Scheck val == LONG_MINes sollte sein *val == LONG_MIN

    – Joakim

    11. Dezember 2014 um 0:24 Uhr

  • Stimmen Sie nicht zu: “Die Bibliothek kann errno auf einen Wert ungleich Null setzen, selbst wenn die Funktion erfolgreich ist.” C11 §7.5 3 diskutiert das, aber das trifft nicht zu strtol() weil “sofern die Verwendung von errno nicht in der Beschreibung der Funktion dokumentiert ist” die strtol() tut. if (temp == str || *temp != '\0' || errno == ERANGE) ist ausreichend . Meiner Meinung nach if (temp == str || *temp != '\0' || errno) ist besser, da es einige ID-Erweiterungen erfasst. Die (*val == LONG_MIN || *val == LONG_MAX) werden nicht benötigt.

    – chux – Wiedereinsetzung von Monica

    26. April 2017 um 19:34 Uhr


  • @JonathanLeffler Stimme zu EINVAL und so der Vorschlag temp == str || *temp != '\0' || errno – Ich denke, da stimmen wir gut überein. Doch der Kommentar bezieht sich auf die Notwendigkeit *val == LONG_MIN || *val == LONG_MAXdie angesichts der nicht verbessert wird andere errno Möglichkeiten. Wenn errno == ERANGE stimmt, dann auch wenn *val == LONG_MIN || *val == LONG_MAX war auf irgendeiner Einhornmaschine falsch, die strtol() dennoch als nicht bestanden gelten.

    – chux – Wiedereinsetzung von Monica

    26. April 2017 um 19:53 Uhr


Du bist fast da. temp selbst wird nicht null sein, aber es wird auf ein Nullzeichen zeigen, wenn die gesamte Zeichenfolge konvertiert wird, also müssen Sie es dereferenzieren:

if (*temp != '\0')

  • Zusätzliche Überprüfungen sind erforderlich, um Überläufe zu behandeln und eine leere Zeichenfolge zu analysieren. Siehe Jonathan Lefflers Antwort.

    – 0xF

    14. Februar 2014 um 11:44 Uhr

1646964023 102 Korrekte Verwendung von strtol
Chux – Wiedereinsetzung von Monica

Wie kann ich Fehler von strtol erfolgreich erkennen?

static long parseLong(const char * str) {
    int base = 0;
    char *endptr;
    errno = 0;
    long val = strtol(str, &endptr, base);

3 Tests, die von der Standard-C-Bibliothek spezifiziert/unterstützt werden:

  1. Irgendeine Konvertierung durchgeführt?

     if (str == endptr) puts("No conversion.");
    
  2. Im Bereich?

     else if (errno == ERANGE) puts("Input out of long range.");
    
  3. Tailing-Müll?

     else if (*endptr) puts("Extra junk after the numeric text.");
    

Erfolg

    else printf("Success %ld\n", val);

Geben Sie wie ein str == NULL oder base nicht 0, [2 to 36] ist undefiniertes Verhalten. Verschiedene Implementierungen (Erweiterungen der C-Bibliothek) bieten definiertes Verhalten und melden über errno. Wir könnten einen 4. Test hinzufügen.

    else if (errno) puts("Some implementation error found.");

Oder mit kombinieren errno == ERANGE Prüfung.


Beispiel für knappen Code, der auch die Vorteile gängiger Implementierungserweiterungen nutzt.

long my_parseLong(const char *str, int base, bool *success) {
    char *endptr = 0;
    errno = 0;
    long val = strtol(str, &endptr, base);
   
    if (success) {
      *success = endptr != str && errno == 0 && endptr && *endptr == '\0';
    }
    return val;
}

Ihnen fehlt eine Ebene der Indirektion. Sie wollen prüfen, ob die Charakter ist die Beendigung NULund nicht, wenn der Zeiger ist NULL:

if (*temp != '\0')

Übrigens ist dies kein guter Ansatz für die Fehlerprüfung. Die richtige Fehlerprüfmethode der strto* Familie von Funktionen wird nicht durch Vergleichen des Ausgabezeigers mit dem Ende der Zeichenfolge ausgeführt. Dies sollte erfolgen, indem auf einen Rückgabewert von Null geprüft und der Rückgabewert von abgerufen wird errno.

Korrekte Verwendung von strtol
spargw

Sie sollten überprüfen

*temp != '\0'

Sie sollten den Wert von errno auch nach dem Aufruf von strotol wie folgt überprüfen können:

RETURN VALUES
     The strtol(), strtoll(), strtoimax(), and strtoq() functions return the result
     of the conversion, unless the value would underflow or overflow.  If no conver-
     sion could be performed, 0 is returned and the global variable errno is set to
     EINVAL (the last feature is not portable across all platforms).  If an overflow
     or underflow occurs, errno is set to ERANGE and the function return value is
     clamped according to the following table.


       Function       underflow     overflow
       strtol()       LONG_MIN      LONG_MAX
       strtoll()      LLONG_MIN     LLONG_MAX
       strtoimax()    INTMAX_MIN    INTMAX_MAX
       strtoq()       LLONG_MIN     LLONG_MAX

  • Das Zitieren aus „der folgenden Tabelle“ macht keinen Sinn, wenn Sie nicht sagen, wo die „folgende Tabelle“ zu finden ist.

    – Roland Illig

    31. Oktober 2020 um 19:00 Uhr

  • Haben Sie diese Dokumentation selbst geschrieben oder nur vergessen, die Quelle anzugeben, von der Sie sie kopiert haben?

    – Roland Illig

    4. November 2020 um 5:57 Uhr

  • Nein, es ist eine Manpage. Einfach “man strtol” auf jedem Unix-basierten System.

    – spargw

    4. November 2020 um 20:23 Uhr

  • Ich frage nur, weil die Manpage von NetBSD ganz anders aussieht, obwohl es ein UNIX-ähnliches System ist.

    – Roland Illig

    5. November 2020 um 15:00 Uhr

  • Darüber hinaus ist die Frage als “C, C++” gekennzeichnet, daher stammt die richtige Referenz vom C- oder C++-Standard, nicht von einer bestimmten Implementierung auf einer bestimmten Hardwarearchitektur.

    – Roland Illig

    5. November 2020 um 15:01 Uhr

  • Das Zitieren aus „der folgenden Tabelle“ macht keinen Sinn, wenn Sie nicht sagen, wo die „folgende Tabelle“ zu finden ist.

    – Roland Illig

    31. Oktober 2020 um 19:00 Uhr

  • Haben Sie diese Dokumentation selbst geschrieben oder nur vergessen, die Quelle anzugeben, von der Sie sie kopiert haben?

    – Roland Illig

    4. November 2020 um 5:57 Uhr

  • Nein, es ist eine Manpage. Einfach “man strtol” auf jedem Unix-basierten System.

    – spargw

    4. November 2020 um 20:23 Uhr

  • Ich frage nur, weil die Manpage von NetBSD ganz anders aussieht, obwohl es ein UNIX-ähnliches System ist.

    – Roland Illig

    5. November 2020 um 15:00 Uhr

  • Darüber hinaus ist die Frage als “C, C++” gekennzeichnet, daher stammt die richtige Referenz vom C- oder C++-Standard, nicht von einer bestimmten Implementierung auf einer bestimmten Hardwarearchitektur.

    – Roland Illig

    5. November 2020 um 15:01 Uhr

989560cookie-checkKorrekte Verwendung von strtol

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

Privacy policy