¿Funciona la función C con parámetro sin indicador de tipo?

El código es el siguiente:

int func(param111) { printf("%d\n", param111); return param111; } int main() { int bla0 = func(99); int bla1 = func(10,99); int bla2 = func(11111110,99,10001); printf("%d, %d, %d\n", bla0, bla1, bla2); } 

Resultado de la comstackción:

 zbie@ubuntu:~$ gcc -Wall -g -std=c99 -O2 zeroparam.c zeroparam.c: In function 'func': zeroparam.c:2: warning: type of 'param111' defaults to 'int' 

Resultado de la ejecución:

 zbie@ubuntu:~$ ./a.out 99 10 11111110 99, 10, 11111110 

Sé que el código debería estar bien si la función tiene cero parámetros, como int func (), que aceptará cualquier entrada . Pero, ¿cómo se comstack este código y se ejecuta con éxito?

Este comportamiento es proporcionar compatibilidad con versiones anteriores del idioma, la versión K&R del idioma. Cuando GCC encuentra una función de “estilo antiguo”, se ajusta al antiguo comportamiento de K&R C que no implica advertencias en esta situación.

De hecho, si cambia la función a: int func(int param111) , obtiene las advertencias esperadas:

 xc: In function 'main': xc:11:5: error: too many arguments to function 'func' xc:2:5: note: declared here xc:12:5: error: too many arguments to function 'func' xc:2:5: note: declared here xc:14:1: warning: control reaches end of non-void function [-Wreturn-type] 

(Probado con GCC 4.7.3 y “gcc -std = c99 -Wall xc && ./a.out”)

O para citar a JeremyP de los comentarios: “En K&R C fue perfectamente correcto llamar a una función con tantos argumentos como quieras, porque la notación de elipsis no se inventó en ese momento”. .

Tenga en cuenta que un comstackdor puede mostrar todas las advertencias adicionales que quiera y aún cumplir con el estándar. Por ejemplo, el comstackdor de Apple advierte sobre este código.

La statement de función se interpreta como una statement de función de estilo K&R porque carece de tipos. En la norma, esto se denomina una statement de función con una lista de identificadores , en oposición a una lista de tipos de parámetros como en la statement habitual.

De acuerdo con la especificación C99, 6.9.1 / 7, solo las definiciones de función con una lista de tipos de parámetros se consideran prototipos de función. El estilo K&R usa una lista de identificadores en su lugar, por lo que no se considera que tenga un prototipo.

Las llamadas de función a funciones sin prototipos no se verifican para los recuentos o tipos de parámetros (según 6.5.2.2/8, “el número y los tipos de argumentos no se comparan con los de los parámetros en una definición de función que no incluye un declarador de prototipo de función” ). Por lo tanto, es legal llamar a una función declarada en el estilo K&R con cualquier número y tipo de argumentos, pero según 6.5.2.2/9, una llamada con tipos no válidos producirá un comportamiento indefinido.

Como ilustración, lo siguiente se comstackrá sin advertencias (en gcc -Wall -Wextra -pedantic -std=c99 -O ):

 #include  void *func(param111) char *param111; { printf("%s\n", param111); return param111; } int main() { void *bla0 = func(); void *bla1 = func(99); void *bla2 = func(11111110,99); printf("%p, %p, %p\n", bla0, bla1, bla2); return 0; } 

A pesar de tener obviamente tipos de parámetros incorrectos y cuentas.

Se interpreta como K&R C, como han explicado otros. Vale la pena señalar que es un comportamiento indefinido en ANSI C:

C11 6.9.1 Definiciones de funciones Sección 9

Si una función que acepta un número variable de argumentos se define sin una lista de tipos de parámetros que termina con la notación de puntos suspensivos, el comportamiento no está definido.

Así que una función de argumentos de número variable tiene que terminar con ... como parámetro, como printf :

 int printf( const char *format ,...); 

Puedo explicar por qué esto funciona, pero no por qué el comstackdor no advierte al respecto.

Hay algunas convenciones de llamada , que especifican cómo se ordenan los argumentos y dónde se colocan. La convención de llamadas C permite pasar parámetros adicionales sin efectos secundarios, porque la persona que llama los limpia, no la función llamada, y todos se pasan en la stack:

Para su caso con func (10, 99), “main” empuja los valores a la stack en el siguiente orden (de derecha a izquierda):

 99 10 

“func” solo conoce un valor, y los toma del final, por lo que param111 == 10 .

Luego “principal”, sabiendo que se empujaron dos argumentos, los recupera, limpiando así la stack.

La función func en su código solo tiene una definición de función pero no un declarador de función. En C99 6.5.2.2 (Llamadas de función ), sus estadísticas:

“No se realizan otras conversiones implícitamente; en particular, el número y los tipos de argumentos no se comparan con los de los parámetros en una definición de función que no incluye un declarador de prototipo de función”.

Cuando se llama func(10,99) y func(11111110, 99, 10001) , el comstackdor no comparará el número y los tipos de argumentos con los parámetros en la definición de la función. Incluso se puede llamar a través de func("abc") . Sin embargo, si agrega la siguiente statement de func de func en su código:

 int func(int); 

( int fun(int) se declara porque el estándar C99 promoverá implícitamente para111 al tipo int ), el comstackdor enviaría los siguientes errores:

zeroparam.c: En la función ‘principal’:
zeroparam.c: 15: 13: error: demasiados argumentos para funcionar ‘func’
zeroparam.c: 6: 5: nota: declarado aquí
zeroparam.c: 16: 17: error: demasiados argumentos para funcionar ‘func’

BTW: No creo que este sea un problema del “progtwig K&R”, ya que explícitamente especifica “-std = c99” en su comando.

Si no recibe ninguna advertencia para ese código, es porque su comstackdor no está aplicando las reglas C99 (llamar a printf , o cualquier función, sin una statement visible es una violación de restricción). Probablemente pueda obtener al menos algunas advertencias al pasar las opciones correctas a su comstackdor. Si estás usando gcc, prueba gcc -std=c99 -pedantic -Wall -Wextra .

El llamado K&R C, el lenguaje descrito en la 1ª edición de 1978 del libro clásico de Kernighan y Ritchie, El lenguaje de progtwigción C , no tenía prototipos de funciones. (Un prototipo es una statement de función que especifica los tipos de sus parámetros). La definición de una función todavía tenía que definir sus parámetros (tal vez implícitamente), pero una statement no lo hizo, y los comstackdores típicos no verificaron la coincidencia correcta de los argumentos (en una llamada de función) a parámetros (en una definición de función).

No estaba completamente claro qué pasaba si llamabas a una función con el número y / o tipos de argumentos incorrectos. En términos modernos, era un comportamiento indefinido, pero los comstackdores más antiguos comúnmente te permiten jugar trucos.

El estándar ANSI C de 1989 (publicado como el estándar ISO C de 1990) introdujo prototipos (tomados de C ++), pero no los requirió. Pero sí declaró explícitamente que llamar a una función con un número o tipo de argumentos incorrectos causa un comportamiento indefinido ; No se requiere que el comstackdor te avise al respecto, pero el progtwig puede hacer cualquier cosa literalmente cuando lo ejecutas.

El estándar ISO C de 1999 eliminó la regla “int implícita” e hizo que fuera ilegal (una violación de restricción ) llamar a una función sin statement visible, pero todavía permitía declaraciones y definiciones de función de estilo antiguo. Así que bajo las reglas K & R1 y C89 / C90, su definición de función:

 int func(param111) { printf("%d\n", param111); return param111; } 

es válido, y param111 es de tipo int . Bajo las reglas de C99, no es válido, pero esto:

 int func(param111) int param111; { printf("%d\n", param111); return param111; } 

sigue siendo legal (y sigue siendo legal incluso bajo la norma 2011).

A partir de C99 y C11, si llama a una función cuya statement visible no es un prototipo, depende completamente de usted obtener los argumentos correctos; El comstackdor no está obligado a advertirte si te equivocas.

Es por eso que siempre debe usar prototipos para todas las declaraciones de funciones y definiciones. La necesidad de escribir código que compile con comstackdores pre-ANSI es prácticamente inexistente en estos días; es difícil encontrar un comstackdor que no admita al menos C89 / C90.

Ah, y necesitas agregar

 #include  

a la parte superior del archivo fuente porque estás llamando a printf . Bajo las reglas C89 / C90, llamar a printf sin una statement visible tiene un comportamiento indefinido (porque printf toma un número variable de argumentos). Bajo C99 y versiones posteriores, es una violación de restricción, que requiere un diagnóstico en tiempo de comstackción.

He estado escudriñando la statement de parámetros faltantes. Una variante ligeramente modificada de su progtwig:

 #include  /* add this line */ int func(param111) int param111; /* add this line */ { printf("%d\n", param111); return param111; } int main(void) /* add "void" */ { int bla0 = func(99); int bla1 = func(10,99); int bla2 = func(11111110,99,10001); printf("%d, %d, %d\n", bla0, bla1, bla2); } 

no viola ninguna regla que requiera un diagnóstico en tiempo de comstackción en C90, C99 o C11, pero la segunda y tercera llamada a func tienen un comportamiento indefinido.

Tenga en cuenta que el comstackdor en realidad tiene suficiente información para advertirle que sus llamadas a func son incorrectas. Se acaba de ver la definición de func , y debe saber que cualquier llamada que no pase exactamente 1 argumento de un tipo que implícitamente se puede convertir a int no es válida. No se requiere ninguna advertencia, pero los comstackdores siempre pueden imprimir las advertencias adicionales que deseen. Al parecer, los autores de gcc (y del comstackdor que esté utilizando) consideraron que no valía la pena el esfuerzo de advertir sobre llamadas no coincidentes a funciones con declaraciones y / o definiciones de estilo antiguo.

Si revisa las advertencias al comstackr, verá este mensaje:

 zeroparam.c: 2: advertencia: el tipo de 'param111' por defecto es 'int'

Eso le indica que un argumento sin un tipo será por defecto un entero. Igual que definir una función sin tipo de retorno, también se establecerá de forma predeterminada en int .