2016-07-31 14 views
2

ich über diese Übung werde (Source Code): http://c.learncodethehardway.org/book/ex29.htmlWo ist der Null-Terminierungsfehler?

Ein Teil des Codes ist unten dargestellt:

int uppercase(const char *msg) 
{ 
    int i = 0; 

    // BUG: \0 termination problems 
    for(i = 0; msg[i] != '\0'; i++) { 
     printf("%c", toupper(msg[i])); 
    } 

    printf("\n"); 

    return 0; 
} 

Der Autor merkt an, dass es ein Fehler ist. Später sagt sein Befehl:

Haben Sie auf den schlechten Code geachtet, den ich in den libex29.c Funktionen habe? Sehen Sie, wie, obwohl ich eine for-Schleife verwende, sie immer noch nach '\ 0'-Enden suchen? Korrigieren Sie dies so, dass die Funktionen immer eine Länge für die Zeichenfolge innerhalb der Funktion verwenden.

Allerdings sehe ich den Fehler hier nicht. Da die Schleife beendet wird, wenn ein Nullzeichen vorhanden ist.

Sonst sieht jemand ein Problem hier?

+1

Ähm, verstehst du * was ein Null-Fehler ist? Weil das genau die Definition von einem ist? –

+3

"Da die Schleife endet, wenn ein Nullzeichen vorhanden ist." Ja, aber was ist, wenn ** nicht ** ist? –

+0

@ MarcusMüller, wenn da nicht der String nicht fertig ist. Wie ist der vorgeschlagene Weg? – drdot

Antwort

4

Der einzige "Fehler" ist, dass das Verhalten und die Anforderungen der Funktion nicht eindeutig dokumentiert sind.

Wenn die Dokumentation besagt, dass das Argument ein gültiger Zeiger auf eine Zeichenfolge sein muss (die definitionsgemäß null-terminiert sein muss), dann ist die Funktion korrekt, soweit ich das beurteilen kann (naja, fast - siehe unten) und es liegt in der Verantwortung des Anrufers, ein korrektes Argument zu übergeben. Die C-Standardbibliothek ist voll von String-Funktionen, die sich so verhalten.

Wenn in der Dokumentation angegeben wird, dass die Funktion selbst für die Überprüfung eines gültigen Arguments verantwortlich ist, muss (1) genau angegeben werden, was die Anforderungen sind und (2) wie sich die Funktion bei einem ungültigen Argument verhalten soll.

Es kann leicht auf msg == NULL überprüfen - aber dann müssen Sie angeben, was es tun soll, wenn das passiert.

Es kann prüfen, auf das Vorhandensein eines '\0' Terminator innerhalb der ersten Zeichen N - aber dann müssen Sie den Wert von N angeben irgendwie (anscheinend der Autor erwartet einen zusätzlichen Längenparameter) und Sie zu sagen haben, wie Die Funktion sollte sich dann verhalten.

Es kann nicht nach einem ungültigen Nicht-Null-Argument suchen. Beispielsweise könnte ein Aufrufer einen Zeiger übergeben, der nicht initialisiert wurde oder der an free() übergeben wurde. Es gibt keinen tragbaren (und wahrscheinlich keinen nicht portablen) Weg, die Funktion auf diese Art von Fehler zu überprüfen.

Wenn die Funktion geändert wird, um ein Längenargument zu verwenden (was eine durchaus sinnvolle Änderung ist, die die Verwendung der Funktion sicherer macht), kann sie dennoch nicht alle möglichen Fehlerzustände prüfen. Ein Aufrufer könnte ein Längenargument übergeben, das nicht mit der Länge des tatsächlichen Arrays übereinstimmt. Es liegt immer noch in der Verantwortung des Anrufers, tolower korrekt anzurufen. Eine Spezifikation für eine Funktion ist ein Vertrag zwischen dem Anrufer und dem Angerufenen. Beide Seiten müssen die Spezifikation erfüllen. In Ermangelung einer solchen Spezifikation (über die Funktionsdeklaration hinaus, die uns einige Informationen gibt, aber nicht genug), ist es sehr schwierig zu sagen, dass die Funktion einen "Fehler" hat. Ein kleiner Punkt: Ich fand tatsächlich, was wahrscheinlich ist ein echter Fehler in der Funktion. Die Funktion toupper() übernimmt ein Argument vom Typ int, dessen Wert entweder gleich EOF oder im Bereich 0 bis UCHAR_MAX sein muss. Wenn die Ebene char standardmäßig signiert ist, ist es möglich, einen gültigen char Wert zu haben, der negativ und ungleich EOF ist. Das Ergebnis ist ein nicht definiertes Verhalten. Das Update für das ist das Argument zu unsigned char würfen

printf("%c", toupper((unsigned char)msg[i])); 

(Der EOF Sonderfall ist hier nicht relevant.)

Nun, da nicht wirklich ein Bug sein könnte. In Ermangelung einer Spezifikation könnten wir annehmen, dass die Zeichenfolge nur Zeichen mit nicht-negativen Werten enthalten sollte. Eine solche Einschränkung sollte jedoch ausdrücklich erwähnt werden.

Es gibt auch ein mögliches Portabilitätsproblem: je nach System könnte es möglich sein, eine Zeichenfolge länger als INT_MAX Bytes zu haben. Making i a size_t anstatt eine int würde dieses (zugegebenermaßen unwahrscheinliche) Problem vermeiden. Auch dies könnte als implizite Einschränkung betrachtet werden - solche Einschränkungen sollten jedoch, wann immer möglich, explizit gemacht werden.

Schließlich ist die Funktion so definiert, dass sie ein int Ergebnis zurückgibt, aber sie gibt immer 0 zurück. Es könnte sinnvoll sein, den Rückgabewert als Fehlerindikator zu verwenden. Eine allgemeine Konvention besteht darin, 0 für Erfolg, nicht Null für Fehler zurückzugeben. Wenn diese Version der Funktion als Grundlage für eine verbesserte Version gedacht ist, die mehr Fehlerprüfungen durchführt, ist die Rückgabe eines int Ergebnisses sinnvoll. Aber dann sollte es eine Spezifikation dessen geben, was dieses Ergebnis bedeutet.

+0

Dokumentation, genau. UV – Bathsheba

+0

wie Ihre umfassende Antwort – drdot

2

Es gibt zwei Probleme:

Das „string“ möglicherweise nicht nullterminierten sein.

Der Eingabezeiger msg könnte NULL sein.

Sie umgehen das erste Problem, indem Sie es entweder reparieren (indem Sie die Länge übergeben) oder eine klare Dokumentation schreiben, die den Funktionen der c-Standardbibliothek entspricht. Ich bevorzuge Letzteres, da die Berechnung der Länge vor dem Aufruf der Funktion zu zwei Traversierungen der Zeichenfolge führen kann, die schwerfällig ist.

Sie sollten sich vor dem zweiten Problem mit einem if-Block oder einem ähnlichen Konstrukt schützen.

+0

Ich mag die Fälle, die Sie aufgelistet haben. Aber wie würden Sie den Fehler beheben? – drdot

+0

Können Sie ein Beispiel für Ihre bevorzugte Lösung für die erste bereitstellen? Die zweite Lösung ist ziemlich offensichtlich. Passlänge ist leicht zu verstehen. Aber was meinst du mit klarer Dokumentation? – drdot

+0

@elixir 'für (i = 0; (msg [i]! = '\ 0') && (i user3078414