Warum bricht der erweiterte GCC 6-Optimierer praktischen C ++ - Code?

148

GCC 6 verfügt über eine neue Optimierungsfunktion : Es wird davon ausgegangen, dass diese thisimmer nicht null ist, und darauf basierend optimiert.

Die Weitergabe des Wertebereichs setzt nun voraus, dass der Zeiger this der C ++ - Mitgliedsfunktionen nicht null ist. Dies eliminiert häufige Nullzeigerprüfungen, unterbricht jedoch auch einige nicht konforme Codebasen (wie Qt-5, Chromium, KDevelop) . Als vorübergehende Problemumgehung können -fno-delete-null-Zeigerprüfungen verwendet werden. Falscher Code kann mit -fsanitize = undefined identifiziert werden.

Das Änderungsdokument weist dies eindeutig als gefährlich aus, da es eine überraschende Menge häufig verwendeten Codes zerstört.

Warum sollte diese neue Annahme den praktischen C ++ - Code brechen? Gibt es bestimmte Muster, bei denen unachtsame oder nicht informierte Programmierer auf dieses bestimmte undefinierte Verhalten angewiesen sind? Ich kann mir nicht vorstellen, if (this == NULL)dass jemand schreibt, weil das so unnatürlich ist.

boot4life
quelle
21
@ Ben Hoffentlich meinst du es gut. Code mit UB sollte neu geschrieben werden, um UB nicht aufzurufen. So einfach ist das. Heck, es gibt oft FAQs, die Ihnen sagen, wie Sie es erreichen können. Also meiner Meinung nach kein wirkliches Problem. Alles gut.
Stellen Sie Monica am
49
Ich bin erstaunt zu sehen, wie Leute Nullzeiger im Code dereferenzieren. Einfach unglaublich.
SergeyA
19
@Ben, das Erklären von undefiniertem Verhalten war lange Zeit die sehr effektive Optimierungstaktik. Ich liebe es, weil ich Optimierungen liebe, die meinen Code schneller laufen lassen.
SergeyA
17
Ich stimme SergeyA zu. Das ganze brouhaha begann, weil die Leute sich anscheinend mit der Tatsache befassen, dass thises als impliziter Parameter übergeben wird, und sie beginnen dann, es so zu verwenden, als wäre es ein expliziter Parameter. Es ist nicht. Wenn Sie eine Null dereferenzieren, rufen Sie UB so auf, als hätten Sie einen anderen Nullzeiger dereferenziert. Das ist alles was dazu gehört. Wenn Sie nullptrs weitergeben möchten, verwenden Sie den expliziten Parameter DUH . Es wird nicht langsamer, es wird nicht klobiger sein, und der Code, der eine solche API hat, ist sowieso tief in den Interna enthalten, hat also einen sehr begrenzten Umfang. Ende der Geschichte denke ich.
Stellen Sie Monica am
41
Ein großes Lob an GCC für die Unterbrechung des Zyklus von fehlerhaftem Code -> ineffizienter Compiler zur Unterstützung von fehlerhaftem Code -> fehlerhafterer Code -> ineffizientere Kompilierung -> ...
MM

Antworten:

87

Ich denke, die Frage, die beantwortet werden muss, warum gut gemeinte Leute die Schecks überhaupt ausstellen würden.

Der häufigste Fall ist wahrscheinlich, wenn Sie eine Klasse haben, die Teil eines natürlich vorkommenden rekursiven Aufrufs ist.

Wenn du hättest:

struct Node
{
    Node* left;
    Node* right;
};

in C könnten Sie schreiben:

void traverse_in_order(Node* n) {
    if(!n) return;
    traverse_in_order(n->left);
    process(n);
    traverse_in_order(n->right);
}

In C ++ ist es schön, dies zu einer Mitgliedsfunktion zu machen:

void Node::traverse_in_order() {
    // <--- What check should be put here?
    left->traverse_in_order();
    process();
    right->traverse_in_order();
}

In den frühen Tagen von C ++ (vor der Standardisierung) wurde betont, dass Mitgliedsfunktionen syntaktischer Zucker für eine Funktion sind, bei der der thisParameter implizit ist. Code wurde in C ++ geschrieben, in äquivalentes C konvertiert und kompiliert. Es gab sogar explizite Beispiele dafür, dass der Vergleich thismit null sinnvoll war, und der ursprüngliche Cfront-Compiler nutzte dies ebenfalls aus. Ausgehend von einem C-Hintergrund ist die offensichtliche Wahl für die Prüfung:

if(this == nullptr) return;      

Hinweis: Bjarne Stroustrup erwähnt sogar, dass sich die Regeln für thishier im Laufe der Jahre geändert haben

Und das hat viele Jahre bei vielen Compilern funktioniert. Als die Standardisierung stattfand, änderte sich dies. In jüngerer Zeit nutzten Compiler den Vorteil, eine Member-Funktion aufzurufen, bei der thises sich nullptrum ein undefiniertes Verhalten handelt. Dies bedeutet, dass diese Bedingung immer falsebesteht und der Compiler sie weglassen kann.

Das bedeutet, dass Sie zum Durchlaufen dieses Baums entweder:

  • Führen Sie alle Überprüfungen durch, bevor Sie anrufen traverse_in_order

    void Node::traverse_in_order() {
        if(left) left->traverse_in_order();
        process();
        if(right) right->traverse_in_order();
    }

    Dies bedeutet auch, an JEDER Anrufstelle zu prüfen, ob Sie einen Nullstamm haben könnten.

  • Verwenden Sie keine Mitgliedsfunktion

    Dies bedeutet, dass Sie den alten Code im C-Stil (möglicherweise als statische Methode) schreiben und ihn mit dem Objekt explizit als Parameter aufrufen. z.B. Sie schreiben wieder Node::traverse_in_order(node);und nicht node->traverse_in_order();an der Anrufstelle.

  • Ich glaube, der einfachste / sauberste Weg, dieses spezielle Beispiel auf eine Weise zu reparieren, die Standards entspricht, besteht darin, tatsächlich einen Sentinel-Knoten anstelle eines zu verwenden nullptr.

    // static class, or global variable
    Node sentinel;
    
    void Node::traverse_in_order() {
        if(this == &sentinel) return;
        ...
    }

Keine der ersten beiden Optionen scheint so ansprechend zu sein, und obwohl Code damit durchkommen könnte, haben sie schlechten Code mit geschrieben, this == nullptranstatt eine richtige Korrektur zu verwenden.

Ich vermute, so haben sich einige dieser Codebasen entwickelt, um sie zu this == nullptrüberprüfen.

jtlim
quelle
6
Wie kann 1 == 0undefiniertes Verhalten sein? Es ist einfach false.
Johannes Schaub - litb
11
Die Prüfung selbst ist kein undefiniertes Verhalten. Es ist einfach immer falsch und wird daher vom Compiler eliminiert.
SergeyA
15
Hmm .. this == nullptridiom ist ein undefiniertes Verhalten, da Sie zuvor eine Member-Funktion für ein nullptr-Objekt aufgerufen haben, die undefiniert ist. Und der Compiler kann die Prüfung weglassen
jtlim
6
@Joshua, der erste Standard wurde 1998 veröffentlicht. Was vorher geschah, war das, was jede Implementierung wollte. Finsteres Mittelalter.
SergeyA
26
Heh, wow, ich kann nicht glauben, dass jemals jemand Code geschrieben hat, der darauf beruhte, Instanzfunktionen aufzurufen ... ohne eine Instanz . Ich hätte instinktiv den Auszug "Alle Überprüfungen durchführen, bevor ich traverse_in_order thisaufrufe " verwendet, ohne überhaupt daran zu denken, jemals nullbar zu sein. Ich denke, vielleicht ist dies der Vorteil des Lernens von C ++ in einer Zeit, in der SO existiert, um die Gefahren von UB in meinem Gehirn zu verankern und mich davon abzubringen, bizarre Hacks wie diesen zu machen.
underscore_d
65

Dies liegt daran, dass der "praktische" Code fehlerhaft war und zunächst undefiniertes Verhalten beinhaltete. Es gibt keinen Grund, eine Null zu verwenden this, außer als Mikrooptimierung, normalerweise eine sehr verfrühte.

Dies ist eine gefährliche Vorgehensweise, da die Anpassung von Zeigern aufgrund des Durchlaufs der Klassenhierarchie eine Null thisin eine Nicht-Null-Null verwandeln kann . Zumindest muss die Klasse, deren Methoden mit einer Null arbeiten thissollen, eine endgültige Klasse ohne Basisklasse sein: Sie kann von nichts abgeleitet werden und sie kann nicht von abgeleitet werden. Wir gehen schnell vom praktischen zum hässlichen Hack-Land über .

In der Praxis muss der Code nicht hässlich sein:

struct Node
{
  Node* left;
  Node* right;
  void process();
  void traverse_in_order() {
    traverse_in_order_impl(this);
  }
private:
  static void traverse_in_order_impl(Node * n)
    if (!n) return;
    traverse_in_order_impl(n->left);
    n->process();
    traverse_in_order_impl(n->right);
  }
};

Wenn Sie einen leeren Baum hatten (z. B. root ist nullptr), stützt sich diese Lösung immer noch auf undefiniertes Verhalten, indem Sie traverse_in_order mit einem nullptr aufrufen.

Wenn der Baum leer ist, auch bekannt als Null Node* root, sollten Sie keine nicht statischen Methoden dafür aufrufen. Zeitraum. Es ist vollkommen in Ordnung, C-ähnlichen Baumcode zu haben, der einen Instanzzeiger durch einen expliziten Parameter nimmt.

Das Argument hier scheint darauf hinauszulaufen, dass nicht statische Methoden für Objekte geschrieben werden müssen, die von einem Nullinstanzzeiger aufgerufen werden könnten. Es gibt keine solche Notwendigkeit. Die Art und Weise, wie C-with-Objects solchen Code schreiben, ist in der C ++ - Welt noch viel besser, da sie zumindest typsicher sein kann. Grundsätzlich ist die Null thiseine solche Mikrooptimierung mit einem so engen Anwendungsbereich, dass es meiner Meinung nach vollkommen in Ordnung ist, sie nicht zuzulassen. Keine öffentliche API sollte von einer Null abhängen this.

Stellen Sie Monica wieder her
quelle
18
@ Ben, wer auch immer diesen Code geschrieben hat, war an erster Stelle falsch. Es ist lustig, dass Sie so schrecklich kaputte Projekte wie MFC, Qt und Chromium benennen. Gute Befreiung von ihnen.
SergeyA
19
@ Ben, schreckliche Codierungsstile in Google sind mir bekannt. Google-Code (zumindest öffentlich verfügbar) ist oft schlecht geschrieben, obwohl mehrere Leute glauben, dass Google-Code das leuchtende Beispiel ist. Möglicherweise werden sie dadurch dazu gebracht, ihre Codierungsstile (und Richtlinien, während sie darauf sind) zu überdenken.
SergeyA
18
@Ben Niemand ersetzt Chromium auf diesen Geräten rückwirkend durch Chromium, das mit gcc 6 kompiliert wurde. Bevor Chromium mit gcc 6 und anderen modernen Compilern kompiliert wird, muss es repariert werden. Es ist auch keine große Aufgabe; Die thisÜberprüfungen werden von verschiedenen statischen Code-Analysatoren ausgewählt, sodass es nicht so ist, als müsste jemand sie alle manuell suchen. Der Patch würde wahrscheinlich ein paar hundert Zeilen mit trivialen Änderungen enthalten.
Stellen Sie Monica am
8
@Ben In der Praxis ist eine Null- thisDereferenzierung ein sofortiger Absturz. Diese Probleme werden sehr schnell erkannt, selbst wenn niemand einen statischen Analysator über den Code ausführen möchte. C / C ++ folgt dem Mantra "Nur für Funktionen bezahlen, die Sie verwenden". Wenn Sie Überprüfungen wünschen, müssen Sie diese explizit angeben. Dies bedeutet, dass Sie sie nicht ausführen müssen this, wenn es zu spät ist, da der Compiler davon ausgeht, dass sie thisnicht null sind. Andernfalls müsste es überprüft werden this, und für 99,9999% des Codes da draußen sind solche Überprüfungen Zeitverschwendung.
Stellen Sie Monica am
10
Mein Rat für alle, die denken, dass der Standard gebrochen ist: Verwenden Sie eine andere Sprache. Es gibt keinen Mangel an C ++ - ähnlichen Sprachen, die nicht die Möglichkeit eines undefinierten Verhaltens haben.
MM
35

Das Änderungsdokument weist dies eindeutig als gefährlich aus, da es eine überraschende Menge häufig verwendeten Codes zerstört.

Das Dokument nennt es nicht gefährlich. Es wird auch nicht behauptet, dass es eine überraschende Menge an Code zerstört . Es werden lediglich einige beliebte Codebasen aufgezeigt, von denen behauptet wird, dass sie auf diesem undefinierten Verhalten beruhen und aufgrund der Änderung nicht funktionieren würden, wenn nicht die Problemumgehungsoption verwendet wird.

Warum sollte diese neue Annahme den praktischen C ++ - Code brechen?

Wenn praktischer C ++ - Code auf undefiniertem Verhalten beruht, können Änderungen an diesem undefinierten Verhalten das Verhalten beeinträchtigen. Aus diesem Grund ist UB zu vermeiden, auch wenn ein darauf basierendes Programm wie beabsichtigt zu funktionieren scheint.

Gibt es bestimmte Muster, bei denen sich nachlässige oder nicht informierte Programmierer auf dieses bestimmte undefinierte Verhalten verlassen?

Ich weiß nicht, ob es ein weit verbreitetes Anti- Muster ist, aber ein nicht informierter Programmierer könnte denken, dass er sein Programm vor Abstürzen schützen kann, indem er Folgendes tut:

if (this)
    member_variable = 42;

Wenn der eigentliche Fehler darin besteht, einen Nullzeiger an einer anderen Stelle zu dereferenzieren.

Ich bin sicher, wenn der Programmierer nicht ausreichend informiert ist, kann er fortgeschrittenere (Anti) Muster entwickeln, die auf dieser UB basieren.

Ich kann mir nicht vorstellen, if (this == NULL)dass jemand schreibt, weil das so unnatürlich ist.

Ich kann.

Eerorika
quelle
11
"Wenn praktischer C ++ - Code auf undefiniertem Verhalten beruht, können Änderungen an diesem undefinierten Verhalten dazu führen, dass es beschädigt wird. Deshalb ist UB zu vermeiden." This * 1000
underscore_d
if(this == null) PrintSomeHelpfulDebugInformationAboutHowWeGotHere(); Zum Beispiel ein schönes, einfach zu lesendes Protokoll einer Abfolge von Ereignissen, von denen ein Debugger Ihnen nicht leicht erzählen kann. Viel Spaß beim Debuggen, ohne stundenlang überall Schecks zu platzieren, wenn in einem großen Datensatz plötzlich eine zufällige Null in Code vorliegt, den Sie nicht geschrieben haben ... Und die UB-Regel dazu wurde später erstellt, nachdem C ++ erstellt wurde. Es war früher gültig.
Stephane Hockenhull
@StephaneHockenhull Dafür -fsanitize=nullist.
Eerorika
@ user2079303 Probleme: Verlangsamt dies den Produktionscode bis zu dem Punkt, an dem Sie den Check-in während des Betriebs nicht mehr verlassen können, was das Unternehmen viel Geld kostet? Wird das größer und passt nicht in Flash? Funktioniert das auf allen Zielplattformen einschließlich Atmel? Können -fsanitize=nulldie Fehler mithilfe von SPI auf der SD / MMC-Karte an den Pins 5, 6, 10, 11 protokolliert werden? Das ist keine universelle Lösung. Einige haben argumentiert, dass es gegen objektorientierte Prinzipien verstößt, auf ein Nullobjekt zuzugreifen, aber einige OOP-Sprachen haben ein Nullobjekt, das bearbeitet werden kann, so dass es keine universelle Regel von OOP ist. 1/2
Stephane Hockenhull
1
... ein regulärer Ausdruck, der mit solchen Dateien übereinstimmt? Wenn beispielsweise gesagt wird, dass auf einen l-Wert zweimal zugegriffen wird, kann ein Compiler die Zugriffe konsolidieren, es sei denn, der Code zwischen ihnen führt eine von mehreren spezifischen Aufgaben aus, wäre viel einfacher, als zu versuchen, die genauen Situationen zu definieren, in denen Code auf den Speicher zugreifen darf.
Supercat
25

Einige der "praktischen" (lustige Art, "Buggy" zu buchstabieren) Codes, die kaputt waren, sahen folgendermaßen aus:

void foo(X* p) {
  p->bar()->baz();
}

und es wurde vergessen, die Tatsache zu berücksichtigen, dass p->bar()manchmal ein Nullzeiger zurückgegeben wird, was bedeutet, dass die Dereferenzierung zum Aufruf baz()undefiniert ist.

Nicht der gesamte fehlerhafte Code enthielt explizite if (this == nullptr)oder if (!p) return;Überprüfungen. Einige Fälle waren einfach Funktionen, die nicht auf Mitgliedsvariablen zugegriffen haben und daher in Ordnung zu sein schienen . Beispielsweise:

struct DummyImpl {
  bool valid() const { return false; }
  int m_data;
};
struct RealImpl {
  bool valid() const { return m_valid; }
  bool m_valid;
  int m_data;
};

template<typename T>
void do_something_else(T* p) {
  if (p) {
    use(p->m_data);
  }
}

template<typename T>
void func(T* p) {
  if (p->valid())
    do_something(p);
  else 
    do_something_else(p);
}

In diesem Code gibt es beim Aufrufen func<DummyImpl*>(DummyImpl*)mit einem Nullzeiger eine "konzeptionelle" Dereferenzierung des aufzurufenden Zeigers p->DummyImpl::valid(), aber tatsächlich kehrt die falseElementfunktion nur ohne Zugriff zurück *this. Das return falsekann inline sein und so muss in der Praxis überhaupt nicht auf den Zeiger zugegriffen werden. Bei einigen Compilern scheint es also in Ordnung zu sein: Es gibt keinen Segfault für die Dereferenzierung von Null, p->valid()ist falsch, daher ruft der Code auf do_something_else(p), der nach Nullzeigern sucht, und tut nichts. Es wird kein Absturz oder unerwartetes Verhalten beobachtet.

Mit GCC 6 erhalten Sie immer noch den Aufruf von p->valid(), aber der Compiler leitet jetzt aus diesem Ausdruck ab, pder nicht null sein darf (andernfalls p->valid()wäre dies ein undefiniertes Verhalten), und notiert diese Informationen. Diese abgeleiteten Informationen werden vom Optimierer verwendet, sodass do_something_else(p)die if (p)Prüfung jetzt als redundant betrachtet wird , wenn der Aufruf von inline ausgeführt wird , da der Compiler sich daran erinnert, dass sie nicht null ist, und den Code daher wie folgt einfügt :

template<typename T>
void func(T* p) {
  if (p->valid())
    do_something(p);
  else {
    // inlined body of do_something_else(p) with value propagation
    // optimization performed to remove null check.
    use(p->m_data);
  }
}

Dies dereferenziert jetzt wirklich einen Nullzeiger, und so funktioniert Code, der zuvor zu funktionieren schien, nicht mehr.

In diesem Beispiel befindet sich der Fehler func, der zuerst auf null hätte prüfen sollen (oder die Anrufer hätten ihn niemals mit null aufrufen sollen):

template<typename T>
void func(T* p) {
  if (p && p->valid())
    do_something(p);
  else 
    do_something_else(p);
}

Ein wichtiger Punkt, an den Sie sich erinnern sollten, ist, dass die meisten Optimierungen wie diese nicht vom Compiler stammen, der sagt: "Ah, der Programmierer hat diesen Zeiger gegen Null getestet, ich werde ihn entfernen, nur um ärgerlich zu sein." Was passiert, ist, dass verschiedene Standardoptimierungen wie Inlining und Wertebereichsausbreitung zusammen diese Überprüfungen überflüssig machen, da sie nach einer früheren Überprüfung oder einer Dereferenzierung erfolgen. Wenn der Compiler weiß, dass ein Zeiger an Punkt A in einer Funktion nicht null ist und der Zeiger nicht vor einem späteren Punkt B in derselben Funktion geändert wird, weiß er, dass er auch an B nicht null ist. Wenn Inlining auftritt Die Punkte A und B können tatsächlich Codeteile sein, die sich ursprünglich in separaten Funktionen befanden, jetzt aber zu einem Codeteil zusammengefasst sind, und der Compiler kann sein Wissen anwenden, dass der Zeiger an mehreren Stellen nicht null ist.

Jonathan Wakely
quelle
Ist es möglich, GCC 6 so zu instrumentieren, dass Warnungen zur Kompilierungszeit ausgegeben werden, wenn solche Verwendungen von auftreten this?
Jotik
3
@jotik, ^^^ was TC gesagt hat. Es wäre möglich, aber Sie würden diese Warnung FÜR ALLEN CODE, DIE GANZE ZEIT erhalten . Die Weitergabe des Wertebereichs ist eine der häufigsten Optimierungen, die fast den gesamten Code überall betrifft. Die Optimierer sehen nur Code, der vereinfacht werden kann. Sie sehen nicht "einen Code, der von einem Idioten geschrieben wurde, der gewarnt werden möchte, wenn ihr dummer UB weg optimiert wird". Für den Compiler ist es nicht einfach, den Unterschied zwischen "redundante Überprüfung, dass der Programmierer optimiert werden möchte" und "redundante Überprüfung, von der der Programmierer glaubt, dass sie hilft, aber redundant ist" zu erkennen.
Jonathan Wakely
1
Wenn Sie Ihren Code instrumentieren möchten, um Laufzeitfehler für verschiedene Arten von UB zu geben, einschließlich ungültiger Verwendungen von this, dann verwenden Sie einfach-fsanitize=undefined
Jonathan Wakely
-25

Der C ++ - Standard ist in wichtigen Punkten gebrochen. Anstatt die Benutzer vor diesen Problemen zu schützen, haben sich die GCC-Entwickler leider dafür entschieden, undefiniertes Verhalten als Ausrede für die Implementierung geringfügiger Optimierungen zu verwenden, selbst wenn ihnen klar erklärt wurde, wie schädlich es ist.

Hier eine viel klügere Person, als ich ausführlich erläutere. (Er spricht über C, aber dort ist die Situation dieselbe).

Warum ist es schädlich?

Durch einfaches Neukompilieren von zuvor funktionierendem, sicherem Code mit einer neueren Version des Compilers können Sicherheitslücken entstehen . Während das neue Verhalten mit einem Flag deaktiviert werden kann, ist bei vorhandenen Makefiles dieses Flag offensichtlich nicht gesetzt. Und da keine Warnung ausgegeben wird, ist es für den Entwickler nicht offensichtlich, dass sich das zuvor vernünftige Verhalten geändert hat.

In diesem Beispiel hat der Entwickler eine Überprüfung auf Ganzzahlüberlauf mit eingefügt assert, die das Programm beendet, wenn eine ungültige Länge angegeben wird. Das GCC-Team hat die Prüfung auf der Grundlage entfernt, dass der Ganzzahlüberlauf undefiniert ist. Daher kann die Prüfung entfernt werden. Dies führte dazu, dass echte In-the-Wild-Instanzen dieser Codebasis nach Behebung des Problems wieder anfällig gemacht wurden.

Lesen Sie das Ganze. Es ist genug, um dich zum Weinen zu bringen.

OK, aber was ist mit diesem?

Vor langer Zeit gab es eine ziemlich verbreitete Redewendung, die ungefähr so ​​lautete:

 OPAQUEHANDLE ObjectType::GetHandle(){
    if(this==NULL)return DEFAULTHANDLE;
    return mHandle;

 }

 void DoThing(ObjectType* pObj){
     osfunction(pObj->GetHandle(), "BLAH");
 }

Die Redewendung lautet also: Wenn pObjnicht null, verwenden Sie das darin enthaltene Handle, andernfalls verwenden Sie ein Standardhandle. Dies ist in der GetHandleFunktion gekapselt .

Der Trick besteht darin, dass beim Aufrufen einer nicht virtuellen Funktion der thisZeiger nicht verwendet wird, sodass keine Zugriffsverletzung vorliegt.

Ich verstehe es immer noch nicht

Es gibt viel Code, der so geschrieben ist. Wenn jemand es einfach neu kompiliert, ohne eine Zeile zu ändern, ist jeder Anruf bei DoThing(NULL)ein Absturzfehler - wenn Sie Glück haben.

Wenn Sie kein Glück haben, werden Aufrufe von abstürzenden Fehlern zu Sicherheitslücken bei der Remote-Ausführung.

Dies kann sogar automatisch erfolgen. Sie haben ein automatisiertes Build-System, oder? Ein Upgrade auf den neuesten Compiler ist harmlos, oder? Aber jetzt ist es nicht - nicht, wenn Ihr Compiler GCC ist.

OK, sag es ihnen!

Ihnen wurde gesagt. Sie tun dies in voller Kenntnis der Konsequenzen.

aber wieso?

Wer kann das schon sagen? Vielleicht:

  • Sie schätzen die ideale Reinheit der C ++ - Sprache gegenüber dem tatsächlichen Code
  • Sie glauben, dass Menschen dafür bestraft werden sollten, dass sie sich nicht an den Standard halten
  • Sie haben kein Verständnis für die Realität der Welt
  • Sie ... führen absichtlich Fehler ein. Vielleicht für eine ausländische Regierung. Wo wohnst du? Alle Regierungen sind dem größten Teil der Welt fremd und die meisten sind einem Teil der Welt feindlich gesinnt.

Oder vielleicht etwas anderes. Wer kann das schon sagen?

Ben
quelle
32
Nicht einverstanden mit jeder einzelnen Zeile der Antwort. Dieselben Kommentare wurden für strikte Aliasing-Optimierungen abgegeben, und diese werden hoffentlich jetzt verworfen. Die Lösung besteht darin, Entwickler zu schulen und Optimierungen aufgrund schlechter Entwicklungsgewohnheiten nicht zu verhindern.
SergeyA
30
Ich habe das Ganze gelesen, wie du gesagt hast, und tatsächlich habe ich geweint, aber hauptsächlich über die Dummheit von Felix, von der ich nicht glaube, dass du versucht hast, sie zu vermitteln ...
Mike Vine
33
Für die nutzlose Schimpfe herabgestimmt. "Sie ... führen absichtlich Fehler ein. Vielleicht für eine ausländische Regierung." "Ja wirklich?" Dies ist keine / r / Verschwörung.
Isanae
31
Anständige Programmierer, die das Mantra immer wieder wiederholen, rufen kein undefiniertes Verhalten hervor , aber diese Nonks haben es trotzdem getan. Und schau was passiert ist. Ich habe überhaupt kein Mitgefühl. Dies ist die Schuld der Entwickler, so einfach ist das. Sie müssen Verantwortung übernehmen. Erinnere dich daran? Persönliche Verantwortung? Leute, die sich auf Ihr Mantra verlassen "aber was ist mit in der Praxis !" Genau so ist diese Situation überhaupt entstanden. Unsinn wie diesen zu vermeiden, ist genau der Grund, warum Standards überhaupt existieren. Code nach Standards, und Sie werden kein Problem haben. Zeitraum.
Leichtigkeitsrennen im Orbit
18
"Durch einfaches Neukompilieren von zuvor funktionierendem, sicherem Code mit einer neueren Version des Compilers können Sicherheitslücken entstehen" - das passiert immer . Es sei denn, Sie möchten festlegen, dass eine Version eines Compilers der einzige Compiler ist, der für den Rest der Ewigkeit zulässig ist. Erinnerst du dich, als der Linux-Kernel nur mit genau gcc 2.7.2.1 kompiliert werden konnte? Das gcc-Projekt wurde sogar gespalten, weil die Leute genug von Bullcrap hatten. Es hat lange gedauert, bis wir darüber hinweg waren.
MM