Lernen von C, würde sich über die Gründe für diese Lösung freuen

7

Das ist buchstäblich das Erste, was ich jemals in C geschrieben habe. Also, bitte zögern Sie nicht, auf alle Fehler hinzuweisen. :) Mein Problem ist jedoch folgendes: Wenn ich das Programm so schreibe, wie ich es am saubersten finde, bekomme ich ein kaputtes Programm:

%Vor%

Wie es aussieht, erzeugt das obige keine Fehler beim Kompilieren, aber gibt mir einen segfault, wenn ich ran. Ich kann das beheben, indem ich das Programm so ändere, dass cmd als Referenz übergeben wird, wenn scanf und strcmp aufgerufen werden. Der segfault verschwindet und wird durch Warnungen für jede Verwendung von strcmp zur Kompilierzeit ersetzt. Trotz der Warnungen funktioniert der betroffene Code.

  

Warnung: Übergeben von arg 1 von 'strcmp' von inkompatiblen Zeigertyp

Als zusätzlichen Bonus erlaubt das Ändern der scanf- und strcmp-Aufrufe, dass das Programm weit genug fortschreitet, um return (0) auszuführen, wobei die Sache zu diesem Zeitpunkt mit einer Abort-Falle abstürzt. Wenn ich return (0) für exit (0) austausche, funktioniert alles wie erwartet.

Das hinterlässt zwei Fragen: Warum war das ursprüngliche Programm falsch? Wie kann ich es besser beheben als ich?

Das bisschen darüber, dass ich exit anstelle von return benutzen muss, hat mich besonders verwirrt.

    
Keifer 09.06.2010, 17:18
quelle

9 Antworten

11

Dies geschieht aufgrund der scanf-Anweisung.

Schauen Sie, wie cmd auf NULL zeigt. Wenn scanf ausgeführt wird, schreibt es an die Adresse von cmd, was NULL ist, wodurch ein segfault erzeugt wird.

Die Lösung besteht darin, einen Puffer für cmd zu erstellen, beispielsweise:

%Vor%

Jetzt kann Ihr Puffer 20 Zeichen enthalten. Allerdings müssen Sie sich nun Gedanken über Pufferüberläufe machen, wenn ein Benutzer mehr als 20 Zeichen eingibt.

Willkommen bei C.

BEARBEITEN: Beachten Sie auch, dass Ihre Kredit-, Debit- und Servicegebührenfunktionen nicht wie erwartet funktionieren, da Sie sie geschrieben haben. Dies liegt daran, dass die Parameter als Wert übergeben werden , nicht als Referenz. Dies bedeutet, dass nach der Rückkehr der Methode alle Änderungen verworfen werden. Wenn Sie möchten, dass sie die von Ihnen angegebenen Argumente ändern, ändern Sie die Methoden wie folgt:

Stornokredit (unsigned int * acct, int * Betrag)

Und nenn sie dann wie:

%Vor%

Dadurch werden die Parameter als Referenz übergeben, was bedeutet, dass alle Änderungen, die Sie innerhalb der Kreditfunktion vornehmen, die Parameter beeinflussen, selbst wenn die Funktion zurückkehrt.

    
samoz 09.06.2010, 17:20
quelle
7

Sie ordnen Speicher für cmd nicht zu, also ist es NULL .

Versuchen Sie es mit etwas Platz zu deklarieren:

%Vor%     
dcp 09.06.2010 17:19
quelle
5

Wie andere darauf hingewiesen haben, haben Sie nichts für scanf zum Einlesen vergeben. Aber Sie sollten auch den Rückgabewert von scanf testen:

%Vor%

Die scanf-Funktion gibt die Anzahl der erfolgreichen Conversions zurück. Wenn jemand also XXXX eingibt, erwartet man eine ganze Zahl, die man erkennen und damit umgehen will. Aber ehrlich gesagt wird der Benutzerschnittstellencode, der scanf () verwendet, nie wirklich ein Beweis gegen solche Dinge sein. scanf () sollte eigentlich formatierte Dateien lesen, nicht zufällige Eingaben von Menschen.

    
anon 09.06.2010 17:28
quelle
4

Dies:

%Vor%


Sollte sein:

%Vor%



Bitte beachten Sie: Sie sollten sicherstellen, dass die vom Benutzer in cmd eingegebene Zeichenfolge die Länge weniger als 100 oder n

hat     
Betamoo 09.06.2010 17:21
quelle
2

cmd wird mit einem Null-Zeiger initialisiert, der niemals auf irgendeinen Speicher zeigt. scanf überprüft nicht, dass cmd gültig ist, bevor er versucht, auf das zu schreiben, auf das cmd verweist.

Eine vorläufige Lösung schafft stattdessen etwas Platz für cmd, auf den verwiesen werden soll:

%Vor%

Dies ist jedoch eine sehr gefährliche Bewegung, weil Sie immer noch segfaults erhalten, wenn die Eingabe länger als erwartet ist und scanf versucht, in cmd [30] und darüber hinaus zu schreiben.

Aus diesem Grund gilt scanf als unsicher und sollte nicht im Produktionscode verwendet werden. Zu den sichereren Alternativen gehört die Verwendung von fgets zum Lesen einer Eingabezeile und von sscanf zum Verarbeiten.

Leider ist C I / O sehr schwer zu erreichen, ohne die Möglichkeit eines Pufferüberlaufs in Ihr Programm einzuführen. Sie müssen immer darüber nachdenken, wie viel Speicher Ihnen zur Verfügung steht und ob es ausreicht, um die längste mögliche Eingabe zu speichern, die Sie erhalten könnten. Sie müssen auch die Rückgabewerte der meisten E / A-Funktionen auf Fehler überprüfen.

    
Philip Potter 09.06.2010 17:31
quelle
2

In Ihrem Beispiel wird scanf() ein Nullzeiger übergeben.

%Vor%

scanf () wird keinen Platz für die Zeichenkette reservieren - Sie müssen einen Platz für die Zeichenkette reservieren, damit sie gehen kann.

%Vor%

Sie erhalten einen Segmentierungsfehler, weil scanf() versucht, seine Ausgabe in nicht zugeordneten Speicherplatz zu schreiben.

    
David Lively 09.06.2010 17:22
quelle
1

Andere haben auf den Fehler in Ihrem Programm hingewiesen, aber für ein besseres Verständnis der Zeiger, da Sie gerade anfangen, C zu lernen, schauen Sie sich das an Frage bei SO.

    
omermuhammed 09.06.2010 17:24
quelle
1

Ihr Grundproblem besteht darin, dass Sie keinen Speicher für Ihre Zeichenfolge zugewiesen haben. In C sind Sie für die gesamte Speicherverwaltung verantwortlich. Wenn Sie Variablen auf dem Stack deklarieren, ist dies einfach. Mit Zeigern ist es etwas schwieriger. Da Sie die Zeile char* str = NULL haben, schreiben Sie Bytes nach scanf , wenn Sie versuchen, NULL hinein zu schreiben, was illegal ist. Was der %s -Spezifizierer tut, schreibt in was str zeigt; Es kann str nicht ändern, da Parameter an Wert übergeben werden. Deshalb musst du &acct anstatt nur acct übergeben.

Wie reparierst du es? Sie müssen Speicher bereitstellen, in dem die eingelesene Zeichenfolge leben kann. So etwas wie char str[5] = "" . Dies macht str zu einem aus fünf Elementen bestehenden Zeichenarray, groß genug, um "exit" und sein abschließendes Nullbyte zu enthalten. (Arrays zerfallen bei der kleinsten Provokation in Zeiger, also geht es uns an dieser Front gut.) Das ist jedoch gefährlich. Wenn der Benutzer die Zeichenfolge malicious eingibt, werden Sie "malic" in str und die Bytes für "iciousscanf" in das schreiben, was danach im Speicher kommt. Dies ist ein Pufferüberlauf und ist ein klassischer Fehler. Der einfachste Weg, dies hier zu beheben, besteht darin, dass der Benutzer einen Befehl mit höchstens N Buchstaben eingeben muss, wobei N der längste Befehl ist, den Sie haben. In diesem Fall ist N = 4. Dann können Sie scanf("%4s %u %i", cmd, &acct, &amt) anweisen, maximal vier Zeichen einzulesen: %4s . Der malformed 3 4 sagt "lies höchstens vier Zeichen", damit du keinen anderen Speicher vermasselst. Beachten Sie jedoch, dass Sie, wenn der Benutzer ormed eingibt, die 3 und die 4 nicht finden können, da Sie sich scanf("%s %u %i", &cmd, &acct, &amount) ansehen.

Der Grund, warum Sie &cmd verwenden könnten, ist, dass C nicht typsicher ist. Wenn Sie char** angegeben haben, haben Sie ein char* ; es war jedoch glücklich, es als cmd zu behandeln. Daher hat es Bytes über exit geschrieben. Wenn Sie also die Zeichenfolge cmd übergeben haben, könnte 0x65786974 (wenn es 4 Byte breit wäre und die entsprechende Endianness hätte) gleich% co_de sein % (0x65 = e , 0x78 = x , 0x69 = i , 0x74 = t ). Und dann das Nullbyte oder irgendwelche anderen Bytes, die Sie übergaben, würden Sie anfangen, über willkürlichen Speicher zu schreiben. Wenn Sie es jedoch auch in strcmp ändern, wird auch der Wert von str als String behandelt und alles wird konsistent sein. Warum return 0; fehlschlägt, aber exit(0) funktioniert, bin ich mir nicht sicher, aber ich schätze: Sie haben vielleicht die Absenderadresse von main geschrieben. Das ist auch auf dem Stapel gespeichert, und wenn es im Stack-Layout nach cmd kommt, dann können Sie es auf Null setzen oder es kritzeln. Nun muss exit seine Bereinigung manuell vornehmen, an die richtigen Stellen springen usw. Wenn jedoch (wie ich denke, der Fall ist, obwohl ich mir nicht sicher bin) main verhält sich wie jede andere Funktion, es return springt zu dem Speicherplatz auf dem Stapel, der als Rückgabeadresse gespeichert ist (was wahrscheinlich eine Art von Aufräumroutine ist). Da Sie jedoch darüber gekritzelt haben, erhalten Sie einen Abbruch.

Nun, es gibt noch ein paar andere kleine Verbesserungen, die Sie machen könnten. Erstens, da du done als Boolean behandelst, solltest du while (!done) { ... } loopen. Zweitens erfordert das aktuelle Setup, dass Sie exit 1 1 schreiben, um das Programm zu beenden, obwohl das 1 1 -Bit nicht notwendig sein sollte. Drittens sollten Sie überprüfen, ob Sie alle drei Argumente erfolgreich gelesen haben, damit Sie keine Fehler / Inkonsistenzen erhalten; zum Beispiel, wenn Sie das nicht beheben, dann die Eingabe

%Vor%

Ruft debit(1,2) und debit(3,2) auf, während die a in der Eingabe verbleiben, um Sie auszulösen. Schließlich sollten Sie sauber mit EOF beenden, anstatt für immer das letzte zu tun, was Sie getan haben. Wenn wir das zusammensetzen, erhalten wir den folgenden Code:

%Vor%

Beachten Sie, dass Sie alle Verwendungen von done durch break ersetzen und die Deklaration von done entfernen können, wenn kein "Bereinigungscode" vorhanden ist, und geben Sie die schönere Schleife an

%Vor%     
Antal Spector-Zabusky 09.06.2010 17:59
quelle
0

Um zu verstehen, was hier vor sich geht, müssen Sie einige Grundlagen über C-Zeiger verstehen. Ich schlage vor, Sie schauen hier, wenn Sie wirklich neu in C sind:

Ссылка

Die häufigste Ursache für segfaults finden Sie hier:

Ссылка

    
logancautrell 09.06.2010 18:02
quelle

Tags und Links