Pourquoi les déclarations sans effet sont-elles considérées comme légales en C?

13

Pardonnez si cette question est naïve. Considérez le programme suivant:

#include <stdio.h>

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

Dans l'exemple ci-dessus, les instructions 5;et i;semblent totalement superflues, mais le code se compile sans avertissement ni erreur par défaut (cependant, gcc envoie un warning: statement with no effect [-Wunused-value]avertissement lorsqu'il est exécuté avec -Wall). Ils n'ont aucun effet sur le reste du programme, alors pourquoi sont-ils considérés comme des déclarations valables en premier lieu? Le compilateur les ignore-t-il simplement? Y a-t-il des avantages à autoriser de telles déclarations?

AW
la source
5
Quels sont les avantages d'interdire de telles déclarations?
Mooing Duck
2
Toute expression peut être une déclaration en la mettant ;après. Cela compliquerait le langage d'ajouter plus de règles sur le moment où les expressions ne peuvent pas être des déclarations
MM
3
Préférez-vous que votre code ne parvienne pas à être compilé parce que vous ignorez la valeur de retour de printf()? L'instruction 5;dit essentiellement "faire tout ce qui 5fait (rien) et ignorer le résultat. Votre instruction printf(...)est" faire tout ce qui printf(...)fait et ignorer les résultats (la valeur de retour de printf()) ". C les traite de la même manière. Cela permet également d'utiliser du code tel que (void) i;iest un paramètre à une fonction que vous transformez pour voidle marquer comme délibérément inutilisé
Andrew Henle
1
@AndrewHenle: Ce n'est pas tout à fait la même chose, car appeler printf()a un effet, même si vous ignorez la valeur qu'il retourne finalement. En revanche, 5;n'a aucun effet.
Nate Eldredge
1
Parce que Dennis Ritchie, et il n'est pas là pour nous le dire.
user207421

Réponses:

10

Un avantage à autoriser de telles déclarations provient du code créé par des macros ou d'autres programmes, plutôt que d'être écrit par des humains.

Par exemple, imaginez une fonction int do_stuff(void)qui est censée retourner 0 en cas de succès ou -1 en cas d'échec. Il se pourrait que la prise en charge de "stuff" soit facultative, et vous pourriez donc avoir un fichier d'en-tête qui ne

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

Imaginez maintenant du code qui veut faire des choses si possible, mais qui ne se soucie pas vraiment s'il réussit ou échoue:

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

Quand STUFF_SUPPORTEDest 0, le préprocesseur étendra l'appel func2à une instruction qui lit simplement

    (-1);

et ainsi le passage du compilateur verra juste le genre de déclaration "superflue" qui semble vous déranger. Mais que faire d'autre? Si vous #define do_stuff() // nothing, alors le code func1entrera en panne. (Et vous aurez toujours une instruction vide dans func2laquelle se lit simplement ;, ce qui est peut-être encore plus superflu.) D'un autre côté, si vous devez réellement définir une do_stuff()fonction qui renvoie -1, vous pouvez encourir le coût d'un appel de fonction sans raison valable.

Nate Eldredge
la source
Une version plus classique (ou je veux dire une version commune) du no-op est ((void)0).
Jonathan Leffler
Un bon exemple de cela est assert.
Neil
3

Les instructions simples en C se terminent par un point-virgule.

Les instructions simples en C sont des expressions. Une expression est une combinaison de variables, de constantes et d'opérateurs. Chaque expression donne une valeur d'un certain type qui peut être affectée à une variable.

Cela dit, certains «compilateurs intelligents» pourraient en rejeter 5; et moi; déclarations.

J. Dumbass
la source
Je ne peux pas imaginer de compilateur qui ferait quoi que ce soit avec ces instructions à part les supprimer. Que pouvait-il faire d'autre avec eux?
Jeremy Friesner
@JeremyFriesner: Un compilateur très simple et non optimisant pourrait très bien générer du code pour calculer la valeur et mettre le résultat dans un registre (à partir duquel il serait ignoré).
Nate Eldredge
La norme C n'est pas l'expression «simple énoncé». Une instruction d'expression se compose d'une expression (facultative) suivie d'un point-virgule. Toutes les expressions n'aboutissent pas à une valeur; une expression de type voidn'a aucune valeur.
Keith Thompson
2

Les déclarations sans effet sont autorisées car il serait plus difficile de les interdire que de les autoriser. Cela était plus pertinent lorsque C a été conçu pour la première fois et que les compilateurs étaient plus petits et plus simples.

Une instruction d'expression se compose d'une expression suivie d'un point-virgule. Son comportement consiste à évaluer l'expression et à ignorer le résultat (le cas échéant). Normalement, le but est que l'évaluation de l'expression ait des effets secondaires, mais il n'est pas toujours facile ni même possible de déterminer si une expression donnée a des effets secondaires.

Par exemple, un appel de fonction est une expression, donc un appel de fonction suivi d'un point-virgule est une instruction. Cette déclaration a-t-elle des effets secondaires?

some_function();

Il est impossible de dire sans voir la mise en œuvre de some_function.

Que dis-tu de ça?

obj;

Probablement pas - mais si objest défini comme volatile, alors c'est le cas.

Permettre à toute expression d'être transformée en une expression-expression en ajoutant un point-virgule rend la définition du langage plus simple. Exiger que l'expression ait des effets secondaires ajouterait de la complexité à la définition du langage et au compilateur. C est construit sur un ensemble cohérent de règles (les appels de fonction sont des expressions, les affectations sont des expressions, une expression suivie d'un point-virgule est une instruction) et permet aux programmeurs de faire ce qu'ils veulent sans les empêcher de faire des choses qui peuvent ou non avoir du sens.

Keith Thompson
la source
2

Les instructions que vous avez répertoriées sans effet sont des exemples d'une instruction d'expression , dont la syntaxe est donnée dans la section 6.8.3p1 de la norme C comme suit:

 expression-instruction :
    expression opt  ;

Toute la section 6.5 est consacrée à la définition d'une expression, mais en gros, une expression se compose de constantes et d'identificateurs liés à des opérateurs. Notamment, une expression peut contenir ou non un opérateur d'affectation et elle peut ou non contenir un appel de fonction.

Ainsi, toute expression suivie d'un point-virgule peut être considérée comme une instruction d'expression. En fait, chacune de ces lignes de votre code est un exemple de déclaration d'expression:

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

Certains opérateurs contiennent des effets secondaires tels que l'ensemble des opérateurs d'affectation et les opérateurs d'incrémentation / décrémentation pré / post, et l'opérateur d'appel de fonction () peut avoir un effet secondaire en fonction de ce que fait la fonction en question. Il n'est cependant pas obligatoire que l'un des opérateurs ait un effet secondaire.

Voici un autre exemple:

atoi("1");

C'est appeler une fonction et ignorer le résultat, tout comme l'appel printfdans votre exemple, mais contrairement à printfl'appel de fonction lui-même n'a pas d'effet secondaire.

dbush
la source
1

Parfois, de telles déclarations sont très utiles:

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

    return x*x;
}

Ou lorsque le manuel de référence nous dit de simplement lire les registres pour archiver quelque chose - par exemple pour effacer ou définir un indicateur (situation très courante dans le monde uC)

#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__
la source
Quand *SREGest volatile, *SREG;n'a aucun effet dans le modèle spécifié par la norme C. La norme C spécifie qu'il a un effet secondaire observable.
Eric Postpischil
@EricPostpischil - non, il n'a pas l' effet observable , mais s'il a l'effet. Aucun des objets C visibles n'a changé.
P__J__
C 2018 5.1.2.3 6 définit le comportement observable du programme pour inclure que «les accès aux objets volatils sont évalués strictement selon les règles de la machine abstraite». Il n'est pas question d'interprétation ou de déduction; c'est la définition d'un comportement observable.
Eric Postpischil