c++ - todas - tipos de funciones en c
Definiciones de función frente a función en alcance (6)
Por lo que sé, esto es legal en C:
foo.c
struct foo {
int a;
};
bar.c
struct foo {
char a;
};
Pero lo mismo con las funciones es ilegal:
foo.c
int foo() {
return 1;
}
bar.c
int foo() {
return 0;
}
y dará como resultado un error de vinculación (definición múltiple de la función foo
).
¿Porqué es eso? ¿Cuál es la diferencia entre los nombres de estructura y los nombres de funciones que hacen que C no pueda manejar una pero no la otra? ¿También este comportamiento se extiende a C ++?
¿Porqué es eso?
struct foo {
int a;
};
define una plantilla para crear objetos. No crea ningún objeto o función. A menos que struct foo
se use en algún lugar de su código, en lo que respecta al compilador / enlazador, esas líneas de código tampoco pueden existir.
Tenga en cuenta que existe una diferencia en la forma en que C y C ++ tratan las definiciones de struct
incompatibles.
Las diferentes definiciones de struct foo
en su código publicado están bien en un programa en C siempre que no mezcle su uso.
Sin embargo, no es legal en C ++. En C ++, tienen enlaces externos y deben definirse de manera idéntica. Ver 3.2 Una regla de definición / 5 para más detalles.
El concepto distintivo en este caso se llama vinculación .
En C struct, las etiquetas union o enum no tienen vinculación . Son efectivamente locales a su alcance.
6.2.2 Enlaces de identificadores
6 Los siguientes identificadores no tienen vinculación: un identificador declarado que no es un objeto o una función; un identificador declarado como un parámetro de función; un identificador de ámbito de bloque para un objeto declarado sin el especificador de clase de almacenamientoextern
.
No se pueden volver a declarar en el mismo ámbito (a excepción de las llamadas declaraciones a plazo ). Pero se pueden volver a declarar libremente en diferentes ámbitos, incluidas diferentes unidades de traducción. En diferentes ámbitos pueden declarar tipos completamente independientes. Esto es lo que tiene en su ejemplo: en dos unidades de traducción diferentes (es decir, en dos ámbitos de archivos diferentes) declaró dos tipos de struct foo
diferentes y no relacionados. Esto es perfectamente legal.
Mientras tanto, las funciones tienen enlace en C. En su ejemplo, estas dos definiciones definen la misma función foo
con enlace externo . Y no se le permite proporcionar más de una definición de cualquier función de enlace externo en todo su programa
6.9 Definiciones externas
5 [...] Si un identificador declarado con enlace externo se usa en una expresión (que no sea parte del operando de un operador_Alignof
o_Alignof
cuyo resultado sea una constante entera), en algún lugar del programa completo habrá exactamente uno definición externa para el identificador; De lo contrario, no habrá más de uno.
En C ++, el concepto de enlace se extiende: asigna un enlace específico a una variedad mucho más amplia de entidades, incluidos los tipos. En C ++ los tipos de clases tienen vinculación. Las clases declaradas en el ámbito del espacio de nombres tienen un enlace externo . Y una regla de definición de C ++ establece explícitamente que si una clase con vínculos externos tiene varias definiciones (a través de diferentes unidades de traducción) se definirá de manera equivalente en todas estas unidades de traducción ( http://eel.is/c++draft/basic.def.odr#12 ). Entonces, en C ++ sus definiciones de struct
serían ilegales.
Sus definiciones de funciones también son ilegales en C ++ debido a la regla ODR de C ++ (pero esencialmente por las mismas razones que en C).
La diferencia no está tanto en los nombres como en la existencia; una definición de estructura no se almacena en ninguna parte y su nombre solo existe durante la compilación.
(Es responsabilidad del programador asegurarse de que no haya conflicto en el uso de estructuras con nombres idénticos. De lo contrario, nuestro querido amigo Undefined Behavior viene llamando).
Por otro lado, una función debe almacenarse en algún lugar, y si tiene un enlace externo, el enlazador necesita su nombre.
Si hace que sus funciones sean static
, por lo que son "invisibles" fuera de su respectiva unidad de compilación, el error de enlace desaparecerá.
Las definiciones de funciones declaran una entidad llamada foo
con enlace externo, y el estándar C dice que no debe haber más de una definición de una entidad con enlace externo. Los tipos de estructura que definió no son entidades con enlace externo, por lo que puede tener más de una definición de struct foo
.
Si declara objetos con enlace externo usando el mismo nombre, sería un error:
foo.c
struct foo {
int a;
};
struct foo obj;
bar.c
struct foo {
char a;
};
struct foo obj;
Ahora tiene dos objetos llamados obj
que ambos tienen enlace externo, lo cual no está permitido.
Aún sería incorrecto incluso si uno de los objetos solo se declara, no se define:
foo.c
struct foo {
int a;
};
struct foo obj;
bar.c
struct foo {
char a;
};
extern struct foo obj;
Esto no está definido, porque las dos declaraciones de obj
refieren al mismo objeto, pero no tienen tipos compatibles (porque la struct foo
se define de manera diferente en cada archivo).
C ++ tiene reglas similares, pero más complejas, para tener en cuenta las funciones en inline
y las variables en inline
, las plantillas y otras características de C ++. En C ++, los requisitos relevantes se conocen como la Regla de definición única (ODR). Una diferencia notable es que C ++ ni siquiera permite las dos definiciones de struct
diferentes, incluso si nunca se usan para declarar objetos con enlaces externos o de otro modo "compartidos" entre unidades de traducción.
Las dos declaraciones para struct foo
son incompatibles entre sí porque los tipos de los miembros no son los mismos. Usarlos dentro de cada unidad de traducción está bien siempre y cuando no hagas nada para confundirlos.
Si por ejemplo hiciste esto:
foo.c:
struct foo {
char a;
};
void bar_func(struct foo *f);
void foo_func()
{
struct foo f;
bar_func(&f);
}
bar.c:
struct foo {
int a;
};
void bar_func(struct foo *f)
{
f.a = 1000;
}
Estaría invocando un comportamiento indefinido porque la struct foo
que espera bar_func
no es compatible con la struct foo
que foo_func
está suministrando.
La compatibilidad de las estructuras se detalla en la sección 6.2.7 del estándar C :
1 Dos tipos tienen tipo compatible si sus tipos son iguales. Reglas adicionales para determinar si dos tipos son compatibles se describen en 6.7.2 para especificadores de tipo, en 6.7.3 para calificadores de tipo y en 6.7.6 para declaradores. Además, dos tipos de estructura, unión o enumerados declarados en unidades de traducción separadas son compatibles si sus etiquetas y miembros cumplen con los siguientes requisitos: Si se declara una con una etiqueta, la otra se debe declarar con la misma etiqueta. Si ambos se completan en cualquier lugar dentro de sus respectivas unidades de traducción, entonces se aplican los siguientes requisitos adicionales: debe haber una correspondencia uno a uno entre sus miembros, de modo que cada par de miembros correspondientes se declare con tipos compatibles; si un miembro del par se declara con un especificador de alineación, el otro se declara con un especificador de alineación equivalente; y si un miembro del par se declara con un nombre, el otro se declara con el mismo nombre. Para dos estructuras, los miembros correspondientes serán declarados en el mismo orden. Para dos estructuras o uniones, los campos de bits correspondientes tendrán los mismos anchos. Para dos enumeraciones, los miembros correspondientes tendrán los mismos valores.
2 Todas las declaraciones que se refieran al mismo objeto o función tendrán un tipo compatible; de lo contrario, el comportamiento es indefinido.
Para resumir, las dos instancias de struct foo
deben tener miembros con el mismo nombre y tipo y en el mismo orden para ser compatibles.
Tales reglas son necesarias para que una struct
pueda definirse una vez en un archivo de encabezado y ese encabezado se incluya posteriormente en varios archivos de origen. Esto hace que la struct
se defina en varios archivos de origen, pero que cada instancia sea compatible.
Para ocultar la definición de la función del enlazador, use la palabra clave static.
foo.c
static int foo() {
return 1;
}
bar.c
static int foo() {
return 0;
}