Supongamos que tengo tres objetos compilados, todos producidos por el mismo compilador / versión :
- A fue compilado con el estándar C ++ 11
- B fue compilado con el estándar C ++ 14
- C fue compilado con el estándar C ++ 17
Para simplificar, supongamos que todos los encabezados se escribieron en C ++ 11, utilizando solo construcciones cuya semántica no ha cambiado entre las tres versiones estándar , por lo que cualquier interdependencia se expresó correctamente con la inclusión del encabezado y el compilador no objetó.
¿Qué combinaciones de estos objetos es y no es seguro vincular en un solo binario? ¿Por qué?
EDITAR: las respuestas que cubren los principales compiladores (por ejemplo, gcc, clang, vs ++) son bienvenidas
std::string
implementación activa en libstdc ++ es independiente del-std
modo utilizado . Esta es una propiedad importante, precisamente para soportar situaciones como las de los OP. Puede usar elstd::string
código nuevo en C ++ 03 y puede usar elstd::string
código antiguo en C ++ 11 (consulte el enlace en el comentario posterior de Matteo).Respuestas:
Para GCC es seguro vincular cualquier combinación de objetos A, B y C. Si todos están construidos con la misma versión, entonces son compatibles con ABI, la versión estándar (es decir, la
-std
opción) no hace ninguna diferencia.¿Por qué? Porque esa es una propiedad importante de nuestra implementación que trabajamos arduamente para asegurar.
Donde tiene problemas es si vincula objetos compilados con diferentes versiones de GCC y ha utilizado características inestables de un nuevo estándar C ++ antes de que se complete el soporte de GCC para ese estándar. Por ejemplo, si compila un objeto usando GCC 4.9
-std=c++11
y otro objeto con GCC 5-std=c++11
, tendrá problemas. El soporte de C ++ 11 era experimental en GCC 4.x, por lo que hubo cambios incompatibles entre las versiones de GCC 4.9 y 5 de las características de C ++ 11. Del mismo modo, si compila un objeto con GCC 7-std=c++17
y otro objeto con GCC 8 y-std=c++17
tendrá problemas, porque el soporte de C ++ 17 en GCC 7 y 8 aún es experimental y está evolucionando.Por otro lado, cualquier combinación de los siguientes objetos funcionará (aunque vea la nota a continuación sobre la
libstdc++.so
versión):-std=c++03
-std=c++11
-std=c++17
Esto se debe a que el soporte de C ++ 03 es estable en las tres versiones del compilador utilizadas, por lo que los componentes de C ++ 03 son compatibles entre todos los objetos. El soporte de C ++ 11 es estable desde GCC 5, pero el objeto D no usa ninguna característica de C ++ 11, y los objetos E y F usan versiones donde el soporte de C ++ 11 es estable. La compatibilidad con C ++ 17 no es estable en ninguna de las versiones del compilador utilizadas, pero solo el objeto F usa las características de C ++ 17 y por lo tanto no hay problemas de compatibilidad con los otros dos objetos (las únicas características que comparten provienen de C ++ 03 o C ++ 11, y las versiones utilizadas hacen que esas partes estén bien). Si luego quisiera compilar un cuarto objeto, G, usando GCC 8 y
-std=c++17
luego necesitaría recompilar F con la misma versión (o no vincularlo a F) porque los símbolos de C ++ 17 en F y G son incompatibles.La única advertencia para la compatibilidad descrita anteriormente entre D, E y F es que su programa debe usar la
libstdc++.so
biblioteca compartida de GCC 7 (o posterior). Debido a que el objeto F se compiló con GCC 7, debe usar la biblioteca compartida de esa versión, ya que compilar cualquier parte del programa con GCC 7 podría introducir dependencias en símbolos que no están presentes enlibstdc++.so
GCC 4.9 o GCC 5. De manera similar, si se vinculó al objeto G, construido con GCC 8, necesitaría usar ellibstdc++.so
de GCC 8 para asegurarse de que se encuentren todos los símbolos necesarios para G. La regla simple es asegurarse de que la biblioteca compartida que utiliza el programa en tiempo de ejecución sea al menos tan nueva como la versión utilizada para compilar cualquiera de los objetos.Otra advertencia al usar GCC, ya mencionada en los comentarios de su pregunta, es que desde GCC 5 hay dos implementaciones
std::string
disponibles en libstdc ++. Las dos implementaciones no son compatibles con enlaces (tienen diferentes nombres mutilados, por lo que no se pueden vincular entre sí) pero pueden coexistir en el mismo binario (tienen diferentes nombres mutilados, así que no entre en conflicto si un objeto usastd::string
y el otros usosstd::__cxx11::string
). Si sus objetos usanstd::string
, por lo general, todos deben compilarse con la misma implementación de cadena. Compile con-D_GLIBCXX_USE_CXX11_ABI=0
para seleccionar lagcc4-compatible
implementación original o-D_GLIBCXX_USE_CXX11_ABI=1
para seleccionar la nuevacxx11
implementación (no se deje engañar por el nombre, también se puede usar en C ++ 03, se llamacxx11
porque cumple con los requisitos de C ++ 11). La implementación predeterminada depende de cómo se configuró GCC, pero el valor predeterminado siempre se puede anular en tiempo de compilación con la macro.fuente
Hay dos partes en la respuesta. Compatibilidad a nivel de compilador y compatibilidad a nivel de enlazador. Empecemos por el primero.
Usar el mismo compilador significa que se usarán el mismo encabezado de biblioteca estándar y los mismos archivos fuente (las partes asociadas con el compilador) independientemente del estándar C ++ de destino. Por lo tanto, los archivos de encabezado de la biblioteca estándar se escriben para que sean compatibles con todas las versiones de C ++ admitidas por el compilador.
Dicho esto, si las opciones del compilador utilizadas para compilar una unidad de traducción especifican un estándar de C ++ en particular, entonces las funciones que solo están disponibles en los estándares más nuevos no deberían ser accesibles. Esto se hace usando la
__cplusplus
directiva. Consulte el archivo de origen vectorial para ver un ejemplo interesante de cómo se usa. Del mismo modo, el compilador rechazará cualquier característica sintáctica ofrecida por las versiones más nuevas del estándar.Todo eso significa que su suposición solo se puede aplicar a los archivos de encabezado que escribió. Estos archivos de encabezado pueden causar incompatibilidades cuando se incluyen en diferentes unidades de traducción dirigidas a diferentes estándares C ++. Esto se discute en el Anexo C del estándar C ++. Hay 4 cláusulas, solo discutiré la primera y mencionaré brevemente el resto.
C.3.1 Cláusula 2: convenciones léxicas
Las comillas simples delimitan un carácter literal en C ++ 11, mientras que son separadores de dígitos en C ++ 14 y C ++ 17. Suponga que tiene la siguiente definición de macro en uno de los archivos de encabezado C ++ 11 puros:
#define M(x, ...) __VA_ARGS__ // Maybe defined as a field in a template or a type. int x[2] = { M(1'2,3'4) };
Considere dos unidades de traducción que incluyen el archivo de encabezado, pero apuntan a C ++ 11 y C ++ 14, respectivamente. Al apuntar a C ++ 11, la coma entre las comillas no se considera un separador de parámetros; solo hay un parámetro. Por tanto, el código sería equivalente a:
int x[2] = { 0 }; // C++11
Por otro lado, al apuntar a C ++ 14, las comillas simples se interpretan como separadores de dígitos. Por tanto, el código sería equivalente a:
int x[2] = { 34, 0 }; // C++14 and C++17
El punto aquí es que el uso de comillas simples en uno de los archivos de encabezado de C ++ 11 puros puede resultar en errores sorprendentes en las unidades de traducción que apuntan a C ++ 14/17. Por lo tanto, incluso si un archivo de encabezado está escrito en C ++ 11, debe escribirse con cuidado para garantizar que sea compatible con versiones posteriores del estándar. La
__cplusplus
directiva puede resultar útil aquí.Las otras tres cláusulas del estándar incluyen:
C.3.2 Cláusula 3: conceptos básicos
C.3.3 Cláusula 7: declaraciones
C.3.4 Cláusula 27: biblioteca de entrada / salida
Las posibles incompatibilidades entre C ++ 14 y C ++ 17 se analizan en C.4. Dado que todos los archivos de encabezado no estándar están escritos en C ++ 11 (como se especifica en la pregunta), estos problemas no ocurrirán, por lo que no los mencionaré aquí.
Ahora discutiré la compatibilidad en el nivel del enlazador. En general, las posibles razones de las incompatibilidades incluyen las siguientes:
main
punto de entrada.Si el formato del archivo de objeto resultante depende del estándar C ++ de destino, el vinculador debe poder vincular los diferentes archivos de objeto. En GCC, LLVM y VC ++, afortunadamente este no es el caso. Es decir, el formato de los archivos de objetos es el mismo independientemente del estándar de destino, aunque depende en gran medida del propio compilador. De hecho, ninguno de los enlazadores de GCC, LLVM y VC ++ requiere conocimientos sobre el estándar C ++ de destino. Esto también significa que podemos vincular archivos de objeto que ya están compilados (vinculando estáticamente el tiempo de ejecución).
Si la rutina de inicio del programa (la función que llama
main
) es diferente para diferentes estándares C ++ y las diferentes rutinas no son compatibles entre sí, entonces no sería posible vincular los archivos de objeto. En GCC, LLVM y VC ++, afortunadamente este no es el caso. Además, la firma de lamain
función (y las restricciones que se le aplican, consulte la Sección 3.6 del estándar) es la misma en todos los estándares C ++, por lo que no importa en qué unidad de traducción exista.En general, es posible que WPO no funcione bien con archivos de objeto compilados con diferentes estándares de C ++. Esto depende exactamente de qué etapas del compilador requieren conocimiento del estándar de destino y qué etapas no y el impacto que tiene en las optimizaciones entre procedimientos que cruzan archivos de objetos. Afortunadamente, GCC, LLVM y VC ++ están bien diseñados y no tienen este problema (no que yo sepa).
Por lo tanto, GCC, LLVM y VC ++ se han diseñado para permitir la compatibilidad binaria en diferentes versiones del estándar C ++. Sin embargo, esto no es realmente un requisito de la norma en sí.
Por cierto, aunque el compilador de VC ++ ofrece el modificador std , que le permite apuntar a una versión particular del estándar C ++, no es compatible con C ++ 11. La versión mínima que se puede especificar es C ++ 14, que es la predeterminada a partir de Visual C ++ 2013 Update 3. Podría usar una versión anterior de VC ++ para apuntar a C ++ 11, pero luego tendría que usar diferentes compiladores de VC ++ para compilar diferentes unidades de traducción que tengan como objetivo diferentes versiones del estándar C ++, lo que al menos rompería WPO.
AVISO: Mi respuesta puede no ser completa o muy precisa.
fuente
Los nuevos estándares de C ++ se dividen en dos partes: características del lenguaje y componentes de biblioteca estándar.
Como quiere decir con nuevo estándar , los cambios en el idioma en sí (por ejemplo, ranged-for) casi no hay problema (a veces existen conflictos en los encabezados de bibliotecas de terceros con características de idioma estándar más nuevas).
Pero la biblioteca estándar ...
Cada versión del compilador viene con una implementación de la biblioteca estándar C ++ (libstdc ++ con gcc, libc ++ con clang, biblioteca estándar MS C ++ con VC ++, ...) y exactamente una implementación, no muchas implementaciones para cada versión estándar. Además, en algunos casos, puede utilizar otra implementación de la biblioteca estándar que no sea la proporcionada por el compilador. Lo que debería importarle es vincular una implementación de biblioteca estándar más antigua con una más nueva.
El conflicto que podría ocurrir entre las bibliotecas de terceros y su código es la biblioteca estándar (y otras bibliotecas) que se vincula a esas bibliotecas de terceros.
fuente