last estandar compiler c c99

estandar - func() vs func(void) en c99



estandar c99 (4)

de acuerdo con el estándar, ¿func () y func (void) son lo mismo?

No. func(void) dice que la función no toma argumentos en absoluto; mientras que func() dice que la función toma un número no especificado de argumentos. Ambos son válidos, pero el estilo func() está obsoleto y no debe usarse.

Este es un artefacto del estándar anterior C. C99 marcó esto como obsoleto.

6.11.6 Declaradores de funciones :

El uso de declaradores de función con paréntesis vacíos (no declaradores de tipo de parámetro de formato prototipo) es una característica obsoleta.

A partir de C11, sigue siendo tan obsoleto y no se ha eliminado del estándar.

void func() En la práctica, un parámetro vacío significa que se acepta cualquier argumento.

void func(void) no acepta argumentos.

Pero en el Estándar C99, encuentro esas líneas:

6.7.5.3 Declaradores de funciones (incluidos los prototipos)
14 Una lista de identificadores declara solo los identificadores de los parámetros de la función. Una lista vacía en un declarador de función que forma parte de una definición de esa función especifica que la función no tiene parámetros. La lista vacía en un declarador de función que no forma parte de una definición de esa función especifica que no se proporciona información sobre el número o los tipos de los parámetros.

de acuerdo con el estándar, ¿ func() y func(void) son lo mismo?


TL; DR

En declaraciones,

void func1(); // obsolescent void func2(void);

El comportamiento es bastante diferente. El primero declara una función sin ningún prototipo, ¡y puede tomar cualquier cantidad de argumentos! Mientras que este último declara una función con un prototipo, que no tiene parámetros y no acepta argumentos.

En definiciones

void func1() { } // obsolescent

y

void func2(void) { }

  • El primero declara y define una función func1 que no tiene parámetros ni prototipo.

  • Este último declara y define una función func2 con un prototipo que no tiene parámetros.

Estos dos se comportan de manera distinta porque, si bien el compilador de C debe imprimir un mensaje de diagnóstico al llamar a una función prototipada con un número incorrecto de argumentos, no es necesario que lo haga al llamar a una función sin prototipo.

Es decir, dadas las definiciones anteriores

func1(1, 2, 3); // need not produce a diagnostic message func2(1, 2, 3); // must always produce a diagnostic message // as it is a constraint violation

Sin embargo, ambas llamadas son ilegales en programas estrictamente conformes ya que son comportamientos explícitamente indefinidos según 6.5.2.2p6 .

Además, los paréntesis vacíos se consideran una característica obsoleta:

El uso de declaradores de función con paréntesis vacíos (no declaradores de tipo de parámetro de formato prototipo) es una característica obsoleta.

y

El uso de definiciones de funciones con identificadores de parámetros y listas de declaraciones separadas (no tipo de parámetro con formato de prototipo y declaradores de identificadores) es una característica obsoleta.

En detalle

Hay 2 conceptos relacionados pero distintos: parámetros y argumentos.

  • Los argumentos son los valores pasados ​​a la función.

  • Los parámetros son los nombres / variables dentro de la función que se establecen en los valores de los argumentos cuando se ingresa la función

En el siguiente extracto:

int foo(int n, char c) { ... } ... foo(42, ch);

c son parámetros. 42 y ch son argumentos.

El extracto citado solo se refiere a los parámetros de una función, pero no menciona nada sobre el prototipo o los argumentos de la función.

La declaración void func1() significa que la función func1 se puede llamar con cualquier número de argumentos , es decir, no se especifica información sobre el número de argumentos (como una declaración separada, C99 especifica esto como "función sin especificación de parámetros), mientras que declaración void func2(void) significa que la función func2 no acepta ningún argumento en absoluto.

La cita en su pregunta significa que dentro de una definición de función , void func1() y void func2(void) ambos les void func2(void) que no hay parámetros , es decir , nombres de variables que se establecen en los valores de los argumentos cuando se ingresa la función. void func() {} contrasta con void func(); el primero declara que func no toma parámetros, mientras que el segundo es una declaración para una función func para la cual no se especifican parámetros ni sus tipos (una declaración sin prototipo).

Sin embargo, todavía difieren en cuanto a definición en que

  • La definición void func1() {} no declara un prototipo, mientras que void func2(void) {} hace, porque () no es una lista de tipos de parámetros, mientras que (void) es una lista de tipos de parámetros ( 6.7.5.3.10 ):

    El caso especial de un parámetro sin nombre de tipo void como el único elemento en la lista especifica que la función no tiene parámetros.

    y más 6.9.1.7

    Si el declarador incluye una lista de tipos de parámetros, la lista también especifica los tipos de todos los parámetros; dicho declarador también sirve como prototipo de función para llamadas posteriores a la misma función en la misma unidad de traducción. Si el declarante incluye una lista de identificadores, los tipos de los parámetros se declararán en la siguiente lista de declaraciones. En cualquier caso, el tipo de cada parámetro se ajusta como se describe en 6.7.5.3 para una lista de tipos de parámetros; El tipo resultante será un tipo de objeto.

    El declarador de la definición de función para func1 no contiene una lista de tipos de parámetros y, por lo tanto, la función no tiene un prototipo.

  • void func1() { ... } todavía se puede llamar con cualquier número de argumentos, mientras que es un error en tiempo de compilación llamar a void func2(void) { ... } con cualquier argumento (6.5.2.2):

    Si la expresión que denota la función llamada tiene un tipo que incluye un prototipo , el número de argumentos debe coincidir con el número de parámetros. Cada argumento tendrá un tipo tal que su valor pueda asignarse a un objeto con la versión no calificada del tipo de su parámetro correspondiente.

    (énfasis mío)

    Esta es una restricción , que según el estándar dice que una implementación conforme debe mostrar al menos un mensaje de diagnóstico sobre este problema. Pero dado que func1 no tiene un prototipo, no se requiere una implementación conforme para producir ningún diagnóstico.

Sin embargo, si el número de argumentos no es igual al número de parámetros, el comportamiento es indefinido 6.5.2.2p6 :

Si la expresión que denota la función llamada tiene un tipo que no incluye un prototipo , [...] si el número de argumentos no es igual al número de parámetros, el comportamiento es indefinido.

Entonces, en teoría, un compilador C99 conforme también puede cometer un error o diagnosticar una advertencia en este caso. StoryTeller proporcionó evidencia de que el StoryTeller podría diagnosticar esto ; sin embargo, mi GCC no parece hacerlo (y esto también puede ser necesario para que sea compatible con algún código oscuro antiguo también):

void test() { } void test2(void) { } int main(void) { test(1, 2); test2(1, 2); }

Cuando el programa anterior se compila con gcc -std=c99 test.c -Wall -Werror , el resultado es:

test.c: In function ‘main’: test.c:7:5: error: too many arguments to function ‘test2’ test2(1, 2); ^~~~~ test.c:3:6: note: declared here void test2(void) { } ^~~~~

Es decir, los argumentos no se comparan en absoluto con los parámetros de una función cuya declaración en definición no está prototipada ( test ), mientras que GCC considera que es un error en tiempo de compilación especificar cualquier argumento para una función prototipada ( test2 ); cualquier implementación conforme debe diagnosticar esto, ya que es una violación de restricción.


La lista de parámetros vacía dentro de una definición de función significa que no incluye un prototipo ni tiene ningún parámetro.

C11 §6.9.1 / 7 Definiciones de funciones (el énfasis en las citas en curso es mío)

El declarador en una definición de función especifica el nombre de la función que se está definiendo y los identificadores de sus parámetros. Si el declarador incluye una lista de tipos de parámetros , la lista también especifica los tipos de todos los parámetros; dicho declarador también sirve como prototipo de función para llamadas posteriores a la misma función en la misma unidad de traducción.

La pregunta pregunta:

de acuerdo con el estándar, ¿ func() y func(void) son lo mismo?

No. La diferencia esencial entre void func() y void func(void) reside en sus llamadas.

C11 §6.5.2.2 / 2 Llamadas a funciones (dentro de la sección de restricciones ):

Si la expresión que denota la función llamada tiene un tipo que incluye un prototipo , el número de argumentos debe coincidir con el número de parámetros . Cada argumento tendrá un tipo tal que su valor pueda asignarse a un objeto con la versión no calificada del tipo de su parámetro correspondiente.

Observe que los parámetros ≠ argumentos. La función puede no contener parámetros, pero puede tener múltiples argumentos.

Como la función definida con parámetros vacíos no introduce un prototipo, no se compara con sus llamadas, por lo que, en teoría, se le puede proporcionar cualquier número de argumentos.

Sin embargo, técnicamente es un comportamiento indefinido llamar a tal función con al menos un argumento (ver los comments Antti Haapala).

C11 §6.5.2.2 / 6 Llamadas de función (dentro de la sección de semántica ):

Si el número de argumentos no es igual al número de parámetros, el comportamiento es indefinido.

Por lo tanto, la diferencia es sutil:

  • Cuando una función se define con void , no se compilará cuando el número de argumentos no coincida con los parámetros (junto con sus tipos), debido a una violación constante (§6.5.2.2 / 2). Dicha situación requiere un mensaje de diagnóstico del compilador conforme.
  • Si se define con parámetros vacíos, puede o no compilarse (no se requiere un mensaje de diagnóstico del compilador conforme), sin embargo, es UB para llamar a dicha función.

Ejemplo:

#include <stdio.h> void func1(void) { puts("foo"); } void func2() { puts("foo"); } int main(void) { func1(1, 2); // constraint violation, it shouldn''t compile func2(3, 4); // may or may not compile, UB when called return 0; }

Tenga en cuenta que el compilador de optimización puede cortar los argumentos en tal caso. Por ejemplo, así es como Clang compila el código anterior (excluyendo la llamada de func1 ) con -01 en x86-64 de acuerdo con las convenciones de llamadas SysV ABI:

main: # @main push rax ; align stack to the 16-byte boundary call func2 ; call func2 (no arguments given) xor eax, eax ; set zero as return value pop rcx ; restore previous stack position (RSP) ret


La parte significativa de la cita se destaca en negrita a continuación:

6.7.5.3 Declaradores de funciones (incluidos los prototipos) 14 Una lista de identificadores declara solo los identificadores de los parámetros de la función. Una lista vacía en un declarador de función que forma parte de una definición de esa función especifica que la función no tiene parámetros. La lista vacía en un declarador de función que no forma parte de una definición de esa función especifica que no se proporciona información sobre el número o los tipos de los parámetros.

Entonces, cuando la lista de parámetros está vacía para una función con su cuerpo, son lo mismo. Pero de eso es solo una declaración de una función.

void function1(); // No information about arguments void function2(void); // Function with zero arguments void function3() { // Zero arguments } void function4(void) { // Zero arguments }