
El estándar ANSI C define el concepto de prototipo de función , que es un subconjunto de una declaración de función que indica los tipos de parámetros de entrada. Se introdujeron prototipos para eliminar las desventajas que poseen las declaraciones de funciones comunes.
Por lo tanto, es obligatorio especificar una lista de tipos de parámetros entre paréntesis de un prototipo de función, de lo contrario, el compilador reconocerá dicha expresión como una declaración de función obsoleta, lo que puede conducir a situaciones ambiguas descritas en este artículo.
Prototipos obsoletos
Una declaración de función introduce el tipo de retorno de la función y su identificador en el alcance especificado. Tenga en cuenta que no todas las declaraciones de funciones pueden considerarse prototipos, sino solo aquellas que tienen una lista de tipos de parámetros de entrada.
Por lo tanto, la primera expresión del código a continuación es una declaración, pero no un prototipo de función. La siguiente expresión puede considerarse legítimamente un prototipo, ya que especifica los tipos de sus parámetros:
void foo(); void bar(int count, const char *word);
Definiciones obsoletas
Vayamos directamente a 1972 (el año en que se lanzó el lenguaje C) y recordemos cómo los programadores de esa época definieron sus funciones. Permítame recordarle que la definición de una función conecta su firma con el bloque ejecutable correspondiente (cuerpo). Este código demuestra la definición de la función add
en el estilo K&R:
void add(right, left, result) int right; int left; int *result; { *result = right + left; }
Como habrá notado, en esta entrada, los paréntesis identifican la función, pero no contienen ningún tipo de parámetros de entrada. Las declaraciones de funciones "clásicas" descritas en la sección anterior tienen la misma propiedad.
Situaciones ambiguas
Es posible que si no se observa la nueva sintaxis de prototipos y definiciones de funciones introducidas por el estándar ANSI C, pueden surgir situaciones ambiguas difíciles. Considere un ejemplo:
#include <stdio.h> #include <stdint.h> #include <inttypes.h> #include <limits.h> /* "print_number" */ void print_number(); int main(void) { /* */ print_number((double)13.359); print_number((double)9238.46436); print_number((double)18437); /* */ print_number(UINT64_MAX); print_number("First", "Second", "Third"); print_number(NULL, "Breakfast", &print_number); } void print_number(double number) { printf(" : [%f]\n", number); }
Analizaremos este programa. La función correcta print_number
declara sin especificar una lista de tipos de parámetros, por lo que puede llamar a esta función con cualquier argumento. El programa compiló sin errores e imprimió el siguiente resultado:
$ gcc illegal.c -o illegal -Wall $ ./illegal : [13.359000] : [9238.464360] : [18437.000000] : [0.000000] : [0.000000] : [0.000000]
También tenga en cuenta que incluso con el indicador -Wall
, el compilador -Wall
gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0
no generó ninguna advertencia (pero sería muy deseable).
Arreglar este programa no será difícil, solo agregue el double number
entre paréntesis de la declaración de la función print_number
en la séptima línea, después de lo cual cualquier compilador que siga el estándar indicará errores en la función main()
:
$ gcc -Wall illegal.c -o illegal illegal.c: In function 'main': illegal.c:17:22: error: incompatible type for argument 1 of 'print_number' print_number("First", "Second", "Third"); ^~~~~~~ illegal.c:7:6: note: expected 'double' but argument is of type 'char *' void print_number(double number); ^~~~~~~~~~~~ illegal.c:17:9: error: too many arguments to function 'print_number' print_number("First", "Second", "Third"); ^~~~~~~~~~~~ illegal.c:7:6: note: declared here void print_number(double number); ^~~~~~~~~~~~ illegal.c:18:22: error: incompatible type for argument 1 of 'print_number' print_number(NULL, "Breakfast", &print_number); ^~~~ illegal.c:7:6: note: expected 'double' but argument is of type 'void *' void print_number(double number); ^~~~~~~~~~~~ illegal.c:18:9: error: too many arguments to function 'print_number' print_number(NULL, "Breakfast", &print_number); ^~~~~~~~~~~~ illegal.c:7:6: note: declared here void print_number(double number); ^~~~~~~~~~~~
Funciones sin parámetros.
También noto que es altamente deseable (pero no necesario) especificar la palabra clave void
entre paréntesis de prototipos y definiciones de funciones que no toman parámetros. Si no se observa esta regla, el compilador no podrá verificar la correspondencia de los tipos de argumentos pasados cuando se llama a la función con tipos válidos de la definición.
#include <stdio.h> /* "do_something" */ void do_something(); int main(void) { /* "do_something" */ do_something(NULL, "Papa Johns", 2842, 1484.3355); } void do_something() { puts("I am doing something interesting right now!"); }
Corrija el código anterior insertando la palabra clave void
en la definición y declaración de la función do_something()
, de lo contrario, este programa se compilará sin errores. En este ejemplo, la función main()
también se define con el token void
en los parámetros, aunque esto no es necesario.
Conclusión
La redacción de este artículo se inspiró en el libro de Stephen Prat "Lenguaje de programación C. Conferencias y ejercicios. Sexta edición", y específicamente la sección "Funciones con argumentos" del quinto capítulo.