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 quevoid 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 avoid 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()
yfunc(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
}