Wie kennt printf die Adresse der Zeichendaten eines CString?

7

Betrachtet dieses Codefragment:

%Vor%

Dieses Snippet ergibt die "intuitive" Ausgabe "aha". Aber ich habe keine Ahnung, wie das funktionieren kann:

  • Wenn der variadic-function-Aufruf so übersetzt wird, dass er die Zeiger der Argumente verschiebt, erhält printf ein CStringA* , das als const char* interpretiert wird.
  • Wenn der Variadic-Funktionsaufruf operator (const char*) aufruft, warum sollte das dann nicht für meine eigene Klasse geschehen?

Kann jemand das erklären?

BEARBEITEN: Eine Dummy-Variadic-Funktion hinzugefügt, die ihre Argumente als const char* s behandelt. Siehe - es stürzt sogar ab, wenn es das Argument my erreicht ...

    
xtofl 04.02.2011, 13:43
quelle

7 Antworten

7

Der relevante Text von C ++ 98 Standard §5.2.2 / 7:

  

Die Standardkonvertierungen von lvalue-to-rvalue (4.1), array-to-pointer (4.2) und function-to-pointer (4.3) werden für den Argumentausdruck durchgeführt. Wenn nach diesen Konvertierungen das Argument keine Arithmetik, Aufzählung, Zeiger, Zeiger auf Member oder Klassentyp aufweist, ist das Programm schlecht formatiert. Wenn das Argument einen Nicht-POD-Klassentyp (Klausel 9) hat, ist das Verhalten nicht definiert.

Also ist das Verhalten formal undefiniert .

Ein bestimmter Compiler kann jedoch eine beliebige Anzahl von Spracherweiterungen bereitstellen, und Visual C ++ tut dies auch. Die MSDN Library dokumentiert das Verhalten von Visual C ++ wie folgt in Bezug auf die Übergabe von Argumenten an ... :

  • Wenn das tatsächliche Argument vom Typ float ist, wird es vor dem Funktionsaufruf auf double hochgestuft.
  • Alle signierten oder unsignierten Char-, Short-, Enumerated-Type- oder Bit-Felder werden entweder in einen signierten oder einen unsignierten Int konvertiert, der eine integrierte Promotion verwendet.
  • Jedes Argument des Klassentyps wird als Datenstruktur als Wert übergeben; Die Kopie wird durch binäres Kopieren erstellt, anstatt den Kopierkonstruktor der Klasse aufzurufen (falls vorhanden).

Dies erwähnt nichts über Visual C ++, das benutzerdefinierte Konvertierungen anwendet.

Es kann sein, dass CString::Format , was die Funktion ist, an der Sie wirklich interessiert sind, vom letzten Punkt abhängt.

Prost & amp; hth.,

    
Cheers and hth. - Alf 04.02.2011, 14:37
quelle
6

Was Sie tun, ist ein nicht definiertes Verhalten. Es handelt sich entweder um eine nicht standardmäßige Erweiterung, die von Ihrem Compiler bereitgestellt wird, oder um reines Glück. Ich vermute, dass der CString die String-Daten als erstes Element in der Struktur speichert und somit das Lesen von CString , als ob es ein char * wäre, eine gültige nullterminierte Zeichenkette ergibt.

    
Tim Martin 04.02.2011 13:47
quelle
4

Sie können Nicht-POD-Daten nicht in variable Funktionen einfügen. Weitere Informationen

    
quelle
1
  

Wenn der Variadic-Funktionsaufruf den Operator (const char *) darauf aufruft, warum sollte er das nicht für meine eigene Klasse tun?

Ja, aber Sie sollten es explizit in Ihren Code umsetzen: printf("%s", (LPCSTR)s, ...); .

    
Benoit 04.02.2011 13:48
quelle
1

Es tut es nicht. Es ruft nicht einmal operator const char* an. Visual C ++ übergibt die Klassendaten einfach an printf als ob von memcpy . Es funktioniert wegen des Layouts der Klasse CString : Es enthält nur eine Elementvariable, die ein Zeiger auf die Zeichendaten ist.

    
dan04 04.02.2011 14:51
quelle
1
  

Wenn der Variadic-Funktionsaufruf übersetzt wird, indem man die Zeiger der Argumente verschiebt, ...

So funktionieren variadische Funktionen nicht. Die -Werte der Argumente und nicht die Zeiger auf die Argumente werden nach speziellen Konvertierungsregeln für integrierte Typen übergeben (z. B. char an int).

C ++ 03 §5.2.2p7:

  

Wenn für ein gegebenes Argument kein Parameter vorhanden ist, wird das Argument so übergeben, dass die empfangende Funktion den Wert des Arguments durch Aufruf von va_arg (18.7) erhalten kann. Die Standardkonvertierungen von lvalue-to-rvalue (4.1), array-to-pointer (4.2) und function-to-pointer (4.3) werden für den Argumentausdruck durchgeführt. Wenn nach diesen Konvertierungen das Argument keine Arithmetik, Aufzählung, Zeiger, Zeiger auf Member oder Klassentyp aufweist, ist das Programm schlecht formatiert. Wenn das Argument einen Nicht-POD-Klassentyp hat (Klausel 9), ist das Verhalten nicht definiert. Wenn das Argument einen Integral- oder Aufzählungstyp aufweist, der den Integral-Promotions (4.5) unterliegt, oder ein Gleitkommatyp, der der Gleitkomma-Heraufstufung (4.6) unterliegt, wird der Wert des Arguments vor dem Aufruf in den Typ mit der Priorität umgewandelt . Diese Werbeaktionen werden als Standardargumente für Werbeaktionen bezeichnet.

Insbesondere von oben:

  

Wenn das Argument einen Nicht-POD-Klassentyp hat (Klausel 9), ist das Verhalten nicht definiert.

C ++ stuft C für die Definition von va_arg ein, und C99 TC3 §7.15.1.2p2 sagt:

  

... wenn der Typ nicht mit dem Typ des eigentlichen nächsten Arguments kompatibel ist (wie gemäß den Standardargumentenpromotions hochgestuft), ist das Verhalten mit Ausnahme der folgenden Fälle nicht definiert: [Liste der Fälle, die hier nicht zutreffen]

Wenn Sie also einen Klassentyp übergeben, muss es POD sein, und die empfangende Funktion muss den korrekten Typ anwenden, andernfalls ist das Verhalten nicht definiert. Dies bedeutet, dass es im schlimmsten Fall genau so funktioniert, wie Sie es erwarten.

Printf wendet den richtigen Typ für einen benutzerdefinierten Klassentyp nicht an, da er keine Kenntnis von ihnen hat. Daher können Sie keinen UDT-Klassentyp an printf übergeben. Ihr foo macht dasselbe, indem Sie anstelle des korrekten Klassentyps einen char-Zeiger verwenden.

    
Fred Nurk 04.02.2011 14:23
quelle
0

Ihre printf-Anweisung ist falsch:

%Vor%

Sollte sein:

%Vor%

Was "aha mein" ausgibt.

CString hat einen Konvertierungsoperator für const char* (das ist eigentlich für LPCTSTR , das ist ein const TCHAR* - CStringA hat eine Konvertierungsfunktion für LPCSTR ).

Der Aufruf printf konvertiert Ihr CStringA Objekt nicht in einen CStringA* Zeiger. Es behandelt es im Wesentlichen wie ein void* . Im Fall von CString ist es reines Glück (oder vielleicht Design von Microsofts Entwicklern, die etwas ausnutzen, das nicht im Standard ist), dass es Ihnen die NULL-terminierte Zeichenkette geben wird. Wenn Sie stattdessen ein _bstr_t verwenden würden (welches die Größe der Zeichenkette als erstes hat), würde es trotz der Konvertierungsfunktion schrecklich ausfallen.

Es ist eine gute Übung (und in vielen Fällen erforderlich), Ihre Objekte / Zeiger beim Aufruf von printf (oder einer anderen Variadic-Funktion) explizit so zu deklarieren, wie Sie möchten.

    
Zac Howland 04.02.2011 13:49
quelle