Warum printf ("% s", (char []) {'H', 'i', '\ 0'}) funktioniert wie printf ("% s", "Hi"), aber printf ("% s ", (char *) {'H', 'i', '\ 0'}); scheitert? [Duplikat]

7

Ich brauche wirklich Hilfe zu diesem Thema. Es hat meine Basis in C. Long erschüttert und detaillierte Antworten werden sehr geschätzt. Ich habe meine Frage in zwei Teile geteilt.

A: Warum funktioniert printf("%s",(char[]){'H','i','Hi'}); und druckt printf("%s","Hi"); genauso wie das konventionelle (char[]){'H','i','"Hi"'} ? Können wir "Hi" als Ersatz für Hi irgendwo in unserem C-Code verwenden? "Haben sie das gleiche gemeint? Ich meine, wenn wir (char[]){'H','i','printf("%s",(char[]){'H','i','printf("%s","Hi")'})'} in C schreiben, heißt das im Allgemeinen, dass printf("%s",(char*){'A','B','char[]'} irgendwo im Speicher gespeichert ist und ein Zeiger darauf übergeben wird. Das gleiche gilt für die scheinbar hässliche char* . Sind sie genau gleich?

B: Wenn char* erfolgreich funktioniert, dasselbe wie char demo[] , warum dann char demo* große Zeit und seg-faults ausfällt, wenn ich es trotz der Warnungen starte? Es erstaunt mich nur, weil in% %code% nicht in %code% zerlegt werden soll, wie wenn wir es in Funktionsargumenten übergeben, warum tut es das hier nicht und %code% gibt Fehler? , übergibt %code% nicht als Argument an eine Funktion, die gleich %code% ist? Warum sind dann die Ergebnisse nicht gleich?

Bitte helfen Sie mir dabei. Ich habe das Gefühl, dass ich die Grundlagen von C. noch nicht verstanden habe. Ich bin sehr enttäuscht. Danke!

    
Thokchom 17.05.2013, 17:10
quelle

5 Antworten

7

Betreffend Snippet # 2:

Der Code funktioniert aufgrund einer neuen Funktion in C99, zusammengesetzte Literale genannt. Sie können über sie an verschiedenen Stellen lesen, einschließlich der GCC-Dokumentation , Mike Ashs Artikel und ein bisschen Google-Suche.

Im Wesentlichen erstellt der Compiler ein temporäres Array auf dem Stack und füllt es mit 3 Bytes - 0x48 , 0x69 und 0x00 . Dieses temporäre Array, das einmal erstellt wurde, wird dann zu einem Zeiger zerfallen und an die Funktion printf übergeben. Eine sehr wichtige Anmerkung zu zusammengesetzten Literalen ist, dass sie nicht wie die meisten C-Strings standardmäßig const sind.

Betreffend Snippet # 3:

Sie erstellen überhaupt kein Array - Sie werfen das erste Element in den skalaren Intializer, in diesem Fall H oder 0x48 in einen Zeiger. Sie können dies sehen, indem Sie die %s in Ihrer printf-Anweisung in eine %p ändern, was diese Ausgabe für mich ergibt:

%Vor%

Als solches müssen Sie sehr vorsichtig sein mit dem, was Sie mit zusammengesetzten Literalen tun - sie sind ein mächtiges Werkzeug, aber es ist einfach, sich mit ihnen in den Fuß zu schießen.

    
Richard J. Ross III 17.05.2013, 17:35
quelle
8

Ihr drittes Beispiel:

%Vor%

ist nicht einmal legal (streng genommen handelt es sich um eine Constraint-Verletzung ), und Sie sollten mindestens eine Warnung erhalten haben, als Sie sie kompiliert haben. Wenn ich es mit gcc mit Standardoptionen kompiliert habe, habe ich 6 Warnungen:

%Vor%

Das zweite Argument von printf ist ein zusammengesetztes Literal . Es ist zulässig (aber ungerade), ein zusammengesetztes Literal vom Typ char* zu haben, aber in diesem Fall ist der Initialisiererliste Teil des zusammengesetzten Literals ungültig.

Nach dem Drucken der Warnungen scheint gcc folgendes zu tun: (a) Konvertieren des Ausdrucks 'H' , der vom Typ int ist, in char* , was einen Abfallzeigerwert ergibt und (b) Ignorieren der Rest der Initialisierungselemente, 'i' und 'char*' . Das Ergebnis ist ein 0x48 -Zeigerwert, der auf die (wahrscheinlich virtuelle) Adresse int zeigt - unter der Annahme eines ASCII-basierten Zeichensatzes.

Ignorieren von überschüssigen Initialisierern ist zwar gültig (aber eine Warnung wert), aber es gibt keine implizite Konvertierung von char* nach -pedantic-errors (abgesehen von dem Spezialfall einer Null-Zeiger-Konstante, die hier nicht gilt). gcc hat seine Arbeit erledigt, indem es eine Warnung ausgegeben hat, aber es könnte (und IMHO sollte) es mit einer fatalen Fehlermeldung zurückgewiesen haben. Dies geschieht mit der Option "%s" .

Wenn Ihr Compiler Sie vor diesen Zeilen gewarnt hat, sollten Sie diese Warnungen in Ihre Frage aufnehmen. Wenn dies nicht der Fall ist, erhöhen Sie entweder die Warnstufe oder erhalten Sie einen besseren Compiler.

Gehen Sie detaillierter auf das ein, was in jedem der drei Fälle passiert:

%Vor%

Ein C-String-Literal wie "Hi" oder char erstellt ein anonymes statisch zugewiesenes Array von const . (Dieses Objekt ist nicht 'sizeof' , aber der Versuch, es zu ändern, hat undefiniertes Verhalten; dies ist nicht ideal, aber es gibt historische Gründe dafür.) Ein abschließendes & Nullzeichen wird hinzugefügt, um es zu einer gültigen Zeichenkette zu machen / p>

Ein Ausdruck des Array-Typs ist in den meisten Kontexten (die Ausnahmen sind, wenn es der Operand des unären Operators printf oder char* ist oder wenn es ein String-Literal in einem Initialisierer ist, der ein Array-Objekt initialisiert) implizit in einen Zeiger auf das erste Element des Arrays konvertiert ("zerfällt in"). Die beiden Argumente, die an printf übergeben wurden, sind vom Typ printf ; printf verwendet diese Zeiger, um die jeweiligen Arrays zu durchlaufen.

%Vor%

Dies verwendet eine Funktion, die der Sprache von C99 hinzugefügt wurde (die Ausgabe von 1999 des ISO-C-Standards), die als ein zusammengesetztes Literal bezeichnet wird. Es ähnelt einem String-Literal, indem es ein anonymes Objekt erstellt und auf den Wert dieses Objekts verweist. Ein zusammengesetztes Literal hat die Form:

%Vor%

und das Objekt hat den angegebenen Typ und wird auf den Wert initialisiert, der von der Initialisierungsliste angegeben wird.

Das obige ist fast gleichbedeutend mit:

%Vor%

Auch hier bezieht sich das zweite Argument auf anon auf ein Array-Objekt und es "verfällt" in einen Zeiger auf das erste Element des Arrays; char* verwendet diesen Zeiger, um das Array zu durchlaufen.

Zum Schluss:

%Vor%

wie Sie sagen, schlägt große Zeit. Der Typ eines zusammengesetzten Literals ist normalerweise ein Array oder eine Struktur (oder Union); es war mir tatsächlich nicht in den Sinn gekommen, dass es sich um einen skalaren Typ wie einen Zeiger handeln könnte. Das obige ist fast gleichbedeutend mit:

%Vor%

Offensichtlich ist printf vom Typ "%s" , was 'A' für ein int Format erwartet. Aber was ist der Anfangswert?

Der Standard erfordert, dass der Initialisierer für ein skalares Objekt ein einzelner Ausdruck ist, der optional in geschweifte Klammern eingeschlossen ist. Aber aus irgendeinem Grund ist diese Anforderung unter "Semantik", also ist es keine Einschränkung der Beschränkung, sie zu verletzen; es ist bloß undefiniertes Verhalten. Das bedeutet, dass der Compiler alles tun kann, was er möchte, und möglicherweise keine Diagnose ausstellt. Die Autoren von gcc haben anscheinend beschlossen, eine Warnung auszugeben und alle bis auf den ersten Initialisierer in der Liste zu ignorieren.

Danach wird es äquivalent zu:

%Vor%

Die Konstante int hat den Typ char (aus historischen Gründen ist es int anstatt char* , aber das gleiche Argument würde in beide Richtungen gelten). Es gibt keine implizite Konvertierung von -pedantic-errors nach A , und tatsächlich ist der obige Initialisierer eine Constraint-Verletzung. Das heißt, ein Compiler muss eine Diagnose (gcc do) ausgeben und darf das Programm ablehnen (gcc tut dies nur, wenn Sie int verwenden). Sobald die Diagnose ausgegeben ist, kann der Compiler tun, was er will; das Verhalten ist nicht definiert (es gibt einige sprachlich-anwaltliche Meinungsverschiedenheiten in diesem Punkt, aber es ist nicht wirklich wichtig). gcc wählt konvertieren den Wert von char* von 0x00000041 nach printf (wahrscheinlich aus historischen Gründen, wenn C noch weniger stark typisiert wurde als heute), was zu a führt Müll -Zeiger mit einer Darstellung, die wahrscheinlich wie %code% oder 0x0000000000000041 aussieht.

Dieser Abfallzeiger wird dann an %code% übergeben, der versucht, damit auf eine Zeichenfolge an dieser Stelle im Speicher zuzugreifen. Heiterkeit folgt.

Es gibt zwei wichtige Dinge zu beachten:

  1. Wenn der Compiler Warnungen ausgibt, achten Sie genau darauf. gcc gibt insbesondere Warnungen für viele Dinge aus, die IMHO fatale Fehler sein sollten. Nie ignorieren Warnungen, es sei denn, Sie verstehen, was die Warnung bedeutet, gründlich genug für Ihr Wissen, um die der Autoren des Compilers zu überschreiben.

  2. Arrays und Zeiger sind sehr unterschiedliche Dinge. Einige Regeln der C-Sprache scheinen sich zu verschwören, damit es so aussieht, als seien sie gleich. Sie können vorübergehend mit der Annahme fortfahren, dass Arrays nichts weiter als verkappte Zeiger sind, aber diese Annahme wird irgendwann wiederkehren, um Sie zu beißen. Lesen Sie Abschnitt 6 der FAQ von comp.lang.c ; Es erklärt die Beziehung zwischen Arrays und Zeigern besser als ich kann.

Keith Thompson 17.05.2013 17:42
quelle
3

(Ok ... jemand hat die Frage komplett überarbeitet. Die Antwort überarbeitet.)

Das Array # 3 enthält die Hexadezimalbytes. (Wir wissen nichts über den 4.):

48 49 00 xx

Wenn der Inhalt dieses Arrays übergeben wird, werden nur im zweiten Fall diese Bytes als Adresse der zu druckenden Zeichenfolge verwendet. Es hängt davon ab, wie diese 4 Bytes zu einem Zeiger in Ihrer tatsächlichen CPU-Hardware konvertieren, aber sagen wir, dass "414200FF" die Adresse ist (da wir erraten werden, dass das 4. Byte ein 0xFF ist. Wir machen das alles sowieso.) Wir nehmen auch an, dass ein Zeiger 4 Byte lang ist und eine Endian-Reihenfolge und solche Sachen. Es ist nicht wichtig für die Antwort, aber andere sind frei zu erklären.

Hinweis : Eine der anderen Antworten scheint zu glauben, dass es die 0x48 nimmt und sie auf ein (int) 0x00000048 erweitert und diesen Zeiger aufruft. Könnte sein. Aber wenn GCC das getan hat und @KiethThompson nicht gesagt hat, dass er den generierten Code überprüft hat, heißt das nicht, dass ein anderer C-Compiler dasselbe tun würde. Das Ergebnis ist in beiden Fällen gleich.

Dies wird an die Funktion printf () übergeben und versucht, zu dieser Adresse zu gehen, um einige Zeichen zum Drucken zu erhalten. (Seg-Fehler tritt auf, weil diese Adresse möglicherweise nicht auf der Maschine vorhanden ist und Ihrem Prozess nicht zum Lesen zugewiesen ist.)

Im Fall # 2 kennt es ein Array und keinen Zeiger, also übergibt es die Adresse des Speichers, in dem die Bytes gespeichert sind, und printf () kann das tun.

Siehe andere Antworten für eine formellere Sprache.

Eine Sache, über die man nachdenken sollte, ist, dass zumindest ein C-Compiler wahrscheinlich keinen Aufruf von printf von einem Aufruf an irgendeine andere Funktion kennt. Also nimmt es den "format string" und speichert einen Zeiger für den Aufruf (der zufällig zu einer Zeichenkette gehört) und nimmt dann den 2. Parameter und speichert alles, was er gemäß der Deklaration der Funktion erhält, ob ein int oder a char oder ein Zeiger für den Aufruf. Die Funktion zieht diese dann heraus, wenn der Anrufer sie entsprechend der gleichen Deklaration eingibt. Die Deklaration für den zweiten und größeren Parameter muss etwas wirklich Generisches sein, um Zeiger, int, double und all die verschiedenen Typen akzeptieren zu können, die da sein könnten. (Was ich sagen will, ist, dass der Compiler wahrscheinlich nicht auf die Formatzeichenfolge schaut, wenn er entscheidet, was mit dem zweiten und den folgenden Parametern zu tun ist.)

Es könnte interessant sein zu sehen, was passiert für:

%Vor%

Vorhersagen?

    
Lee Meador 17.05.2013 17:20
quelle
2

In jedem Fall erstellt der Compiler ein initialisiertes Objekt vom Typ char [3]. Im ersten Fall behandelt es das Objekt als ein Array, also übergibt es einen Zeiger an sein erstes Element an die Funktion. Im zweiten Fall behandelt es das Objekt als einen Zeiger, also übergibt es den Wert des Objekts. printf erwartet einen Zeiger, und der Wert des Objekts ist ungültig, wenn er als Zeiger behandelt wird, so dass das Programm zur Laufzeit abstürzt.

    
William Pursell 17.05.2013 17:22
quelle
-1

Die dritte Version sollte nicht einmal kompilieren. 'H' ist kein gültiger Initializer für einen Zeigertyp. GCC gibt Ihnen standardmäßig eine Warnung, aber keinen Fehler.

    
R.. 17.05.2013 17:39
quelle

Tags und Links