¿Por qué se compila una función sin parámetros (en comparación con la definición de función real)?

388

Acabo de encontrar el código C de alguien que me confunde por qué está compilando. Hay dos puntos que no entiendo.

Primero, el prototipo de la función no tiene parámetros en comparación con la definición de la función real. En segundo lugar, el parámetro en la definición de la función no tiene un tipo.

#include <stdio.h>

int func();

int func(param)
{
    return param;
}

int main()
{
    int bla = func(10);    
    printf("%d", bla);
}

¿Por qué funciona esto? Lo he probado en un par de compiladores y funciona bien.

AdmiralJonB
fuente
77
Es K&R C. Escribimos un código como este en la década de 1980 antes de que hubiera prototipos de funciones completas.
hughdbrown
66
gcc advierte -Wstrict-prototypestanto para el int func()y int main(): xc: 3: advertencia: la declaración de función no es un prototipo. Usted debe declarar main()como main(void)así.
Jens
3
@Jens ¿Por qué editaste la pregunta? Parece que has perdido el punto ...
dlras2
1
Acabo de hacer el int implícito explícito. ¿Cómo se pierde eso? Creo que el punto es por qué int func();es compatible con int func(arglist) { ... }.
Jens
3
@MatsPetersson Esto está mal. C99 5.1.2.2.1 contradice expresamente su reclamo y afirma que la versión sin argumento es int main(void).
Jens

Respuestas:

271

Todas las otras respuestas son correctas, pero solo para completar

Una función se declara de la siguiente manera:

  return-type function-name(parameter-list,...) { body... }

return-type es el tipo de variable que devuelve la función. Esto no puede ser un tipo de matriz o un tipo de función. Si no se da, se supone int .

nombre-función es el nombre de la función.

parámetro-lista es la lista de parámetros que la función toma separados por comas. Si no se dan parámetros, la función no toma ninguno y debe definirse con un conjunto de paréntesis vacío o con la palabra clave void. Si no hay ningún tipo de variable delante de una variable en la lista de parámetros, se supone int . Las matrices y funciones no se pasan a funciones, sino que se convierten automáticamente en punteros. Si la lista termina con puntos suspensivos (, ...), entonces no hay un número establecido de parámetros. Nota: el encabezado stdarg.h se puede usar para acceder a argumentos cuando se usan puntos suspensivos.

Y de nuevo por el bien de la integridad. De la especificación C11 6: 11: 6 (página: 179)

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

Krishnabhadra
fuente
2
"Si no hay ningún tipo de variable delante de una variable en la lista de parámetros, se supone int". Veo esto en el enlace provisto, pero no puedo encontrarlo en ningún estándar c89, c99 ... ¿Puede proporcionar otra fuente?
godaygo
"Si no se dan parámetros, entonces la función no toma ninguno y debe definirse con un paréntesis vacío" suena contradictorio con la respuesta de Tony The Lion, pero acabo de enterarme de que la respuesta de Tony The Lion es correcta de la manera difícil.
jakun
160

En C func()significa que puede pasar cualquier número de argumentos. Si no desea argumentos, debe declarar como func(void). El tipo que está pasando a su función, si no se especifica de manera predeterminada int.

Tony el león
fuente
3
En realidad, no hay un solo tipo predeterminado en int. De hecho, todos los argumentos por defecto son int (incluso el tipo de retorno). Está perfectamente bien llamar func(42,0x42);(donde todas las llamadas deben usar la forma de dos argumentos).
Jens
1
En C, es bastante común que los tipos no especificados tengan el valor predeterminado int como, por ejemplo, cuando declara una variable: unsigned x; ¿De qué tipo es la variable x? Resulta su int sin firmar
bitek
44
Prefiero decir que el int implícito era común en los viejos tiempos. Ciertamente ya no es más y en C99 fue retirado de C.
Jens
2
Puede valer la pena señalar que esta respuesta se aplica a prototipos de funciones con listas de parámetros vacías, pero no a definiciones de funciones con listas de parámetros vacías.
autista
@mnemonicflow que no es un "tipo no especificado predeterminado en int"; unsignedes solo otro nombre para el mismo tipo que unsigned int.
hobbs
58

int func();es una declaración de función obsoleta de los días en que no existía el estándar C, es decir, los días de K&R C (antes de 1989, año en que se publicó el primer estándar "ANSI C").

Recuerde que no había prototipos en K&R C y que la palabra clave voidaún no se había inventado. Todo lo que podía hacer era decirle al compilador sobre el tipo de retorno de una función. La lista de parámetros vacía en K&R C significa "un número de argumentos no especificado pero fijo". Fijo significa que debe llamar a la función con el mismo número de argumentos cada vez (a diferencia de una función variada como printf, donde el número y el tipo pueden variar para cada llamada).

Muchos compiladores diagnosticarán esta construcción; en particular gcc -Wstrict-prototypesle dirá "la declaración de función no es un prototipo", lo cual es perfecto, porque parece un prototipo (¡especialmente si está envenenado por C ++!), pero no lo es. Es una declaración de tipo de devolución K&R C de estilo antiguo.

Regla general: nunca deje vacía una declaración de lista de parámetros vacía, utilice int func(void)para ser específico. Esto convierte la declaración de tipo de retorno de K&R en un prototipo C89 adecuado. Los compiladores están contentos, los desarrolladores están contentos, los verificadores estáticos están contentos. Sin embargo, los que confunden con ^ W ^ Wfond de C ++ pueden encogerse, ya que necesitan escribir caracteres adicionales cuando intentan ejercer sus habilidades de idioma extranjero :-)

Jens
fuente
1
Por favor, no relacione esto con C ++. Es de sentido común que una lista de argumentos vacía significa que no hay parámetros , y la gente del mundo no está interesada en lidiar con los errores cometidos por los muchachos de K&R hace unos 40 años. Pero los expertos del comité siguen arrastrando opciones contranaturales a C99, C11.
pfalcon
1
@pfalcon El sentido común es bastante subjetivo. Lo que tiene sentido común para una persona es la locura de los demás. Los programadores profesionales de C saben que las listas de parámetros vacías indican un número de argumentos no especificado pero fijo. Cambiar eso probablemente rompería muchas implementaciones. Culpar a los comités por evitar cambios silenciosos es ladrar el árbol equivocado, en mi humilde opinión.
Jens
53
  • La lista de parámetros vacía significa "cualquier argumento", por lo que la definición no es incorrecta.
  • Se supone que el tipo faltante es int.

Sin embargo, consideraría que cualquier compilación que pase esto carece de un nivel de advertencia / error configurado, no tiene sentido permitir que esto permita el código real.

relajarse
fuente
30

Es la declaración y definición de la función de estilo K&R . Del estándar C99 (ISO / IEC 9899: TC3)

Sección 6.7.5.3 Declaradores de funciones (incluidos los prototipos)

Una lista de identificadores declara solo los identificadores de los parámetros de la función. Una lista vacía en un declarador de funciones 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 parámetros. (Si ambos tipos de funciones son "estilo antiguo", los tipos de parámetros no se comparan).

Sección 6.11.6 Declaradores de funciones

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

Sección 6.11.7 Definiciones de funciones

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

Lo que significa que el viejo estilo de K & R estilo

Ejemplo:

Declaración: int old_style();

Definición:

int old_style(a, b)
    int a; 
    int b;
{
     /* something to do */
}
Lei Mou
fuente
16

C asume intsi no se proporciona ningún tipo en el tipo de retorno de función y la lista de parámetros . Solo para esta regla es posible seguir cosas extrañas.

Una definición de función se ve así.

int func(int param) { /* body */}

Si es un prototipo que escribes

int func(int param);

En prototype solo puede especificar el tipo de parámetros. El nombre de los parámetros no es obligatorio. Entonces

int func(int);

Además, si no especifica el tipo de parámetro pero el nombre intse asume como tipo.

int func(param);

Si vas más lejos, seguir funciona también.

func();

El compilador asume int func()cuando escribes func(). Pero no coloque func()dentro de un cuerpo funcional. Esa será una llamada a funciones

Shiplu Mokaddim
fuente
3
Una lista de parámetros vacía no está relacionada con el tipo int implícito. int func()No es una forma implícita de int func(int).
John Bartholomew
11

Como se dijo en @ Krishnabhadra, todas las respuestas anteriores de otros usuarios tienen una interpretación correcta, y solo quiero hacer un análisis más detallado de algunos puntos.

En el Old-C como en ANSI-C, el " parámetro formal sin tipo ", tome la dimensión de su registro de trabajo o capacidad de profundidad de instrucción (registros sombra o ciclo acumulativo de instrucción), en una MPU de 8 bits, será un int16, en un 16 bits MPU y así será un int16 y así sucesivamente, en el caso de arquitecturas de 64 bits pueden optar por compilar opciones como: -m32.

Aunque parezca una implementación más simple a alto nivel, para pasar múltiples parámetros, el trabajo del programador en el paso de tipo de datos de control dimencion, se vuelve más exigente.

En otros casos, para algunas arquitecturas de microprocesadores, los compiladores ANSI personalizados, aprovecharon algunas de estas características antiguas para optimizar el uso del código, forzando la ubicación de estos "parámetros formales sin tipo" para trabajar dentro o fuera del registro de trabajo, hoy usted obtiene casi lo mismo con el uso de "volátil" y "registro".

Pero debe tenerse en cuenta que los compiladores más modernos, no hacen ninguna distinción entre los dos tipos de declaración de parámetros.

Ejemplos de una compilación con gcc en linux:

C Principal

main2.c

main3.c  
En cualquier caso, la declaración del prototipo localmente no sirve de nada, porque no hay llamada sin parámetros, la referencia a este prototipo será negligente. Si utiliza el sistema con "parámetro formal sin tipo", para una llamada externa, proceda a generar un tipo de datos de prototipo declarativo.

Me gusta esto:

int myfunc(int param);
RTOSkit
fuente
55
Me he tomado la molestia de optimizar las imágenes para que el tamaño en bytes de las imágenes sea el mínimo posible. Creo que las imágenes pueden obtener una vista rápida de la falta de diferencia en el código generado. He estado probando el código, generando documentación real de lo que afirmó y no solo teorizando sobre el problema. pero no todos ven el mundo de la misma manera. Lamento muchísimo que mi intento de proporcionar más información a la comunidad te haya molestado tanto.
RTOSkit
1
Estoy bastante seguro de que siempre hay un tipo de retorno no especificado int, que generalmente es el tamaño del registro de trabajo o 16 bits, lo que sea menor.
supercat
@supercat +1 Deducción perfecta, ¡tienes razón! Esto es exactamente lo que discutí anteriormente, un compilador diseñado por defecto para una arquitectura específica, siempre refleja el tamaño del registro de trabajo de la CPU / MPU en cuestión, por lo tanto, en un entorno integrado, hay varias estrategias de reescritura en un SO en tiempo real, en una capa compiladora (stdint.h), o en una capa de portabilidad, que hace que el mismo SO sea portátil en muchas otras arquitecturas, y se obtiene alineando los tipos específicos de CPU / MPU (int, long, long long, etc.) con los tipos de sistema genéricos u8, u16, u32.
RTOSkit
@supercat Sin los tipos de control ofrecidos por un sistema operativo o por un compilador específico, debe tener un poco de atención en el tiempo de desarrollo y hacer que todas las "asignaciones sin tipo" estén alineadas con el diseño de su aplicación, antes de tener la sorpresa de encontrar un " 16bit int "y no un" 32bit int ".
RTOSkit
@RTOSkit: su respuesta sugiere que el tipo predeterminado en un procesador de 8 bits será un Int8. Recuerdo un par de compiladores C-ish para la arquitectura de la marca PICmicro donde ese fue el caso, pero no creo que nada remotamente parecido a un estándar C haya permitido un inttipo que no sea capaz de mantener todos los valores en el rango: 32767 a +32767 (no se requiere la nota -32768) y también es capaz de contener todos los charvalores (lo que significa que si chartiene 16 bits, debe estar firmado o intdebe ser más grande).
supercat
5

Con respecto al tipo de parámetro, ya hay respuestas correctas aquí, pero si desea escucharlo desde el compilador, puede intentar agregar algunas banderas (las banderas son casi siempre una buena idea de todos modos).

compilando su programa usando gcc foo.c -Wextraconsigo:

foo.c: In function func’:
foo.c:5:5: warning: type of param defaults to int [-Wmissing-parameter-type]

extrañamente -Wextrano capta esto por clang(no reconoce -Wmissing-parameter-typepor alguna razón, tal vez por los históricos mencionados anteriormente) pero -pedanticsí:

foo.c:5:10: warning: parameter 'param' was not declared, 
defaulting to type 'int' [-pedantic]
int func(param)
         ^
1 warning generated.

Y para el problema del prototipo, como se dijo anteriormente, se int func()refiere a parámetros arbitrarios a menos que lo defina explícitamente como lo int func(void)que le daría los errores como se esperaba:

foo.c: In function func’:
foo.c:6:1: error: number of arguments doesnt match prototype
foo.c:3:5: error: prototype declaration
foo.c: In function main’:
foo.c:12:5: error: too many arguments to function func
foo.c:5:5: note: declared here

o en clangcomo:

foo.c:5:5: error: conflicting types for 'func'
int func(param)
    ^
foo.c:3:5: note: previous declaration is here
int func(void);
    ^
foo.c:12:20: error: too many arguments to function call, expected 0, have 1
    int bla = func(10);
              ~~~~ ^~
foo.c:3:1: note: 'func' declared here
int func(void);
^
2 errors generated.
ninguna
fuente
3

Si la declaración de la función no tiene parámetros, es decir, está vacía, entonces está tomando un número no especificado de argumentos. Si desea que no tome argumentos, cámbielo a:

int func(void);
PÁGINAS
fuente
1
"Si el prototipo de función no tiene parámetros" Eso no es un prototipo de función, solo una declaración de función.
effeffe
@effeffe arregló eso. No me di cuenta de que esta pregunta llamaría mucho la atención;)
PP
3
¿No es "prototipo de función" solo una expresión alternativa (posiblemente anticuada) para "declaración de función"?
Giorgio
@Giorgio IMO, ambos son correctos. "prototipo de función" debe coincidir con "definición de función". Probablemente creo que effeffe significaba declaración en la pregunta y lo que quise decir en mi respuesta.
PP
@Giorgio no, la declaración de función se realiza mediante el valor de tipo de retorno, el identificador y una lista de parámetros opcional ; Los prototipos de funciones son declaraciones de funciones con una lista de parámetros.
effeffe
0

Es por eso que normalmente aconsejo a las personas que compilen su código con:

cc -Wmissing-variable-declarations -Wstrict-variable-declarations -Wold-style-definition

Estas banderas imponen un par de cosas:

  • -Wmissing-variable-declaraciones: es imposible declarar una función no estática sin obtener un prototipo primero. Esto hace que sea más probable que un prototipo en un archivo de encabezado coincida con la definición real. Alternativamente, exige que agregue la palabra clave estática a las funciones que no necesitan ser visibles públicamente.
  • -Wstrict-variable-declaraciones: el prototipo debe enumerar adecuadamente los argumentos.
  • -Wold-style-definition: la definición de la función en sí también debe enumerar adecuadamente los argumentos.

Estas banderas también se usan por defecto en muchos proyectos de código abierto. Por ejemplo, FreeBSD tiene estas banderas habilitadas cuando compila con WARNS = 6 en su Makefile.

Ed Schouten
fuente