Definiciones de Estructura vs. Función en el ámbito


Así que, hasta donde yo 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á lugar a un error de enlace (definición múltiple de función foo).

¿Por qué es eso? ¿Cuál es la diferencia entre los nombres de estructuras y los nombres de funciones que hace que C no pueda manejar uno pero no el otro? También se extiende este comportamiento para C++?

 28
Author: dbush, 2018-05-29

6 answers

¿Por qué 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 también pueden no existir.

Tenga en cuenta que hay una diferencia en cómo C y C++ tratan con definiciones incompatibles struct.

Las diferentes definiciones de struct foo en su código publicado, está bien en un programa C, siempre y cuando no mezcle sus uso.

Sin embargo, no es legal en C++. En C++, tienen enlace externo y deben definirse de forma idéntica. Véase 3.2 One definition rule/5 para más detalles.

 24
Author: R Sahu,
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
2018-05-30 05:13:10

El concepto distintivo en este caso se llama linkage.

En la estructura C, las etiquetas union o enum no tienen ningún enlace. Son efectivamente locales a su alcance.

6.2.2 Vínculos de los identificadores
6 Los siguientes identificadores no tienen vinculación: un identificador declarado como cualquier otra cosa que un objeto o una función; un identificador declarado como un parámetro de función; un ámbito de bloque identificador de un objeto declarado sin el especificador de clase de almacenamiento extern.

No pueden volver a declararse en el mismo ámbito de aplicación (excepto las denominadas declaraciones forward). Pero pueden re-declararse libremente en diferentes ámbitos, incluyendo 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 archivo diferentes) declaró dos tipos 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 [...] If an identifier declared with external el enlace se usa en una expresión (que no sea parte del operando de a sizeof o _Alignof operador cuyo resultado es una constante entera), en algún lugar del program there shall be exactly one external definition for the identifier; otherwise, there no será más que uno.


En C++ el concepto de enlace se extiende: asigna enlace específico a una variedad mucho más amplia de entidades, incluidos los tipos. En C++ los tipos de clase tienen enlace. Las clases declaradas en el ámbito del espacio de nombres tienen enlace externo. Y Una Regla de Definición de C++ explícitamente establece que si una clase con enlace externo tiene varias definiciones (a través de diferentes unidades de traducción) se definirá de forma equivalente en todas estas unidades de traducción (http://eel.is/c++draft / basic.def.odr#12). Por lo tanto, en C++ sus definiciones struct serían ilegales.

Sus definiciones de función siguen siendo ilegales en C++ también debido a la regla ODR de C++ (pero esencialmente por las mismas razones que en C).

 18
Author: AnT,
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
2018-05-30 06:50:29

Sus definiciones de función 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 ha definido no son entidades con enlace externo, por lo que puede tener más de una definición de struct foo.

Si declaras objetos con enlace externo usando el mismo nombre entonces eso sería un error:

Foo.c

struct foo {
   int a;
};
struct foo obj;

Bar.c

struct foo {
    char a;
};
struct foo obj;

Ahora usted tener dos objetos llamados obj que ambos tienen enlace externo, lo cual no está permitido.

Todavía estaría mal incluso si uno de los objetos solo se declara, no definido:

Foo.c

struct foo {
   int a;
};
struct foo obj;

Bar.c

struct foo {
    char a;
};
extern struct foo obj;

Esto es indefinido, porque las dos declaraciones de obj se refieren al mismo objeto, pero no tienen tipos compatibles (porque struct foo se define de manera diferente en cada archivo).

C++ tiene reglas similares, pero más complejas, para explicar inline funciones y variables inline, plantillas y otras características de C++. En C++ los requisitos relevantes se conocen como Regla de Una Definición (o ODR). Una diferencia notable es que C++ ni siquiera permite las dos definiciones struct diferentes, incluso si nunca se usan para declarar objetos con enlace externo o de otra manera "compartidos" entre unidades de traducción.

 12
Author: Jonathan Wakely,
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
2018-05-29 18:49:55

Las dos declaraciones para struct foo son incompatibles entre sí porque los tipos de los miembros no son los mismos. El uso de ambos dentro de cada unidad de traducción está bien, siempre y cuando no hagas nada para confundir a los dos.

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;
}

Sería invocar un comportamiento indefinido porque struct foo que bar_func espera no es compatible con el struct foo que foo_func es suministrar.

La compatibilidad de las estructuras se detalla en la sección 6.2.7 de la norma C :

1 Dos tipos tienen tipo compatible si sus tipos son los mismos. Las reglas adicionales para determinar si dos tipos son compatibles son descrito en 6.7.2 para los especificadores de tipo, en 6.7.3 para los calificadores de tipo, y en 6.7.6 para los declarantes. Por otra parte, dos estructura, unión, o los tipos enumerados declarados en unidades de traducción separadas son compatibles si sus etiquetas y miembros cumplen los siguientes requisitos: se declara con una etiqueta, la otra se declara con la misma etiqueta. Si ambos se completan en cualquier lugar dentro de su respectiva traducción unidades, a continuación, se aplicarán los siguientes requisitos adicionales: ser una correspondencia uno a uno entre sus miembros de tal manera que cada par de miembros correspondientes se declaran 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 miembro de la pareja se declara con un nombre, el otro se declara con el mismo nombre. Para dos estructuras, los miembros correspondientes serán declarado en el mismo orden. Para dos estructuras o sindicatos, los campos de bits correspondientes tendrán las mismas anchuras. Para dos enumeraciones, miembros correspondientes tendrán los mismos valores.

2 Todas las declaraciones que se refieran al mismo objeto o función deberán tener 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.

Estas reglas son necesarias para que un struct se pueda definir una vez en un archivo de encabezado y ese encabezado se incluya posteriormente en varios archivos fuente. Esto da como resultado que struct se defina en varios archivos fuente, pero con cada instancia compatible.

 3
Author: dbush,
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
2018-05-29 18:57:11

La diferencia no está tanto en los nombres como en la existencia; una definición de estructura no se almacena en ningún lugar 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 nombre idéntico. De lo contrario, nuestro querido viejo amigo Undefined Behaviour viene llamando.)

Por otro lado, una función necesita ser almacenada en algún lugar, y si tiene enlace externo, el enlazador necesita su nombre.

Si usted hace sus funciones static, por lo que son "invisibles" fuera de su unidad de compilación respectiva, el error de enlace desaparecerá.

 2
Author: molbdnilo,
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
2018-05-29 18:48:56

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;
    }
 2
Author: armagedescu,
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
2018-05-29 19:06:11