Wann hat Undefined Behavior in C die Kausalitätsbarriere überschritten?

8

Einige hypermoderne C-Compiler schließen daraus, dass solche Eingaben niemals empfangen werden, wenn ein Programm bei bestimmten Eingaben Undefiniertes Verhalten aufruft. Folglich kann jeder Code, der irrelevant wäre, wenn solche Eingaben nicht empfangen würden, eliminiert werden.

Als einfaches Beispiel gegeben:

void foo(uint32_t);

uint32_t rotateleft(uint_t value, uint32_t amount)
{
  return (value << amount) | (value >> (32-amount));
}

uint32_t blah(uint32_t x, uint32_t y)
{
  if (y != 0) foo(y);
  return rotateleft(x,y);
}

Ein Compiler kann daraus schließen, dass die Funktion niemals mit Null aufgerufen wird , da die Auswertung von value >> (32-amount)undefiniertes Verhalten amountergibt, wenn Null ist. Der Aufruf an kann somit bedingungslos gemacht werden.blahyfoo

Soweit ich das beurteilen kann, scheint sich diese Philosophie irgendwann im Jahr 2010 durchgesetzt zu haben. Die frühesten Beweise, die ich für ihre Wurzeln gesehen habe, stammen aus dem Jahr 2009 und sind im C11-Standard verankert, der ausdrücklich besagt, dass undefiniertes Verhalten überhaupt auftritt Punkt in der Ausführung eines Programms wird das Verhalten des gesamten Programms rückwirkend undefiniert.

War die Vorstellung, dass Compiler versuchen sollten, undefiniertes Verhalten zu verwenden, um umgekehrte kausale Optimierungen zu rechtfertigen (dh das undefinierte Verhalten in der rotateleftFunktion sollte den Compiler veranlassen, anzunehmen, blahdass es mit einer Nicht-Null aufgerufen worden sein muss y, unabhängig davon, ob irgendetwas jemals dazu führen würde oder nichty zu einen Wert ungleich Null halten), der vor 2009 ernsthaft befürwortet wurde? Wann wurde so etwas zum ersten Mal ernsthaft als Optimierungstechnik vorgeschlagen?

[Nachtrag]

Einige Compiler haben sogar im 20. Jahrhundert Optionen aufgenommen, um bestimmte Arten von Rückschlüssen auf Schleifen und die darin berechneten Werte zu ermöglichen. Zum Beispiel gegeben

int i; int total=0;
for (i=n; i>=0; i--)
{
  doSomething();
  total += i*1000;
}

Ein Compiler kann es auch ohne die optionalen Schlussfolgerungen wie folgt umschreiben:

int i; int total=0; int x1000;
for (i=n, x1000=n*1000; i>0; i--, x1000-=1000)
{
  doSomething();
  total += x1000;
}

da das Verhalten dieses Codes genau mit dem Original übereinstimmen würde, selbst wenn der Compiler spezifizierte, dass intWerte immer in mod-65536 Zwei-Komplement-Weise umbrochen werden . Die zusätzliche-Inferenz - Option lassen würde der Compiler erkennen , dass da iund x1000Null zugleich überqueren sollte, kann die ehemalige Variable eliminiert werden:

int total=0; int x1000;
for (x1000=n*1000; x1000 > 0; x1000-=1000)
{
  doSomething();
  total += x1000;
}

Auf einem System, auf dem intWerte in Mod 65536 eingeschlossen sind, würde ein Versuch, eine der ersten beiden Schleifen mit n33 auszuführen , dazu führen, doSomething()dass 33 Mal aufgerufen wird. Im Gegensatz dazu würde die letzte Schleife überhaupt nicht aufrufen doSomething(), obwohl der erste Aufruf von tatsächlich von Vorteil wäre. Darüber hinaus entschuldigte sich die Compiler-Dokumentation in der Regel dafür, dass sie das Verhalten von Programmen ändern würde - selbst von Programmen, die sich mit UB befassten.doSomething() einem arithmetischen Überlauf vorausgegangen wäre. Ein solches Verhalten könnte als "nicht kausal" angesehen werden, aber die Auswirkungen sind ziemlich gut eingeschränkt, und es gibt viele Fälle, in denen das Verhalten nachweislich harmlos wäre (in Fällen, in denen eine Funktion erforderlich ist, um einen Wert zu liefern, wenn eine Eingabe gegeben wird, aber der Wert kann beliebig sein, wenn die Eingabe ungültig ist, und die Schleife wird schneller beendet, wenn ein ungültiger Wert von angegeben wirdn

Ich bin daran interessiert, wann sich die Einstellungen von Compiler-Autoren von der Idee abwandten, dass Plattformen, wenn dies praktikabel ist, einige verwendbare Verhaltensbeschränkungen auch in Fällen dokumentieren sollten, die nicht vom Standard vorgeschrieben sind, zu der Idee, dass Konstrukte, die auf Verhaltensweisen beruhen, die nicht von der Norm vorgeschrieben sind Standard sollte als unzulässig eingestuft werden, selbst wenn er auf den meisten vorhandenen Compilern genauso gut oder besser funktioniert als jeder streng konforme Code, der dieselben Anforderungen erfüllt (häufig sind Optimierungen möglich, die bei streng konformem Code nicht möglich wären).

Superkatze
quelle
Kommentare sind nicht für eine ausführliche Diskussion gedacht. Dieses Gespräch wurde in den Chat verschoben .
maple_shaft
1
@rwong: Ich sehe nicht, wie das überhaupt gegen die Gesetze der Zeit verstößt, geschweige denn gegen die Kausalität. Wenn unzulässige statische Casts die Programmausführung beendeten (kaum ein obskures Ausführungsmodell) und das Build-Modell so war, dass nach dem Verknüpfen keine zusätzlichen Klassen definiert werden konnten, konnte keine Eingabe in das Programm dazu führen shape->Is2D(), dass ein Objekt aufgerufen wurde, das nicht abgeleitet wurde von Shape2D. Es gibt einen großen Unterschied zwischen der Optimierung von Code, der nur relevant wäre, wenn bereits ein kritisches undefiniertes Verhalten
aufgetreten
... gibt es keine nachgeschalteten Ausführungspfade, die keine Form von undefiniertem Verhalten aufrufen. Während die Definition von "kritischem undefiniertem Verhalten" in (IIRC) Anhang L etwas vage ist, könnte eine Implementierung statisches Casting und virtuellen Versand vernünftigerweise so implementieren, dass der angegebene Code zu einer beliebigen Adresse springen würde (es ist kritisches UB). Es Shape2D::Is­2Dist eigentlich besser , immer zu springen, als das Programm es verdient.
Supercat
1
@rwong: Was das Duplikat betrifft, war die Absicht meiner Frage zu fragen, wann in der Geschichte der Menschheit die normative Interpretation von undefiniertem Verhalten in Bezug auf z. B. Überlauf von "Wenn das natürliche Verhalten einer Plattform es einer Anwendung ermöglichen würde, ihre Anforderungen ohne Fehlerprüfung zu erfüllen" verschoben wurde Wenn das Standardmandat ein gegenteiliges Verhalten aufweist, kann dies die Komplexität des Quellcodes, des ausführbaren Codes und des Compilercodes erhöhen, die erforderlich sind, damit ein Programm die Anforderungen erfüllt. Daher sollte der Standard die Plattform stattdessen das tun lassen, was Compiler am besten annehmen sollten dass Programmierer ...
Supercat
1
... enthält immer Code, um Überläufe in allen Fällen zu verhindern, in denen keine Nuklearraketen abgefeuert werden sollen. "Historisch gesehen, angesichts der Spezifikation", schreiben Sie eine Funktion, die bei zwei Argumenten vom Typ 'int' das Produkt zurückgibt, wenn dies der Fall ist darstellbar als 'int' oder entweder das Programm zu beenden oder eine beliebige Zahl zurückzugeben ", int prod(int x, int y) {return x*y;}hätte ausgereicht. Die strikte Einhaltung von" Nukes nicht starten "würde jedoch Code erfordern, der schwerer zu lesen ist und fast sicherlich viel langsamer auf vielen Plattformen laufen.
Supercat

Antworten:

4

Undefiniertes Verhalten wird in Situationen verwendet, in denen es für die Spezifikation nicht möglich ist, das Verhalten anzugeben, und es wurde immer geschrieben, um absolut jedes mögliche Verhalten zuzulassen.

Die extrem lockeren Regeln für UB sind hilfreich, wenn Sie darüber nachdenken, was ein spezifikationskonformer Compiler durchmachen muss. Möglicherweise verfügen Sie über genügend Kompilierungsleistung, um einen Fehler auszugeben, wenn Sie in einem Fall eine schlechte UB ausführen. Fügen Sie jedoch einige Rekursionsebenen hinzu, und das Beste, was Sie jetzt tun können, ist eine Warnung. Die Spezifikation hat kein Konzept von "Warnungen". Wenn die Spezifikation also ein Verhalten angegeben hätte, müsste es "ein Fehler" sein.

Der Grund, warum wir immer mehr Nebenwirkungen sehen, ist der Drang zur Optimierung. Das Schreiben eines spezifikationskonformen Optimierers ist schwierig. Es ist brutal, einen spezifikationskonformen Optimierer zu schreiben, der auch bemerkenswert gute Arbeit leistet, um zu erraten, was Sie beabsichtigt haben, als Sie außerhalb der Spezifikation waren. Für die Compiler ist es viel einfacher, wenn sie annehmen, dass UB UB bedeutet.

Dies gilt insbesondere für gcc, das versucht, viele, viele Befehlssätze mit demselben Compiler zu unterstützen. Es ist weitaus einfacher, UB UB-Verhalten hervorbringen zu lassen, als zu versuchen, sich mit allen Möglichkeiten auseinanderzusetzen, mit denen jeder einzelne UB-Code auf jeder Plattform schief gehen kann, und dies in die frühen Sätze des Optimierers einzubeziehen.

Cort Ammon
quelle
1
Der Grund, warum C für seine Geschwindigkeit bekannt wurde, ist, dass Programmierer, die wussten, dass selbst in einigen Fällen, in denen der Standard keine Anforderungen auferlegte, das Verhalten ihrer Plattform ihren Anforderungen entsprechen würde, ein solches Verhalten nutzen könnten. Wenn eine Plattform garantiert, dass x-y > zwillkürlich 0 oder 1 x-yergibt, wenn dies nicht als "int" dargestellt werden kann, bietet eine solche Plattform mehr Optimierungsmöglichkeiten als eine Plattform, bei der der Ausdruck entweder UINT_MAX/2+1+x+y > UINT_MAX/2+1+zoder geschrieben werden muss (long long)x+y > z.
Supercat
1
Vor ungefähr 2009 wäre eine typische Interpretation vieler Formen von UB z. B. "Selbst wenn die Ausführung eines Befehls" Verschiebung nach rechts um N "auf der Ziel-CPU den Bus für einige Minuten blockieren könnte, wenn der Interrupt deaktiviert ist, wenn N -1 ist [I. Ich habe von einer solchen CPU gehört.] Ein Compiler kann jedes N ohne Validierung an den Befehl "Verschiebung von rechts nach N" übergeben oder N mit 31 maskieren oder willkürlich unter diesen Optionen auswählen. " In Fällen, in denen eines der oben genannten Verhaltensweisen die Anforderungen erfüllen würde, mussten Programmierer keine Zeit (ihre oder die der Maschine) verschwenden, um deren Auftreten zu verhindern. Was hat die Änderung veranlasst?
Supercat
@supercat Ich würde sagen, dass eine "typische" Interpretation von UB auch 2015 ähnlich harmlos wäre. Das Problem ist nicht der typische Fall von UB, sondern die ungewöhnlichen Fälle. Ich würde sagen, 95-98% von UB, die ich versehentlich geschrieben habe, haben "wie beabsichtigt gearbeitet". Es sind die verbleibenden 2-5%, bei denen Sie nicht verstehen können, warum die UB so außer Kontrolle geraten ist, bis Sie 3 Jahre lang tief in den Darm des Optimierers von gcc gegraben haben, um zu erkennen, dass es tatsächlich genauso ausgefallen war, wie sie dachten ... es sah zunächst einfach aus.
Cort Ammon
Ein hervorragendes Beispiel, auf das ich gestoßen bin, war eine Verletzung der One Definition Rule (ODR), die ich versehentlich geschrieben habe, bei der zwei Klassen gleich benannt wurden und bei der ich eine Funktion aufgerufen habe und die andere ausgeführt wurde. Es erscheint vernünftig zu sagen, "warum Sie nicht einfach den nächsten Klassennamen verwendet oder mich zumindest gewarnt haben", bis Sie verstehen, wie der Linker Namensprobleme löst. Es gab einfach keine "gute" Verhaltensoption, es sei denn, sie haben riesige Mengen zusätzlichen Codes in den Compiler eingefügt, um ihn abzufangen.
Cort Ammon
Die Linker-Technologie ist im Allgemeinen schrecklich, aber es gibt erhebliche Henne-Ei-Hindernisse für ihre Verbesserung. Ansonsten ist es meiner Meinung nach erforderlich, dass C eine Reihe von Typedefs und Makros für Portabilität / Optimierung so standardisiert, dass vorhandene Compiler, die das damit verbundene Verhalten unterstützen, den neuen Standard lediglich durch Hinzufügen einer Header-Datei und Code unterstützen können, der die neue verwenden möchte Funktionen könnten mit älteren Compilern verwendet werden, die das Verhalten lediglich durch
Einfügen
4

"Undefiniertes Verhalten kann dazu führen, dass der Compiler Code neu schreibt" ist in Schleifenoptimierungen seit langem aufgetreten.

Nehmen Sie eine Schleife (a und b sind zum Beispiel Zeiger auf double)

for (i = 0; i < n; ++i) a [i] = b [i];

Wir erhöhen ein int, kopieren ein Array-Element und vergleichen es mit einem Limit. Ein optimierender Compiler entfernt zuerst die Indizierung:

double* tmp1 = a;
double* tmp2 = b;
for (i = 0; i < n; ++i) *tmp1++ = *tmp2++;

Wir entfernen den Fall n <= 0:

i = 0;
if (n > 0) {
    double* tmp1 = a;
    double* tmp2 = b;
    for (; i < n; ++i) *tmp1++ = *tmp2++;
}

Jetzt eliminieren wir die Variable i:

i = 0;
if (n > 0) {
    double* tmp1 = a;
    double* tmp2 = b;
    double* limit = tmp1 + n;
    for (; tmp1 != limit; tmp1++, tmp2++) *tmp1 = *tmp2;
    i = n;
}

Wenn nun n = 2 ^ 29 auf einem 32-Bit-System oder 2 ^ 61 auf einem 64-Bit-System ist, haben wir bei typischen Implementierungen das Limit tmp1 == und es wird kein Code ausgeführt. Ersetzen Sie nun die Zuweisung durch etwas, das lange dauert, damit der ursprüngliche Code niemals in den unvermeidlichen Absturz gerät, weil er zu lange dauert und der Compiler den Code geändert hat.

gnasher729
quelle
Wenn keiner der Zeiger flüchtig ist, würde ich das nicht als Umschreiben des Codes betrachten. Compiler haben lange Zeit die Möglichkeit, Schreibvorgänge in Nicht- volatileZeiger neu zu nsequenzieren. Daher ist das Verhalten in dem Fall, in dem Zeiger umbrochen werden, gleichbedeutend damit, dass ein Speicher außerhalb der Grenzen einen temporären Speicherort ivor allem anderen enthält das passiert. Wenn aoder bwaren flüchtig, dokumentierte die Plattform, dass flüchtige Zugriffe physische Lade- / Speicheroperationen in der angeforderten Reihenfolge erzeugen, und die Plattform definiert alle Mittel, über die solche Anforderungen ...
Supercat
... könnte die Programmausführung deterministisch beeinflussen, dann würde ich argumentieren, dass ein Compiler von den angegebenen Optimierungen Abstand nehmen sollte (obwohl ich ohne weiteres anerkennen werde, dass einige möglicherweise nicht programmiert wurden, um solche Optimierungen zu verhindern, es isei denn, sie wurden ebenfalls flüchtig gemacht). Das wäre jedoch ein ziemlich seltener Fall von Verhaltensecken. Wenn aund bnicht flüchtig sind, würde ich vorschlagen, dass es keine plausible beabsichtigte Bedeutung für das gibt, was der Code tun soll, wenn er nso groß ist, dass der gesamte Speicher überschrieben wird. Im Gegensatz dazu haben viele andere Formen von UB plausible beabsichtigte Bedeutungen.
Supercat
Bei weiterer Betrachtung kann ich mir Situationen vorstellen, in denen Annahmen über die Gesetze der Ganzzahlarithmetik dazu führen können, dass Schleifen vorzeitig beendet werden, bevor eine UB auftritt, und ohne dass die a-Schleife unzulässige Adressberechnungen durchführt. In FORTRAN gibt es eine klare Unterscheidung zwischen Regelkreisvariablen und anderen Variablen, aber C fehlt diese Unterscheidung, was bedauerlich ist, da die Verpflichtung der Programmierer, immer einen Überlauf von Regelkreisvariablen zu verhindern, die effiziente Codierung weitaus weniger behindert als die Verhinderung eines Überlaufs irgendwo.
Supercat
Ich frage mich, wie Sprachregeln am besten geschrieben werden können, damit Code, der mit einer Reihe von Verhaltensweisen im Falle eines Überlaufs zufrieden ist, dies sagen kann, ohne wirklich nützliche Optimierungen zu behindern. Es gibt viele Fälle, in denen Code bei gültiger Eingabe eine streng definierte Ausgabe erzeugen muss, bei einer ungültigen Eingabe jedoch eine lose definierte Ausgabe. Nehmen wir zum Beispiel einen Programmierer an, der zum Schreiben neigt: if (x-y>z) do_something()`Es ist egal, ob er do_somethingim Falle eines Überlaufs ausgeführt wird, vorausgesetzt, der Überlauf hat keine andere Wirkung. Gibt es eine Möglichkeit, das oben
Gesagte
... auf zumindest einigen Plattformen unnötig ineffizienten Code generieren (im Vergleich zu dem, was diese Plattformen generieren könnten, wenn sie die freie Wahl hätten, ob sie aufgerufen werden sollen oder nicht do_something)? Selbst wenn es Schleifenoptimierungen verboten wäre, ein Verhalten zu erzielen, das nicht mit einem losen Überlaufmodell vereinbar ist, könnten Programmierer Code so schreiben, dass Compiler optimalen Code generieren können. Gibt es eine Möglichkeit, Ineffizienzen zu umgehen, die durch ein Modell "Überlauf um jeden Preis vermeiden" erzwungen werden?
Supercat
3

In C und C ++ war es immer so, dass aufgrund undefinierten Verhaltens alles passieren kann. Daher war es auch immer so, dass ein Compiler davon ausgehen kann, dass Ihr Code kein undefiniertes Verhalten aufruft: Entweder enthält Ihr Code kein undefiniertes Verhalten, dann war die Annahme richtig. Oder es gibt ein undefiniertes Verhalten in Ihrem Code. Was auch immer aufgrund der falschen Annahme passiert, wird von " irgendetwas " abgedeckt kann passieren" .

Wenn Sie sich die Funktion "Einschränken" in C ansehen, besteht der springende Punkt der Funktion darin, dass der Compiler davon ausgehen kann, dass es kein undefiniertes Verhalten gibt. Daher haben wir den Punkt erreicht, an dem der Compiler nicht nur kann, sondern sollte annehmen annehmen , dass es kein undefiniertes gibt Verhalten.

In dem von Ihnen angegebenen Beispiel werden die Assembler-Anweisungen, die normalerweise auf x86-basierten Computern zum Implementieren der Links- oder Rechtsverschiebung verwendet werden, um 0 Bit verschoben, wenn die Anzahl der Verschiebungen 32 für 32-Bit-Code oder 64 für 64-Bit-Code beträgt. Dies führt in den meisten praktischen Fällen zu unerwünschten Ergebnissen (und Ergebnissen, die nicht mit ARM oder PowerPC identisch sind), sodass der Compiler zu Recht davon ausgehen kann, dass ein solches undefiniertes Verhalten nicht auftritt. Sie können Ihren Code in ändern

uint32_t rotateleft(uint_t value, uint32_t amount)
{
   return amount == 0 ? value : (value << amount) | (value >> (32-amount));
}

und schlagen Sie den gcc- oder Clang-Entwicklern vor, dass auf den meisten Prozessoren der Code "amount == 0" vom Compiler entfernt werden sollte, da der für den Shift-Code generierte Assembler-Code das gleiche Ergebnis wie value liefert, wenn amount == 0 ist.

gnasher729
quelle
1
Ich kann mir nur sehr wenige Plattformen vorstellen, auf denen eine Implementierung von x>>y[für vorzeichenlose x], die funktionieren würde, wenn eine Variable yeinen Wert von 0 bis 31 enthält und etwas anderes als 0 oder x>>(y & 31)für andere Werte ausführt, so effizient sein könnte wie eine, die etwas anderes ausführt ;; Ich kenne keine Plattform, auf der die Garantie, dass keine andere als eine der oben genannten Maßnahmen ergriffen würde, erhebliche Kosten verursachen würde. Die Idee, dass Programmierer eine kompliziertere Formulierung in Code verwenden sollten, die niemals auf obskuren Maschinen ausgeführt werden müsste, wäre als absurd angesehen worden.
Supercat
3
Meine Frage ist, wann sich die Dinge von "x >> 32" willkürlich ergeben xoder 0auf einigen dunklen Plattformen abfangen "zu" x>>32könnte den Compiler veranlassen, die Bedeutung von anderem Code neu zu schreiben "verschoben haben. Die frühesten Beweise, die ich finden kann, stammen aus dem Jahr 2009, aber ich bin gespannt, ob frühere Beweise vorliegen.
Supercat
@Deduplicator: Mein Ersatz funktioniert einwandfrei für sinnvolle Werte, dh 0 <= Betrag <32. Und ob Ihr (Wert >> 32 - Betrag) korrekt ist oder nicht, ist mir egal, aber lassen Sie die Klammern weg ist so schlimm wie ein Käfer.
Gnasher729
Ich habe die Einschränkung nicht gesehen 0<=amount && amount<32. Ob größere / kleinere Werte sinnvoll sind? Ich dachte, ob sie es tun, ist Teil der Frage. Und angesichts von Bit-Ops keine Klammern zu verwenden, ist wahrscheinlich eine schlechte Idee, sicher, aber sicherlich kein Fehler.
Deduplikator
@Deduplicator Dies liegt daran, dass einige CPUs den Shift-Operator nativ wie (y mod 32)für 32-Bit xund (y mod 64)für 64-Bit implementierenx . Beachten Sie, dass es relativ einfach ist, Code auszugeben, der über alle CPU-Architekturen hinweg ein einheitliches Verhalten erzielt - durch Maskieren des Verschiebungsbetrags. Dies erfordert normalerweise eine zusätzliche Anweisung. Aber leider ...
rwong
-1

Dies liegt daran, dass Ihr Code einen Fehler enthält:

uint32_t blah(uint32_t x, uint32_t y)
{
    if (y != 0) 
    {
        foo(y);
        return x; ////// missing return here //////
    }
    return rotateleft(x, y);
}

Mit anderen Worten, es überspringt nur dann die Kausalitätsbarriere, wenn der Compiler feststellt, dass Sie bei bestimmten Eingaben zweifelsfrei undefiniertes Verhalten aufrufen .

Indem Sie unmittelbar vor dem Aufruf von undefiniertem Verhalten zurückkehren, teilen Sie dem Compiler mit, dass Sie bewusst verhindern, dass dieses undefinierte Verhalten ausgeführt wird, und der Compiler erkennt dies an.

Mit anderen Worten, wenn Sie einen Compiler haben, der versucht, die Spezifikation auf sehr strenge Weise durchzusetzen, müssen Sie jede mögliche Argumentvalidierung in Ihrem Code implementieren. Darüber hinaus muss diese Validierung vor dem Aufruf des undefinierten Verhaltens erfolgen.

Warten! Und es gibt noch mehr!

Wenn Compiler diese super verrückten, aber super logischen Dinge tun, müssen Sie dem Compiler unbedingt mitteilen, dass eine Funktion die Ausführung nicht fortsetzen soll. Somit wird das noreturnSchlüsselwort für die foo()Funktion jetzt obligatorisch .

rwong
quelle
4
Entschuldigung, aber das ist nicht die Frage.
Deduplikator
@Deduplicator Wäre dies nicht die Frage gewesen, hätte diese Frage geschlossen werden müssen, um meinungsbasiert zu sein (offene Debatte). Tatsächlich werden die Motivationen der C- und C ++ - Komitees und die des Compiler-Anbieters / der Autoren hinterfragt.
Rwong
1
Es ist eine Frage zur historischen Interpretation, Verwendung und Begründung von UB in der C-Sprache, nicht beschränkt auf den Standard und das Komitee. Die Ausschüsse hinterfragen? Nicht so.
Deduplikator
1
... erkennen, dass letztere nur eine Anweisung annehmen sollten. Ich weiß nicht, ob die Hypermodernisten verzweifelt versuchen, C in Bereichen wettbewerbsfähig zu machen, in denen es ersetzt wurde, aber sie untergraben seine Nützlichkeit in dem einzigen Bereich, in dem es König ist, und sind nach meinen Erkenntnissen, die nützliche Optimierungen erschweren eher als einfacher.
Supercat
1
@ago: Auf einem System mit 24 Bit int wäre das erste erfolgreich und das zweite würde fehlschlagen. In eingebetteten Systemen ist alles möglich. Oder es geht viel; Ich kann mich an eines erinnern, bei dem int = 32 Bit und long = 40 Bit das einzige System ist, bei dem long viel weniger effizient ist als int32_t.
Gnasher729