Perché le dichiarazioni senza effetto sono considerate legali in C?


13

Scusate se questa domanda è ingenua. Considera il seguente programma:

#include <stdio.h>

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

Nell'esempio di cui sopra, le dichiarazioni 5;e i;sembrano del tutto superfluo, ma il codice viene compilato senza avvisi o errori di default (tuttavia, gcc fa lanciare un warning: statement with no effect [-Wunused-value]avviso quando correva con -Wall). Non hanno alcun effetto sul resto del programma, quindi perché vengono considerate dichiarazioni valide in primo luogo? Il compilatore semplicemente li ignora? Ci sono dei vantaggi nel consentire tali dichiarazioni?


5
Quali sono i vantaggi di vietare tali dichiarazioni?
Mooing Duck il

2
Qualsiasi espressione può essere un'affermazione inserendola ;dopo. Sarebbe complicato il linguaggio aggiungere ulteriori regole su quando le espressioni non possono essere dichiarazioni
MM

3
Preferiresti che il tuo codice non si compilasse perché ignori il valore di ritorno di printf()? L'istruzione 5;dice sostanzialmente "fai tutto ciò che 5non fa (nulla) e ignora il risultato. La tua dichiarazione printf(...)è" fai tutto ciò che printf(...)fa e ignora i risultati (il valore restituito da printf()) ". C considera quelli uguali. Ciò consente anche il codice come (void) i;dove si itrova un parametro per una funzione a cui è stato eseguito il cast per voidcontrassegnarlo come deliberatamente inutilizzato.
Andrew Henle

1
@AndrewHenle: Non è esattamente lo stesso, perché la chiamata printf()ha un effetto, anche se ignori il valore che alla fine restituisce. Al contrario, 5;non ha alcun effetto.
Nate Eldredge,

1
Perché Dennis Ritchie e lui non è in giro per dircelo.
user207421

Risposte:


10

Un vantaggio nel consentire tali dichiarazioni è dal codice creato da macro o altri programmi, piuttosto che essere scritto da umani.

Ad esempio, immagina una funzione int do_stuff(void)che dovrebbe restituire 0 in caso di successo o -1 in caso di fallimento. Potrebbe essere che il supporto per "roba" sia facoltativo, e quindi potresti avere un file header che lo fa

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

Ora immagina un po 'di codice che vuole fare cose, se possibile, ma potrebbe davvero non interessarsi se ha successo o meno:

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();
}

Quando STUFF_SUPPORTEDè 0, il preprocessore espande la chiamata func2a un'istruzione appena letta

    (-1);

e così il passaggio del compilatore vedrà proprio il tipo di istruzione "superflua" che sembra disturbarti. Ma cos'altro si può fare? Se sì #define do_stuff() // nothing, il codice in func1si interromperà. (E avrai ancora un'istruzione vuota in func2quello che legge ;, che è forse ancora più superflua.) D'altra parte, se devi effettivamente definire una do_stuff()funzione che restituisce -1, potresti sostenere il costo di una chiamata di funzione senza una buona ragione.


Una versione più classica (o intendo versione comune) della no-op è ((void)0).
Jonathan Leffler,

Un buon esempio di questo è assert.
Neil

3

Le istruzioni semplici in C sono terminate con punto e virgola.

Le dichiarazioni semplici in C sono espressioni. Un'espressione è una combinazione di variabili, costanti e operatori. Ogni espressione genera un valore di un certo tipo che può essere assegnato a una variabile.

Detto questo, alcuni "compilatori intelligenti" potrebbero scartare 5; e io; dichiarazioni.


Non riesco a immaginare un compilatore che farebbe qualsiasi cosa con quelle dichiarazioni oltre a scartarle. Cos'altro potrebbe fare con loro?
Jeremy Friesner,

@JeremyFriesner: un compilatore molto semplice e non ottimizzante potrebbe benissimo generare codice per calcolare il valore e inserire il risultato in un registro (da quel punto sarebbe ignorato).
Nate Eldredge,

Lo standard C non ha il termine "semplice dichiarazione". Un'istruzione expression è composta da un'espressione (facoltativa) seguita da un punto e virgola. Non tutte le espressioni producono un valore; un'espressione di tipo voidnon ha valore.
Keith Thompson,

2

Dichiarazioni senza effetto sono consentite perché sarebbe più difficile vietarle che autorizzarle. Questo era più rilevante quando C fu progettato per la prima volta e i compilatori erano più piccoli e più semplici.

Un'istruzione expression è composta da un'espressione seguita da un punto e virgola. Il suo comportamento è valutare l'espressione e scartare il risultato (se presente). Normalmente lo scopo è che la valutazione dell'espressione abbia effetti collaterali, ma non è sempre facile o addirittura possibile determinare se una determinata espressione abbia effetti collaterali.

Ad esempio, una chiamata di funzione è un'espressione, quindi una chiamata di funzione seguita da un punto e virgola è un'istruzione. Questa affermazione ha effetti collaterali?

some_function();

È impossibile dirlo senza vedere l'implementazione di some_function.

Cosa ne pensi di questo?

obj;

Probabilmente no - ma se objdefinito come volatile, allora lo fa.

Permettere a qualsiasi espressione di essere trasformata in una dichiarazione di espressione aggiungendo un punto e virgola rende la definizione del linguaggio più semplice. Richiedere all'espressione di avere effetti collaterali aggiungerebbe complessità alla definizione del linguaggio e al compilatore. C è basato su un insieme coerente di regole (le chiamate di funzione sono espressioni, i compiti sono espressioni, un'espressione seguita da un punto e virgola è un'istruzione) e consente ai programmatori di fare ciò che vogliono senza impedire loro di fare cose che potrebbero o non abbiano senso.


2

Le istruzioni che hai elencato senza alcun effetto sono esempi di una dichiarazione di espressione , la cui sintassi è data nella sezione 6.8.3p1 dello standard C come segue:

 espressione-espressione :
    espressione opt  ;

Tutta la sezione 6.5 è dedicata alla definizione di un'espressione, ma in termini vaghi un'espressione è costituita da costanti e identificatori collegati agli operatori. In particolare, un'espressione può contenere o meno un operatore di assegnazione e può contenere o meno una chiamata di funzione.

Quindi qualsiasi espressione seguita da un punto e virgola si qualifica come un'istruzione di espressione. In effetti, ognuna di queste righe del tuo codice è un esempio di una dichiarazione di espressione:

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

Alcuni operatori contengono effetti collaterali come l'insieme di operatori di assegnazione e gli operatori di incremento / decremento pre / post e l'operatore di chiamata della funzione () può avere un effetto collaterale a seconda dell'azione della funzione in questione. Non è necessario tuttavia che uno degli operatori debba avere un effetto collaterale.

Ecco un altro esempio:

atoi("1");

Questo è chiamare una funzione e scartare il risultato, proprio come la chiamata printfnel tuo esempio, ma diversamente printfdalla chiamata di funzione stessa non ha un effetto collaterale.


1

A volte tali affermazioni sono molto utili:

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

    return x*x;
}

O quando il manuale di riferimento ci dice di leggere semplicemente i registri per archiviare qualcosa, ad esempio per cancellare o impostare un flag (situazione molto comune nel mondo degli Stati Uniti)

#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


Quando *SREGè volatile, *SREG;non ha alcun effetto nel modello specificato dallo standard C. Lo standard C specifica che ha un effetto collaterale osservabile.
Eric Postpischil,

@EricPostpischil - no, non ha l' effetto osservabile , ma se ha l'effetto. Nessuno degli oggetti C visibili è cambiato.
P__J__

C 2018 5.1.2.3 6 definisce il comportamento osservabile del programma per includere che "Gli accessi a oggetti volatili sono valutati rigorosamente secondo le regole della macchina astratta". Non c'è questione di interpretazione o deduzione; questa è la definizione di comportamento osservabile.
Eric Postpischil,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.