Warum werden Aussagen ohne Wirkung in C als legal angesehen?

13

Entschuldigen Sie, wenn diese Frage naiv ist. Betrachten Sie das folgende Programm:

#include <stdio.h>

int main() {
  int i = 1;
  i = i + 2;
  5;
  i;
  printf("i: %d\n", i);
}

Im obigen Beispiel erscheinen die Anweisungen 5;und i;völlig überflüssig, der Code wird jedoch standardmäßig ohne Warnungen oder Fehler kompiliert (gcc gibt jedoch eine warning: statement with no effect [-Wunused-value]Warnung aus, wenn mit ausgeführt wird -Wall). Sie haben keine Auswirkungen auf den Rest des Programms. Warum werden sie also überhaupt als gültige Aussagen angesehen? Ignoriert der Compiler sie einfach? Gibt es irgendwelche Vorteile, solche Aussagen zuzulassen?

AW
quelle
5
Was sind die Vorteile eines Verbots solcher Aussagen?
Mooing Duck
2
Jeder Ausdruck kann eine Aussage sein, indem er ;danach gesetzt wird. Es würde die Sprache komplizieren, weitere Regeln hinzuzufügen, wenn Ausdrücke keine Anweisungen sein können
MM
3
Möchten Sie lieber, dass Ihr Code nicht kompiliert wird, weil Sie den Rückgabewert von ignorieren printf()? Die Anweisung 5;lautet im Wesentlichen "Mach was 5auch immer (nichts) und ignoriere das Ergebnis. Deine Aussage printf(...)lautet" Mach was printf(...)auch immer macht und ignoriere die Ergebnisse (der Rückgabewert von printf()) ". C behandelt diese gleich. Dies ermöglicht auch Code wie" (void) i;wo iist " Ein Parameter für eine Funktion, die Sie void
Andrew Henle
1
@AndrewHenle: Das ist nicht ganz dasselbe, da das Aufrufen printf()einen Effekt hat, selbst wenn Sie den Wert ignorieren, den es schließlich zurückgibt. Im Gegensatz dazu 5;hat überhaupt keine Wirkung.
Nate Eldredge
1
Weil Dennis Ritchie, und er ist nicht da, um es uns zu sagen.
user207421

Antworten:

10

Ein Vorteil beim Zulassen solcher Anweisungen besteht darin, dass Code von Makros oder anderen Programmen erstellt wird und nicht von Menschen geschrieben wird.

Stellen Sie sich als Beispiel eine Funktion vor int do_stuff(void), die bei Erfolg 0 oder bei Misserfolg -1 zurückgeben soll. Es kann sein, dass die Unterstützung für "Zeug" optional ist, und Sie könnten eine Header-Datei haben, die dies tut

#if STUFF_SUPPORTED
#define do_stuff() really_do_stuff()
#else
#define do_stuff() (-1)
#endif

Stellen Sie sich nun einen Code vor, der nach Möglichkeit etwas tun möchte, sich aber möglicherweise nicht wirklich darum kümmert, ob er erfolgreich ist oder fehlschlägt:

void func1(void) {
    if (do_stuff() == -1) {
        printf("stuff did not work\n");
    }
}

void func2(void) {
    do_stuff(); // don't care if it works or not
    more_stuff();
}

Wenn STUFF_SUPPORTED0 ist, erweitert der Präprozessor den Aufruf func2zu einer Anweisung, die gerade gelesen wird

    (-1);

und so sieht der Compiler-Pass nur die Art von "überflüssiger" Aussage, die Sie zu stören scheint. Doch was kann man noch tun? Wenn Sie #define do_stuff() // nothing, dann wird der Code in func1brechen. (Und Sie haben immer noch eine leere Anweisung func2, die nur liest ;, was vielleicht noch überflüssiger ist.) Wenn Sie andererseits tatsächlich eine do_stuff()Funktion definieren müssen, die -1 zurückgibt, können die Kosten eines Funktionsaufrufs anfallen aus keinem guten Grund.

Nate Eldredge
quelle
Eine klassischere Version (oder meine ich die übliche Version) des No-Op ist ((void)0).
Jonathan Leffler
Ein gutes Beispiel dafür ist assert.
Neil
3

Einfache Anweisungen in C werden durch Semikolon abgeschlossen.

Einfache Anweisungen in C sind Ausdrücke. Ein Ausdruck ist eine Kombination aus Variablen, Konstanten und Operatoren. Jeder Ausdruck führt zu einem Wert eines bestimmten Typs, der einer Variablen zugewiesen werden kann.

Allerdings könnten einige "Smart Compiler" 5 verwerfen; und ich; Aussagen.

J. Dumbass
quelle
Ich kann mir keinen Compiler vorstellen, der mit diesen Anweisungen etwas anderes machen würde, als sie zu verwerfen. Was könnte es sonst noch mit ihnen machen?
Jeremy Friesner
@JeremyFriesner: Ein sehr einfacher, nicht optimierender Compiler kann sehr gut Code generieren, um den Wert zu berechnen und das Ergebnis in ein Register zu schreiben (ab diesem Zeitpunkt würde es ignoriert).
Nate Eldredge
Der C-Standard verwendet nicht den Begriff "einfache Aussage". Eine Ausdrucksanweisung besteht aus einem (optionalen) Ausdruck, gefolgt von einem Semikolon. Nicht jeder Ausdruck führt zu einem Wert. Ein Ausdruck vom Typ voidhat keinen Wert.
Keith Thompson
2

Aussagen ohne Wirkung sind zulässig, da es schwieriger wäre, sie zu verbieten, als sie zuzulassen. Dies war relevanter, als C zum ersten Mal entworfen wurde und die Compiler kleiner und einfacher waren.

Eine Ausdrucksanweisung besteht aus einem Ausdruck, gefolgt von einem Semikolon. Sein Verhalten besteht darin, den Ausdruck auszuwerten und das Ergebnis (falls vorhanden) zu verwerfen. Normalerweise besteht der Zweck darin, dass die Bewertung des Ausdrucks Nebenwirkungen hat, aber es ist nicht immer einfach oder sogar möglich festzustellen, ob ein bestimmter Ausdruck Nebenwirkungen hat.

Ein Funktionsaufruf ist beispielsweise ein Ausdruck, sodass ein Funktionsaufruf gefolgt von einem Semikolon eine Anweisung ist. Hat diese Aussage irgendwelche Nebenwirkungen?

some_function();

Es ist unmöglich zu sagen, ohne die Implementierung von zu sehen some_function.

Wie wäre es damit?

obj;

Wahrscheinlich nicht - aber wenn objdefiniert als volatile, dann tut es.

Ermöglicht , jeden Ausdruck in einem gemacht werden expression-Anweisung durch ein Semikolon macht die Sprachdefinition einfacher. Das Erfordernis, dass der Ausdruck Nebenwirkungen hat, würde die Sprachdefinition und den Compiler komplexer machen. C basiert auf einem konsistenten Regelwerk (Funktionsaufrufe sind Ausdrücke, Zuweisungen sind Ausdrücke, ein Ausdruck gefolgt von einem Semikolon ist eine Anweisung) und ermöglicht es Programmierern, das zu tun, was sie wollen, ohne sie daran zu hindern, Dinge zu tun, die möglicherweise sinnvoll sind oder nicht.

Keith Thompson
quelle
2

Die Anweisungen, die Sie ohne Auswirkung aufgelistet haben, sind Beispiele für eine Ausdrucksanweisung , deren Syntax in Abschnitt 6.8.3p1 des C-Standards wie folgt angegeben ist:

 Ausdrucksanweisung :
    Ausdruck opt  ;

Der gesamte Abschnitt 6.5 ist der Definition eines Ausdrucks gewidmet, aber lose ausgedrückt besteht ein Ausdruck aus Konstanten und Bezeichnern, die mit Operatoren verknüpft sind. Insbesondere kann ein Ausdruck einen Zuweisungsoperator enthalten oder nicht und er kann einen Funktionsaufruf enthalten oder nicht.

Also jeder gefolgt Ausdruck durch ein Semikolon qualifiziert als Ausdruck Aussage. Tatsächlich ist jede dieser Zeilen aus Ihrem Code ein Beispiel für eine Ausdrucksanweisung:

i = i + 2;
5;
i;
printf("i: %d\n", i);

Einige Operatoren enthalten Nebenwirkungen, wie z. B. den Satz von Zuweisungsoperatoren und die Operatoren vor / nach dem Inkrementieren / Dekrementieren, und der Funktionsaufrufoperator () kann abhängig von der Funktion der betreffenden Funktion einen Nebeneffekt haben. Es ist jedoch nicht erforderlich, dass einer der Bediener eine Nebenwirkung hat.

Hier ist ein weiteres Beispiel:

atoi("1");

Dies ruft eine Funktion auf und verwirft das Ergebnis, genau wie der Aufruf printfin Ihrem Beispiel, aber im Gegensatz printfzum Funktionsaufruf selbst hat dies keine Nebenwirkung.

dbush
quelle
1

Manchmal sind solche Aussagen sehr praktisch:

int foo(int x, int y, int z)
{
    (void)y;   //prevents warning
    (void)z;

    return x*x;
}

Oder wenn das Referenzhandbuch uns auffordert, nur die Register zu lesen, um etwas zu archivieren - zum Beispiel um ein Flag zu löschen oder zu setzen (sehr häufige Situation in der uC-Welt)

#define SREG   ((volatile uint32_t *)0x4000000)
#define DREG   ((volatile uint32_t *)0x4004000)

void readSREG(void)
{
    *SREG;   //we read it here
    *DREG;   // and here
}

https://godbolt.org/z/6wjh_5

P__J__
quelle
Wenn *SREGes flüchtig ist, *SREG;hat es keine Auswirkung auf das im C-Standard angegebene Modell. Der C-Standard gibt an, dass er eine beobachtbare Nebenwirkung hat.
Eric Postpischil
@EricPostpischil - nein, es hat nicht den beobachtbaren Effekt, aber wenn hat den Effekt. Keines der C-sichtbaren Objekte hat sich geändert.
P__J__
C 2018 5.1.2.3 6 definiert das beobachtbare Verhalten des Programms so, dass „Zugriffe auf flüchtige Objekte streng nach den Regeln der abstrakten Maschine bewertet werden“. Es gibt keine Frage der Interpretation oder des Abzugs; Dies ist die Definition von beobachtbarem Verhalten.
Eric Postpischil