Entender la diferencia entre f() y f(vacío) en C y C++ de una vez por todas


Ok, así que he escuchado diferentes opiniones sobre este tema y solo quiero asegurarme de entenderlo correctamente.

Para C++

Las declaraciones void f(); y void f(void); significan exactamente lo mismo, la función f no toma ningún parámetro. Lo mismo ocurre con las definiciones.

Para C

La declaración void f(void); significa que f no toma ningún parámetro.

Declaración void f(); significa que la función f puede o no tener parámetros, y si lo hace, no saber qué tipo de parámetros son, o cuántos hay de ellos. Tenga en cuenta que no es lo mismo que puntos suspensivos, no podemos usar va_list.

Ahora aquí es donde las cosas se ponen interesantes.

Caso 1

Declaración:

void f();

Definición:

void f(int a, int b, float c)
{
   //...
}

Caso 2

Declaración:

void f();

Definición:

void f()
{
   //...
}

Pregunta:

Qué sucede en tiempo de compilación en los casos 1 y 2 cuando llamamos a f con los argumentos correctos, incorrectos ¿argumentos y ningún argumento en absoluto? ¿Qué sucede en tiempo de ejecución?

Pregunta adicional:

Si declaro f con argumentos, pero lo defino sin ellos, ¿hará una diferencia? ¿Debería ser capaz de abordar los argumentos del cuerpo de la función?

Author: Baum mit Augen, 2012-11-10

4 answers

Más terminología (C, no C++): un prototipo para una función declara los tipos de sus argumentos. De lo contrario, la función no tiene un prototipo.

void f();                      // Declaration, but not a prototype
void f(void);                  // Declaration and prototype
void f(int a, int b, float c); // Declaration and prototype

Las declaraciones que no son prototipos son remanentes de pre-ANSI C, de los días de K&R C. La única razón para usar una declaración de estilo antiguo es mantener la compatibilidad binaria con el código antiguo. Por ejemplo, en Gtk 2 hay una declaración de función sin un prototipo is está allí por accidente, pero no se puede quitar sin romper los binarios. Los comentarios estándar C99:

6.11.6 Declaradores de funciones

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

Recomendación: Sugiero compilar todo el código C en GCC/Clang con -Wstrict-prototypes y -Wmissing-prototypes, además del habitual -Wall -Wextra.

Lo que sucede

void f(); // declaration
void f(int a, int b, float c) { } // ERROR

¡La declaración no está de acuerdo con el cuerpo de la función! Esto es en realidad un error tiempo de compilación, y es porque no se puede tener un argumento float en una función sin un prototipo. La razón por la que no puedes usar un float en una función no tipificada es porque cuando llamas a tal función, todos los argumentos se promocionan usando ciertas promociones predeterminadas. Este es un ejemplo fijo:

void f();

void g()
{
    char a;
    int b;
    float c;
    f(a, b, c);
}

En este programa, a se promueve a int1 y c es promovido a double. Así que la definición de f() tiene que be:

void f(int a, int b, double c)
{
    ...
}

Véase C99 6.7.6, párrafo 15,

Si un tipo tiene una lista de tipos de parámetros y el otro tipo es especificado por un declarador de función que no forma parte de una definición de función y que contiene un lista de identificadores, la lista de parámetros no tendrá un terminador de puntos suspensivos y el tipo de cada el parámetro será compatible con el tipo que resulte de la aplicación del promociones de argumentos predeterminados.

Respuesta 1

¿Qué sucede en tiempo de compilación en los casos 1 y 2 cuando llamamos a f con los argumentos correctos, argumentos incorrectos y ningún argumento en absoluto? ¿Qué sucede en tiempo de ejecución?

Cuando llamas a f(), los parámetros se promocionan utilizando las promociones predeterminadas. Si los tipos promocionados coinciden con los tipos de parámetros reales para f(), entonces todo está bien. Si no coinciden, probablemente compilará pero definitivamente obtendrá un comportamiento indefinido.

"Comportamiento indefinido" es spec-speak para " no hacemos garantías sobre lo que sucederá."Tal vez su programa se bloquee, tal vez funcione bien, tal vez invite a sus suegros a cenar.

Hay dos maneras de obtener diagnósticos en tiempo de compilación. Si tiene un compilador sofisticado con capacidades de análisis estático de módulos cruzados, probablemente recibirá un mensaje de error. También puede obtener mensajes para declaraciones de funciones sin prototipado con GCC, utilizando -Wstrict-prototypes which que recomiendo activar en todos sus proyectos (excepto los archivos que usan Gtk 2).

Respuesta 2

Si declaro f con argumentos, pero lo defino sin ellos, ¿hará una diferencia? ¿Debería ser capaz de abordar los argumentos del cuerpo de la función?

No debería compilar.

Excepciones

En realidad hay dos casos en los que se permite que los argumentos de la función no estén de acuerdo con la definición de la función.

  1. Está bien pasar char * a un función que espera void *, y viceversa.

  2. Está bien pasar un tipo entero con signo a una función que espera la versión sin signo de ese tipo, o viceversa, siempre y cuando el valor sea representable en ambos tipos (es decir, no es negativo y no está fuera del rango del tipo con signo).

Notas a pie de página

1: es posible que char promueve a unsigned int, pero esto es muy poco común.

 51
Author: Dietrich Epp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-11 19:44:12

Todo esto es realmente un punto discutible si estás usando C99 o posterior (y, a menos que estés atascado en un viejo sistema embebido o algo así, probablemente debería estar usando algo más moderno).

La sección C99 / C11 6.11.6 Future language directions, Function declarators establece:

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.

Por lo tanto, debes evitar usar cosas como void f(); por completo.

Si toma parámetros, los lista, formando un prototipo adecuado. Si no, us void para indicar definitivamente que no toma ningún parámetro.

 6
Author: paxdiablo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-29 05:59:51

En C++, f () y f (vacío) es lo mismo

En C, son diferentes y se puede pasar cualquier número de argumentos mientras se llama a la función f (), pero no se puede pasar ningún argumento en f(void)

 3
Author: rs2012,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-10 10:06:38

En C puro, esto resulta en el error: error C2084: function 'void __cdecl f(void )' already has a body

void f(void);
void f( );

int main() {
  f(10);
  f(10.10);
  f("ten");

  return 0;
}

void f(void) {

}

void f( ) {

}

.

 fvoid.c line(19) : error C2084: function 'void __cdecl f(void )' already has a body

Pero en C++ puro, se compilará sin errores.

Sobrecarga de funciones (solo C++, C no tiene sobrecarga)

Se sobrecarga un nombre de función f declarando más de una función con el nombre f en el mismo ámbito. Las declaraciones de f deben diferir entre sí por los tipos y/o el número de argumentos en la lista de argumentos. Cuando llama a una función sobrecargada llamada f, el la función correcta se selecciona comparando la lista de argumentos de la llamada a la función con la lista de parámetros de cada una de las funciones candidatas sobrecargadas con el nombre f.

Ejemplo:

#include <iostream>
using namespace std;

void f(int i);
void f(double  f);
void f(char* c);


int main() {
  f(10);
  f(10.10);
  f("ten");

  return 0;
}

void f(int i) {
  cout << " Here is int " << i << endl;
}
void f(double  f) {
  cout << " Here is float " << f << endl;
}

void f(char* c) {
  cout << " Here is char* " << c << endl;
}

Salida:

 Here is int 10
 Here is float 10.1
 Here is char* ten
 -1
Author: Software_Designer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-10 11:50:47