2010-01-08 24 views
10

He leído las preguntas existentes sobre el enlace externo/interno aquí en SO. Mi pregunta es diferente: ¿qué sucede si tengo múltiples definiciones de la misma variable con enlaces externos en diferentes unidades de traducción bajo C y C++?Diferencia en el enlace entre C y C++?

Por ejemplo:

/*file1.c*/ 

typedef struct foo { 
    int a; 
    int b; 
    int c; 
} foo; 

foo xyz; 


/*file2.c*/ 

typedef struct abc { 
    double x; 
} foo; 

foo xyz; 

Usando Dev-C++ y como un programa C, el programa anterior compila y enlaces perfectamente; mientras que da un error de redefinición múltiple si el mismo se compila como un programa de C++. ¿Por qué debería funcionar en C y cuál es la diferencia con C++? ¿Es este comportamiento indefinido y dependiente del compilador? ¿Qué tan "malo" es este código y qué debo hacer si quiero refactorizarlo (me he encontrado con un montón de código antiguo escrito de esta manera)?

Respuesta

4

Tanto C como C++ tienen una "regla de definición única", que es que cada objeto solo se puede definir una vez en cualquier programa. Las violaciones de esta regla causan el comportamiento indefinido lo que significa que puede o no puede ver un mensaje de diagnóstico al compilar.

Hay una diferencia de idioma entre las siguientes declaraciones en el alcance del archivo, pero no se refiere directamente al problema con su ejemplo.

int a; 

En C esta es una definición tentativa. Se puede combinar con otras definiciones tentativas en la misma unidad de traducción para formar una definición única. En C++ siempre es una definición (debe usar extern para declarar un objeto sin definirlo) y cualquier definición posterior del mismo objeto en la misma unidad de traducción es un error.

En su ejemplo, ambas unidades de traducción tienen una definición (conflictiva) de xyz de sus definiciones tentativas.

0

El programa C lo permite y trata la memoria como una unión. Se ejecutará, pero puede que no te dé lo que esperabas.

El programa C++ (que es más tipado) detecta correctamente el problema y le pide que lo corrija. Si realmente quieres una unión, declarala como una sola. Si desea dos objetos distintos, limite su alcance.

+1

El comportamiento de C puede ser cierto en su implementación pero no está garantizado por el idioma. –

+0

Un nombre de variable es solo una etiqueta para una dirección de memoria. Si proporciona dos definiciones de cómo interpretar esa etiqueta, eso no hace que la etiqueta se refiera mágicamente a dos objetos diferentes. ¿Alguna vez has visto un enlazador que se comporte de manera diferente a eso? –

+0

No niego que este es el comportamiento del enlazador habitual, este comportamiento es utilizado por otros lenguajes y muchas implementaciones C. La implicación de su respuesta, sin embargo, fue que es un comportamiento bien definido. Permitir más de una definición externa en un programa es una extensión común, según el Anexo J estándar de C, pero incluso con esta extensión si las definiciones no concuerdan, da como resultado un comportamiento indefinido. –

1

C++ no permite que un símbolo se defina más de una vez. No estoy seguro de lo que hace el enlazador C, una buena suposición podría ser que simplemente asigna ambas definiciones al mismo símbolo, lo que, por supuesto, causaría errores graves.

Para portar Intentaré poner el contenido de archivos C individuales en espacios de nombres anónimos, lo que esencialmente hace que los símbolos sean diferentes y locales al archivo, para que no entren en conflicto con el mismo nombre en otro lugar.

+0

Claro que se puede definir más de una vez. Las definiciones tienen que ser idénticas, sin embargo. – Potatoswatter

+1

@Potatoswatter: los objetos deben estar _definidos_ solo una vez, se pueden _declarar_ varias veces. Las funciones 'inline' son especiales ya que pueden definirse una vez por unidad de traducción, pero otras funciones deben definirse solo una vez en cada programa. –

+0

Lo siento, mi mal: P – Potatoswatter

2

Esto se debe al cambio de nombre de C++. De Wikipedia:

La primera compiladores de C++ se implementan como traductores a C código fuente , que luego se compilado por un compilador C código objeto; porque de esto, los nombres de símbolo tenían que cumplir con las reglas de identificador de C. Incluso más tarde, con la aparición de compiladores que produjeron el código de máquina o el ensamblado directamente, el enlazador del sistema generalmente no admitía los símbolos de C++, y aún se requería modificar.

Con respecto a compatibility:

Con el fin de dar a los fabricantes de compiladores mayor libertad, el C++ normas comité decidió no dictar el implementación de renombrado de nombres, manejo de excepciones, y otra características específicas de la implementación. El inconveniente de de esta decisión es que el código de objeto producido por diferentes compiladores se espera que sea incompatible. Existen, sin embargo, estándares de terceros para máquinas particulares o sistemas operativos que intentan estandarizar compiladores en esas plataformas (por ejemplo, C++ ABI [18]); algunos compiladores adoptan un estándar secundario para estos artículos.

De http://www.cs.indiana.edu/~welu/notes/node36.html se da el siguiente ejemplo:


Por ejemplo, para el siguiente código C

int foo(double*); 
double bar(int, double*); 

int foo (double* d) 
{ 
    return 1; 
} 

double bar (int i, double* d) 
{ 
    return 0.9; 
} 

Su tabla de símbolos sería (por dump -t)

[4] 0x18  44  2  1 0 0x2 bar 
[5] 0x0   24  2  1 0 0x2 foo 

Para mismo archivo, si compilación en g ++, a continuación, la tabla de símbolos sería

[4] 0x0   24  2  1 0 0x2 _Z3fooPd 
[5] 0x18  44  2  1 0 0x2 _Z3bariPd 

_Z3bariPd significa una función cuyo nombre es bar y cuya primera arg es entero y segundo argumento es puntero a duplicar.


0

Has encontrado One Definition Rule. Es evidente que su programa tiene un error, desde

  • Sólo puede haber un objeto llamado foo una vez que se vincula el programa.
  • Si algún archivo fuente incluye todos los archivos de encabezado, verá dos definiciones de foo.

Los compiladores de C++ pueden obtener el n. ° 1 debido a la "creación de nombres": el nombre de la variable en el programa vinculado puede ser diferente al que usted eligió. En este caso, no es obligatorio, pero es probable que el compilador haya detectado el problema. # 2, sin embargo, permanece así que no puedes hacer eso.

Si realmente quiere alterar el mecanismo de seguridad, puede desactivar mangling así:

extern "C" struct abc foo; 

... otro archivo ...

extern "C" struct foo foo; 

extern "C" indica al enlazador a utilizar convenciones C ABI.

+0

Oh, por supuesto, como alguien más mencionó, simplemente debería usar una 'unión' en su lugar. – Potatoswatter