¿Por qué las declaraciones sin efecto se consideran legales en C?

13

Perdón si esta pregunta es ingenua. Considere el siguiente programa:

#include <stdio.h>

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

En el ejemplo anterior, las declaraciones 5;y i;parecen totalmente superfluo, sin embargo, los compila el código sin advertencias o errores por defecto (sin embargo, GCC lanzar una warning: statement with no effect [-Wunused-value]advertencia cuando corrió con -Wall). No tienen ningún efecto en el resto del programa, entonces, ¿por qué se consideran declaraciones válidas en primer lugar? ¿El compilador simplemente los ignora? ¿Hay algún beneficio en permitir tales declaraciones?

AW
fuente
55
¿Cuáles son los beneficios de prohibir tales declaraciones?
Mooing Duck
2
Cualquier expresión puede ser una declaración poniéndola ;después. Complicaría el lenguaje agregar más reglas sobre cuándo las expresiones no pueden ser declaraciones
MM
3
¿Prefieres que tu código no se compile porque ignoras el valor de retorno de printf()? La declaración 5;básicamente dice "hacer lo 5que sea ​​que haga (nada) e ignorar el resultado. Su declaración printf(...)es" hacer lo printf(...)que sea ​​que haga e ignorar los resultados (el valor de retorno de printf()) ". C los trata de la misma manera. Esto también permite código como (void) i;dónde iestá un parámetro a una función que se voidutiliza para marcarlo como no utilizado deliberadamente.
Andrew Henle
1
@AndrewHenle: Eso no es lo mismo, porque las llamadas printf()tienen un efecto, incluso si ignoras el valor que finalmente devuelve. Por el contrario 5;no tiene ningún efecto en absoluto.
Nate Eldredge
1
Porque Dennis Ritchie, y él no está cerca para contarnos.
user207421

Respuestas:

10

Una ventaja de permitir tales declaraciones es el código creado por macros u otros programas, en lugar de ser escrito por humanos.

Como ejemplo, imagine una función int do_stuff(void)que se supone que devuelve 0 en caso de éxito o -1 en caso de error. Podría ser que el soporte para "cosas" es opcional, por lo que podría tener un archivo de encabezado que

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

Ahora imagine algún código que quiera hacer cosas si es posible, pero que realmente puede o no importarle si tiene éxito o no:

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

Cuando STUFF_SUPPORTEDes 0, el preprocesador expandirá la llamada func2a una declaración que solo lee

    (-1);

y entonces el pase del compilador verá el tipo de declaración "superflua" que parece molestarle. ¿Pero qué más se puede hacer? Si es así #define do_stuff() // nothing, entonces el código func1se romperá. (Y aún tendrá una declaración vacía en la func2que solo se lee ;, lo que quizás sea aún más superfluo). Por otro lado, si tiene que definir realmente una do_stuff()función que devuelve -1, puede incurrir en el costo de una llamada a función sin ninguna buena razón

Nate Eldredge
fuente
Una versión más clásica (o me refiero a la versión común) de la no-op es ((void)0).
Jonathan Leffler
Un buen ejemplo de esto es assert.
Neil
3

Las declaraciones simples en C terminan en punto y coma.

Las declaraciones simples en C son expresiones. Una expresión es una combinación de variables, constantes y operadores. Cada expresión da como resultado algún valor de cierto tipo que puede asignarse a una variable.

Dicho esto, algunos "compiladores inteligentes" podrían descartar 5; y yo; declaraciones.

J. Dumbass
fuente
No puedo imaginar ningún compilador que haga algo con esas declaraciones además de descartarlas. ¿Qué más podría hacer con ellos?
Jeremy Friesner
@JeremyFriesner: un compilador muy simple y no optimizador podría muy bien generar código para calcular el valor y poner el resultado en un registro (desde ese punto sería ignorado).
Nate Eldredge
El estándar C no incluye el término "declaración simple". Una declaración de expresión consta de una expresión (opcional) seguida de un punto y coma. No todas las expresiones dan como resultado un valor; Una expresión de tipo voidno tiene valor.
Keith Thompson
2

Se permiten declaraciones sin efecto porque sería más difícil prohibirlas que permitirlas. Esto fue más relevante cuando C se diseñó por primera vez y los compiladores eran más pequeños y simples.

Una declaración de expresión consiste en una expresión seguida de un punto y coma. Su comportamiento es evaluar la expresión y descartar el resultado (si lo hay). Normalmente, el propósito es que la evaluación de la expresión tenga efectos secundarios, pero no siempre es fácil o incluso posible determinar si una expresión dada tiene efectos secundarios.

Por ejemplo, una llamada de función es una expresión, por lo que una llamada de función seguida de un punto y coma es una declaración. ¿Esta declaración tiene algún efecto secundario?

some_function();

Es imposible saberlo sin ver la implementación de some_function.

¿Qué tal esto?

obj;

Probablemente no, pero si objse define como volatile, entonces lo hace.

Permitir que cualquier expresión se convierta en una declaración de expresión agregando un punto y coma simplifica la definición del lenguaje. Requerir que la expresión tenga efectos secundarios agregaría complejidad a la definición del lenguaje y al compilador. C se basa en un conjunto coherente de reglas (las llamadas a funciones son expresiones, las asignaciones son expresiones, una expresión seguida de un punto y coma es una declaración) y permite a los programadores hacer lo que quieran sin evitar que hagan cosas que pueden o no tener sentido.

Keith Thompson
fuente
2

Las declaraciones que enumeró sin efecto son ejemplos de una declaración de expresión , cuya sintaxis se proporciona en la sección 6.8.3p1 del estándar C de la siguiente manera:

 expresión-expresión :
    expresión opt  ;

Toda la sección 6.5 está dedicada a la definición de una expresión, pero hablando en términos generales, una expresión consiste en constantes e identificadores vinculados con operadores. En particular, una expresión puede contener o no un operador de asignación y puede contener o no una llamada a la función.

Entonces, cualquier expresión seguida de un punto y coma califica como una declaración de expresión. De hecho, cada una de estas líneas de su código es un ejemplo de una declaración de expresión:

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

Algunos operadores contienen efectos secundarios como el conjunto de operadores de asignación y los operadores de incremento / decremento previo / posterior, y el operador de llamada de función () puede tener un efecto secundario dependiendo de lo que haga la función en cuestión. Sin embargo, no se exige que uno de los operadores deba tener un efecto secundario.

Aquí hay otro ejemplo:

atoi("1");

Esto es llamar a una función y descartar el resultado, al igual que la llamada printfen su ejemplo, pero a diferencia de printfla llamada a la función en sí no tiene un efecto secundario.

dbush
fuente
1

A veces, tales declaraciones son muy útiles:

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

    return x*x;
}

O cuando el manual de referencia nos dice que solo leamos los registros para archivar algo, por ejemplo, para borrar o establecer alguna bandera (situación muy común en el mundo de los EE. UU.)

#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__
fuente
Cuando *SREGes volátil, *SREG;no tiene ningún efecto en el modelo especificado por el estándar C. El estándar C especifica que tiene un efecto secundario observable.
Eric Postpischil
@EricPostpischil: no, no tiene el efecto observable , pero si tiene el efecto. Ninguno de los objetos visibles en C ha cambiado.
P__J__
C 2018 5.1.2.3 6 define el comportamiento observable del programa para incluir que "Los accesos a objetos volátiles se evalúan estrictamente de acuerdo con las reglas de la máquina abstracta". No hay cuestión de interpretación o deducción; Esta es la definición de comportamiento observable.
Eric Postpischil