2013-03-31 8 views
7

Wenn ich in C++ programmiere, werde ich oft cout Anweisungen mit einem Newline (\n) beenden. Mein Instinkt war jedoch immer, diesen Zeilenumbruch als Zeichenfolgenliteral auszudrücken: "\n", obwohl es ein einzelnes Zeichen ist und effizienter ausgedrückt werden kann ('\n'). Zum Beispiel:Sind 1-Zeichen-String-Literale jemals für einfache Zeichenliterale optimiert?

cout << "The value of var is " << var << "\n"; 

Es gibt einen viele Code mit diesem Phänomen. So wird die Frage wie folgt:

  1. Gibt es einen Unterschied whatsoever in der Effizienz der beiden unterschiedlichen Möglichkeiten, den Newline-Zeichen-Konstante zum Ausdruck? Ich mache mir keine Gedanken darüber, ob ich tatsächlich einen Unterschied in der Ausführung des produzierten Programms machen könnte (was ich für trivial halte). eher ärgert es mich, dass irgendeine Effizienz, wie klein sie auch sein mag, ohne Grund verloren gehen könnte.

  2. Wenn die String-Literal-Version weniger effizient ist, wird der Compiler sie auf die Version mit konstanter Zeichenanzahl optimieren, da die beiden das gleiche Verhalten bieten?

  3. Ich bin auch vertraut mit std::endl. Die Dokumentation besagt, dass "dieser Manipulator oft irrtümlicherweise verwendet wird, wenn eine einfache neue Zeile gewünscht wird, was zu einer schlechten Pufferungsleistung führt." Und zeigt auf this article für weitere Informationen. Dieser Artikel gibt jedoch an, dass die erwähnte "schlechte Leistung" nur für Datei-E/A gilt und dass die Verwendung von endl zum Schreiben auf den Bildschirm tatsächlich die Leistung verbessern kann. Was ist damit los?

Ich habe die C++ Standardbibliothek gesucht, konnte aber nicht die Implementierungen der relevanten Überlastungen des << Betreiber finden. Ich fand die Erklärungen in ostream.tcc:

extern template ostream& operator<<(ostream&, char); 
extern template ostream& operator<<(ostream&, const char*); 

aber keine Hinweise darauf, wie die Mechanik bei der Umsetzung einkochen.

Dies ist eher eine theoretische Frage als alles, also bin ich nicht interessiert zu lesen "Es gibt keinen praktischen Unterschied zwischen den beiden." Ich weiß das. Ich frage mich nur, ob überhaupt ein Unterschied besteht und wie der Compiler damit umgeht.

+1

Das einzige "one char string literal", das existiert, ist eine leere Zeichenfolge. Vergessen Sie niemals den Nullabschluss. –

+0

'" \ n "' ist vom Typ 'const char [2]', wie kann das * auf ein einzelnes Zeichen * optimiert werden? Außerdem scheint Ihre Frage 3 unabhängig zu sein und sollte wahrscheinlich als separate Frage veröffentlicht werden. – Praetorian

+2

das OP ist sich der Speicherdifferenz voll bewusst, Jungs. das ist im ersten Absatz abgedeckt. Das OP fragt, ob der c-String durch den Compiler für diesen speziellen Ein-Zeichen-Fall ersetzt werden kann. Das einzelne Zeichen benötigt keinen Terminator. – justin

Antwort

3

Sie sind wahrscheinlich auf eine Zeichenfolge (pro Kompilierungseinheit) optimiert - die meisten Compiler "verschmelzen Strings des gleichen Inhalts".

Ich würde erwarten, dass es sehr wenig praktischen Unterschied abgesehen von der Tatsache, dass Sie einen Zeiger auf eine einzelne Zeichenkette übergeben.

Um Ihre konkreten Fragen:

  1. Ja, es gibt einen kleinen Unterschied, wie ein char * wird einige indirection erfordern und damit ein paar zusätzliche Befehle erzeugen ausgeführt werden. Für die Konsolenausgabe (statt Ausgabe in Datei) ist es nicht wichtig, wie beim Scrollen der Konsole, auch im Vollbild-Textmodus> 100x mehr Befehle.
  2. Zweifel es.
  3. std::endl wird also den Puffer leeren, was tatsächlich die Ausgabe auf Dateien reduziert, weil Teilsektoren oder Blöcke in die Datei geschrieben werden, was den Systemaufruf-Overhead erhöht. Wenn Sie "\n" verwenden, wird die Datei nicht geleert, bis der Puffer selbst gefüllt ist, was mindestens 512 Byte, möglicherweise so viel wie einige zehn Kilobyte wäre. Was aber die Antwort Nr. 1 anbelangt, hängt die Leistung der Konsolenausgabe mehr von der Geschwindigkeit ab, mit der der Bildschirm scrollen kann.
1

Ich bezweifle es stark, weil es sowohl das Speicherlayout ändert (eines hat einen Null-Terminator, das andere nicht), und weil es den tatsächlichen Typ des Literals ändern würde (und dadurch das Funktion, die aufgerufen wird). Es wäre daher in den allermeisten Fällen eine ungültige Transformation und nicht genug eine Hilfe in der winzigen Minderheit zu zählen.

Das heißt, wenn der Compiler genug aggressive Inlining (Inlining die Funktion selbst und die Konstante Daten in die Funktion), können Sie effektiv mit dem gleichen Code enden. Zum Beispiel stellt Clang die folgende:

#include <iostream> 
using namespace std; 

int main() { 
    cout << "X" << "\n"; 
    cout << "Y" << '\n'; 
}    

in diese:

movq std::[email protected](%rip), %rbx 
leaq L_.str(%rip), %rsi 
movq %rbx, %rdi 
movl $1, %edx 
callq std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long) 
leaq L_.str1(%rip), %rsi 
movq %rbx, %rdi 
movl $1, %edx 
callq std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long) 
leaq L_.str2(%rip), %rsi 
movq %rbx, %rdi 
movl $1, %edx 
callq std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long) 
leaq -9(%rbp), %rsi 
movb $10, -9(%rbp) 
movq %rbx, %rdi 
movl $1, %edx 
callq std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long) 
xorl %eax, %eax 
addq $8, %rsp 
popq %rbx 
popq %rbp 

Wie Sie sehen können, inlining die beiden Fälle nahezu identisch gemacht hat.(. Und in der Tat, der '\n' Fall ist etwas komplexer, da der Charakter auf dem Stapel gelegt werden muss)

+2

Auf der anderen Seite erinnere ich mich, dass 'printf (" \ n ")' zu 'putc ('\ n')' optimiert wurde, also würde mich eine solche Optimierung nicht so überraschen. –

+0

'printf' ist ein C-Built-in, das in vielerlei Hinsicht optimiert wird. Zum Beispiel wird 'printf (" blah \ n ")' zu 'puts (" blah ")'. – nneonneo

2

Der Unterschied zwischen Stringliteral \n und endl ist, dass:

\n ist ein Zeichenfolgenliteral, dass an stdout angehängt werden. endl wird auch das Newline-Zeichen an stdout anhängen, jedoch wird es auch den stdout-Puffer leeren. Daher kann es mehr Verarbeitung erfordern. Abgesehen davon sollte es keinen praktischen Unterschied geben.