Pregunta

Si dos archivos C++ tienen diferentes definiciones de clases con el mismo nombre, cuando se compilan y vinculan, algo se descarta incluso sin previo aviso.Por ejemplo,

// a.cc
class Student {
public:
    std::string foo() { return "A"; }
};
void foo_a()
{
    Student stu;
    std::cout << stu.foo() << std::endl;
}

// b.cc
class Student {
public:
    std::string foo() { return "B"; }
};
void foo_b()
{
    Student stu;
    std::cout << stu.foo() << std::endl;
}

Cuando se compilan y vinculan usando g++, ambos generarán "A" (si a.cc precede a b.cc en el orden de la línea de comando).

Un tema similar es aquí.Veo que el espacio de nombres resolverá este problema, pero no sé por qué el vinculador ni siquiera emite una advertencia.Y si una definición de la clase tiene una función adicional que no está definida en otra, digamos si b.cc se actualiza como:

// b.cc
class Student {
public:
    std::string foo() { return "B"; }
    std::string bar() { return "K"; }
};
void foo_b()
{
    Student stu;
    std::cout << stu.foo() << stu.bar() << std::endl;
}

Entonces stu.bar() funciona bien.Gracias a cualquiera que pueda decirme cómo funcionan el compilador y el vinculador en tal situación.

Como pregunta adicional, si las clases se definen en archivos de encabezado, ¿deberían incluirse siempre en un espacio de nombres sin nombre para evitar tal situación?Hay algún efecto secundario?

¿Fue útil?

Solución

Esto es una violación de la regla de una definición (C++03, 3.2/5 "Regla de una definición"), que dice (entre otras cosas):

Puede haber más de una definición de un tipo de clase (cláusula 9), ...En un programa siempre que cada definición aparezca en una unidad de traducción diferente y proporcionó que las definiciones satisfacen los siguientes requisitos.Dada dicha entidad llamada D definida en más de una unidad de traducción, entonces

  • cada definición de D consistirá en la misma secuencia de tokens;

Si viola la regla de una definición, el comportamiento no está definido (lo que significa que pueden suceder cosas extrañas).

El vinculador ve múltiples definiciones de Student::foo() - uno en el archivo objeto de a y otro en el de b.Sin embargo, no se queja de esto;simplemente selecciona uno de los dos (da la casualidad de que es el primero que encuentra).Este manejo "suave" de funciones duplicadas aparentemente ocurre sólo para funciones en línea.Para funciones no en línea, el vinculador voluntad se quejará de múltiples definiciones y se negará a producir un ejecutable (puede haber opciones que relajen esta restricción).Tanto GNU ld y el vinculador de MSVC se comporta de esta manera.

El comportamiento tiene algún sentido;Las funciones en línea deben estar disponibles en todas las unidades de traducción en las que se utilizan.Y en el caso general, necesitan tener disponibles versiones no en línea (en caso de que la llamada no esté en línea o si se toma la dirección de la función). inline En realidad, es solo un pase libre para evitar la regla de una definición, pero para que funcione, todas las definiciones en línea deben ser iguales.

Cuando miro los volcados de los archivos objeto, no veo nada obvio que me explique cómo el vinculador sabe que una función puede tener múltiples definiciones y otras no, pero estoy seguro de que hay alguna marca o registro. que hace precisamente eso.Desafortunadamente, encuentro que el funcionamiento del vinculador y los detalles del archivo objeto no están particularmente bien documentados, por lo que el mecanismo preciso probablemente seguirá siendo un misterio para mí.

En cuanto a tu segunda pregunta:

Como una pregunta adicional, si las clases se definen en los archivos de encabezado, ¿deberían estar siempre envueltos con espacio de nombres sin nombre para evitar tal situación?Hay algún efecto secundario?

Es casi seguro que no desee hacer esto, cada clase sería de un tipo distinto en cada unidad de traducción, por lo que técnicamente las instancias de la clase no podrían pasarse de una unidad de traducción a otra (mediante puntero, referencia o copia).Además, terminaría con múltiples instancias de miembros estáticos.Probablemente eso no funcionaría bien.

Colóquelos en espacios de nombres diferentes con nombres.

Otros consejos

Usted ha violado la regla de la definición de definiciones de clase y el idioma específicamente prohíbe hacer esto.No se requiere que el compilador / enlazador advierte o diagnosticar, y tal escenario no está garantizado para trabajar como se espera en este caso.

Pero no sé por qué el enlazador ni siquiera dispara una advertencia.

Violaciones de la regla de la definición No requiere diagnóstico porque para implementar diagnósticos, el enlazador tendría que demostrar que las dos definiciones no son, de hecho, equivalentes.

Esto es fácil una vez que las clases tienen diferentes tamaños, un número diferente de miembros o diferentes clases de base.Pero tan pronto como todos esos factores son iguales, podría todavía tienen diferentes definiciones de miembros, por ejemplo, y el enlazador tendría que usar alguna introspección avanzada para compararlos.Incluso si eso siempre fuera posible (y no estoy seguro de que lo es, ya que los archivos de objetos podrían compilarse con diferentes opciones, lo que lleva a una salida diferente), es ciertamente muy ineficiente.

Como consecuencia, el enlazador solo acepta que lo que lanza no viole el ODR.No es una situación perfecta, pero bien.

Creo que su pregunta "extra" es una pista para la pregunta principal.

Si entiendo su pregunta adicional, entonces creo que lo hace no quiere envolverlos en un espacio de nombres, ya que si usted incluye la misma clase en varios archivos .cc, entonces probablemente deseeUse solo una copia de cada método, incluso si se definen dentro de la clase.

Este (tipo de) explica por qué solo obtiene una versión de cada función en su ejemplo principal.Espero que el enlazador esté asumiendo que las dos funciones se generan idénticas a partir de la misma fuente # incluidas.Sería bueno si el enlazador detectara cuando fueran diferentes y emitieran una advertencia, pero supongo que es difícil.

Como señala Mark B, la respuesta práctica es "Simplemente no vaya allí".

Aparte de la violación de una regla de definición, no ves compilador que se queja debido a Nombre Mangling en C ++

Editar: Según lo señalado por Konrad Rudolph: los nombres destrozados en este caso serían iguales.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top