Die goldene C ++ "Als-ob" -Regel 1 besagt, dass der Compiler , wenn das beobachtbare Verhalten eines Programms nicht von einer nicht verwendeten Existenz eines Datenelements abhängt, es wegoptimieren darf .
Nimmt eine nicht verwendete Mitgliedsvariable Speicherplatz ein?
Nein (wenn es "wirklich" unbenutzt ist).
Nun kommen zwei Fragen in den Sinn:
- Wann würde das beobachtbare Verhalten nicht von der Existenz eines Mitglieds abhängen?
- Treten solche Situationen in realen Programmen auf?
Beginnen wir mit einem Beispiel.
Beispiel
#include <iostream>
struct Foo1
{ int var1 = 5; Foo1() { std::cout << var1; } };
struct Foo2
{ int var1 = 5; int var2; Foo2() { std::cout << var1; } };
void f1() { (void) Foo1{}; }
void f2() { (void) Foo2{}; }
Wenn wir gcc bitten , diese Übersetzungseinheit zu kompilieren , wird Folgendes ausgegeben:
f1():
mov esi, 5
mov edi, OFFSET FLAT:_ZSt4cout
jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
f2():
jmp f1()
f2
ist das gleiche wie f1
und es wird nie ein Speicher verwendet, um eine tatsächliche zu halten Foo2::var2
. ( Clang macht etwas Ähnliches ).
Diskussion
Einige mögen sagen, dass dies aus zwei Gründen anders ist:
- Dies ist ein zu triviales Beispiel.
- Die Struktur ist vollständig optimiert, sie zählt nicht.
Nun, ein gutes Programm ist eher eine intelligente und komplexe Zusammenstellung einfacher Dinge als eine einfache Gegenüberstellung komplexer Dinge. Im wirklichen Leben schreiben Sie Tonnen einfacher Funktionen mit einfachen Strukturen, die der Compiler nicht optimiert. Zum Beispiel:
bool insert(std::set<int>& set, int value)
{
return set.insert(value).second;
}
Dies ist ein echtes Beispiel dafür, dass ein Datenelement (hier std::pair<std::set<int>::iterator, bool>::first
) nicht verwendet wird. Erraten Sie, was? Es wird weg optimiert ( einfacheres Beispiel mit einem Dummy-Set wenn diese Baugruppe Sie zum Weinen bringt).
Jetzt wäre der perfekte Zeitpunkt, um die ausgezeichnete Antwort von Max Langhof zu lesen (bitte für mich). Es erklärt, warum das Konzept der Struktur auf Assembly-Ebene, die der Compiler ausgibt, letztendlich keinen Sinn ergibt.
"Aber wenn ich X mache, ist die Tatsache, dass das nicht verwendete Mitglied wegoptimiert ist, ein Problem!"
Es gab eine Reihe von Kommentaren, in denen argumentiert wurde, dass diese Antwort falsch sein muss, da eine Operation (wie assert(sizeof(Foo2) == 2*sizeof(int))
) etwas kaputt machen würde.
Wenn X Teil des beobachtbaren Verhaltens von Programm 2 ist, darf der Compiler keine optimierten Dinge entfernen. Es gibt viele Operationen an einem Objekt, die ein "nicht verwendetes" Datenelement enthalten, was sich beobachtbar auf das Programm auswirken würde. Wenn eine solche Operation ausgeführt wird oder der Compiler nicht nachweisen kann, dass keine ausgeführt wird, ist dieses "nicht verwendete" Datenelement Teil des beobachtbaren Verhaltens des Programms und kann nicht wegoptimiert werden .
Operationen, die das beobachtbare Verhalten beeinflussen, umfassen, sind aber nicht beschränkt auf:
- die Größe eines Objekttyps annehmen (
sizeof(Foo)
),
- die Adresse eines Datenelements nehmen, das nach dem "nicht verwendeten" deklariert wurde,
- Kopieren des Objekts mit einer Funktion wie
memcpy
:
- Manipulieren der Darstellung des Objekts (wie bei
memcmp
),
- ein Objekt als flüchtig qualifizieren ,
- etc .
1)
[intro.abstract]/1
Die semantischen Beschreibungen in diesem Dokument definieren eine parametrisierte nichtdeterministische abstrakte Maschine. Dieses Dokument stellt keine Anforderungen an die Struktur konformer Implementierungen. Insbesondere müssen sie die Struktur der abstrakten Maschine nicht kopieren oder emulieren. Vielmehr sind konforme Implementierungen erforderlich, um (nur) das beobachtbare Verhalten der abstrakten Maschine zu emulieren, wie nachstehend erläutert.
2) Wie eine Behauptung ist bestanden oder nicht bestanden.