Wie verhält es sich beim Drucken von NULL mit dem %s-Spezifizierer von printf?

Lesezeit: 7 Minuten

Benutzeravatar von Deepanjan Mazumdar
Deepanjan Mazumdar

Stieß auf eine interessante Interviewfrage:

test 1:
printf("test %s\n", NULL);
printf("test %s\n", NULL);

prints:
test (null)
test (null)

test 2:
printf("%s\n", NULL);
printf("%s\n", NULL);
prints
Segmentation fault (core dumped)

Obwohl dies auf einigen Systemen gut laufen könnte, wirft zumindest meins einen Segmentierungsfehler. Was wäre die beste Erklärung für dieses Verhalten? Der obige Code ist in C.

Nachfolgend meine gcc-Informationen:

deep@deep:~$ gcc --version
gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3

  • Weder stürzt auf VS2010 ab. Es druckt nur (null) für den Nullzeiger.

    – Mystisch

    21. Juli 2012 um 4:05 Uhr

  • Ja richtig. Die Antwort könnte sehr architekturspezifisch sein.

    – Deepanjan Mazumdar

    21. Juli 2012 um 4:08 Uhr

  • Ich wusste nicht, dass du das kannst printf an erster Stelle ein Nullzeiger … Ich hatte erwartet, dass beide abstürzen würden. Sehen Sie nun, ob dies ein spezifiziertes, implementierungsdefiniertes, undefiniertes Verhalten oder nur ein Compiler-Fehler ist.

    – Mystisch

    21. Juli 2012 um 4:10 Uhr


  • Undefiniertes Verhalten bedeutet, dass es abstürzen könnte, es scheint erfolgreich zu sein, es könnte Ihre Festplatte löschen oder es könnte Sonnenschein und Welpen auf Ihrem Bildschirm anzeigen. Man weiß nie.

    – Adam Rosenfield

    21. Juli 2012 um 4:20 Uhr

  • Gemäß 7.1.4: Jede der folgenden Aussagen gilt, sofern in den folgenden detaillierten Beschreibungen nicht ausdrücklich anders angegeben: Wenn ein Argument für eine Funktion einen ungültigen Wert hat (z. B. einen Wert außerhalb der Domäne der Funktion oder einen Zeiger außerhalb der Adresse Bereich des Programms oder ein Nullzeiger oder ein Zeiger auf nicht modifizierbaren Speicher, wenn der entsprechende Parameter nicht konstant qualifiziert ist) oder ein Typ (nach der Heraufstufung), der von einer Funktion mit variabler Anzahl von Argumenten nicht erwartet wird, ist das Verhalten undefiniert .

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    21. Juli 2012 um 4:26 Uhr

Das wichtigste zuerst: printf erwartet einen gültigen (dh Nicht-NULL-)Zeiger für sein %s-Argument, daher ist das Übergeben einer NULL offiziell undefiniert. Es kann “(null)” ausgeben oder es kann alle Dateien auf Ihrer Festplatte löschen – entweder ist das Verhalten in Bezug auf ANSI korrekt (zumindest sagt mir das Harbison und Steele).

Davon abgesehen, ja, das ist wirklich seltsames Verhalten. Es stellt sich heraus, dass das passiert, wenn Sie ein einfaches tun printf so was:

printf("%s\n", NULL);

gcc ist (Hm) schlau genug, dies in einen Aufruf zu zerlegen
puts. Der Erste printfDies:

printf("test %s\n", NULL);

ist kompliziert genug, dass gcc stattdessen einen Aufruf an real aussendet
printf.

(Beachten Sie, dass gcc Warnungen zu Ihrer ungültigen Datei ausgibt printf Argument beim Kompilieren. Das liegt daran, dass es vor langer Zeit die Fähigkeit zum Parsen entwickelt hat *printf Zeichenfolgen formatieren.)

Sie können dies selbst sehen, indem Sie mit kompilieren -save-temps Option und dann das Ergebnis durchsehen .s Datei.

Als ich das erste Beispiel kompilierte, bekam ich:

movl    $.LC0, %eax
movl    $0, %esi
movq    %rax, %rdi
movl    $0, %eax
call    printf      ; <-- Actually calls printf!

(Kommentare wurden von mir hinzugefügt.)

Aber der zweite erzeugte diesen Code:

movl    $0, %edi    ; Stores NULL in the puts argument list
call    puts        ; Calls puts

Das Seltsame ist, dass es den folgenden Zeilenumbruch nicht druckt. Es ist, als ob es herausgefunden hätte, dass dies einen Segfault verursachen wird, also stört es nicht. (Was es hat – es hat mich gewarnt, als ich es kompiliert habe.)

  • Gute Antwort (obwohl R. Sie um ein paar Minuten geschlagen hat). Aber es gibt nichts Seltsames an dem Zeilenumbruch. puts() gibt die Argumentzeichenfolge nach stdout aus, gefolgt von einem Zeilenumbruch. (Im Gegensatz, fputs() druckt in eine angegebene Datei und nicht füge einen Zeilenumbruch hinzu.)

    – Keith Thompson

    21. Juli 2012 um 4:59 Uhr

  • Hm. Ich wusste nichts über den Zeilenumbruch, bis Sie es mir sagten. Ich denke, das liegt daran, dass ich stattdessen immer nur fputs verwendet habe.

    – Chris Reuter

    21. Juli 2012 um 20:52 Uhr

In Bezug auf die C-Sprache liegt der Grund darin, dass Sie undefiniertes Verhalten aufrufen und alles passieren kann.

Was die Mechanik anbelangt, warum dies geschieht, optimiert das moderne gcc printf("%s\n", x) zu puts(x)und puts hat nicht den dummen Code zum Drucken (null) wenn es einen Nullzeiger sieht, wohingegen gängige Implementierungen von printf habe diesen Sonderfall. Da gcc (im Allgemeinen) nicht-triviale Formatzeichenfolgen wie diese nicht optimieren kann, printf wird tatsächlich aufgerufen, wenn die Formatzeichenfolge anderen Text enthält.

  • Das habe ich gesucht. +1

    – Mystisch

    21. Juli 2012 um 4:24 Uhr

  • Ist dieses Verhalten beim Aufrufen von Puts und Printf in verschiedenen Szenarios Teil des C-Standards? Oder ein Compiler kann seine eigenen Mechanismen wählen. Seit auf Visual C++ funktioniert es gut.

    – Krishna Oza

    15. Februar 2014 um 7:22 Uhr

  • Der Compiler kann jede Transformation vornehmen, die das Verhalten eines gültigen Programms nicht ändert. (Hier hat es das Verhalten eines ungültigen geändert.)

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    15. Februar 2014 um 17:42 Uhr

Benutzeravatar von Jonathan Leffler
Jonathan Leffler

Abschnitt 7.1.4 (von C99 oder C11) sagt:

§7.1.4 Nutzung von Bibliotheksfunktionen

¶1 Jede der folgenden Aussagen gilt, sofern in den folgenden detaillierten Beschreibungen nicht ausdrücklich anders angegeben: Wenn ein Argument für eine Funktion einen ungültigen Wert hat (z. B. einen Wert außerhalb der Domäne der Funktion oder einen Zeiger außerhalb des Adressraums der Programm oder ein Nullzeiger oder ein Zeiger auf einen nicht änderbaren Speicher, wenn der entsprechende Parameter nicht konstant qualifiziert ist) oder ein Typ (nach der Heraufstufung), der von einer Funktion mit einer variablen Anzahl von Argumenten nicht erwartet wird, ist das Verhalten undefiniert.

Da die Spezifikation von printf() sagt nichts darüber aus, was passiert, wenn Sie einen Nullzeiger darauf übergeben %s Bezeichner ist das Verhalten explizit undefiniert. (Beachten Sie, dass das Übergeben eines Nullzeigers, der von der gedruckt werden soll, übergeben wird %p Bezeichner ist kein undefiniertes Verhalten.)

Hier ist das ‘Kapitel und Vers’ für die fprintf() Familienverhalten (C2011 – es ist eine andere Abschnittsnummer in C1999):

§7.21.6.1 Die fprintf-Funktion

s Wenn nein l Längenmodifikator vorhanden ist, muss das Argument ein Zeiger auf das Anfangselement eines Arrays vom Zeichentyp sein. […]

Wenn ein l Längenmodifikator vorhanden ist, muss das Argument ein Zeiger auf das Anfangselement eines Arrays vom Typ wchar_t sein.

p Das Argument soll ein Zeiger auf void sein. Der Wert des Zeigers wird in einer implementierungsdefinierten Weise in eine Folge von Druckzeichen umgewandelt.

Die Vorgaben für die s Konvertierungsbezeichner schließen die Möglichkeit aus, dass ein Nullzeiger gültig ist, da der Nullzeiger nicht auf das Anfangselement eines Arrays des entsprechenden Typs zeigt. Die Spezifikation für die p Der Konvertierungsbezeichner erfordert nicht, dass der void-Zeiger auf etwas Bestimmtes zeigt, und daher ist NULL gültig.

Die Tatsache, dass viele Implementierungen eine Zeichenfolge wie z (null) Wenn ein Nullzeiger übergeben wird, ist es eine Freundlichkeit, auf die man sich nicht verlassen kann. Das Schöne an undefiniertem Verhalten ist, dass eine solche Reaktion erlaubt, aber nicht erforderlich ist. Ebenso ist ein Absturz erlaubt, aber nicht erforderlich (eigentlich schade – Leute werden gebissen, wenn sie auf einem fehlerverzeihenden System arbeiten und dann auf andere weniger fehlerverzeihende Systeme portieren).

  • Da die Spezifikation von printf() nichts darüber aussagt, was passiert, wenn Sie für den Spezifizierer %s einen Nullzeiger darauf übergeben, ist das Verhalten explizit undefiniert. eigentlich heißt es Das Argument soll ein Zeiger auf das Anfangselement eines Arrays vom Zeichentyp sein und der Standard sagt die Verletzung von a soll das außerhalb einer Beschränkung auftritt, ist undefiniertes Verhalten (C99, 4.p2).

    – au

    5. August 2012 um 11:47 Uhr

  • @ouah: ist das, was Sie sagen, anders als der zitierte Abschnitt von §7.1.4 sagt (‘Nullzeiger … Verhalten ist undefiniert’), oder was ich sage, es sagt (‘Nullzeiger … Verhalten ist explizit undefiniert’)?

    – Jonathan Leffler

    5. August 2012 um 13:30 Uhr


  • Ich füge dies als zusätzlichen Hinweis hinzu: dass 4.p2 auch mit der Angabe von verwendet werden könnte printf um zu zeigen, dass der Anruf mit der NULL Argument ist UB. In meinem Kommentar das Wort eigentlich ist vielleicht überflüssig.

    – au

    5. August 2012 um 13:49 Uhr

Das NULL -Zeiger zeigt auf keine Adresse, und der Versuch, ihn zu drucken, führt zu undefiniertem Verhalten. Undefiniert bedeutet, dass Ihr Compiler oder Ihre C-Bibliothek entscheiden muss, was zu tun ist, wenn sie versucht, NULL auszugeben.

1416320cookie-checkWie verhält es sich beim Drucken von NULL mit dem %s-Spezifizierer von printf?

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

Privacy policy