c++ - para - manual de programacion android pdf
¿Es seguro vincular objetos C++ 17, C++ 14 y C++ 11? (3)
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 las interdependencias se expresaron correctamente con la inclusión del encabezado y el compilador no se opuso.
¿Qué combinaciones de estos objetos son 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
¿Qué combinaciones de estos objetos son y no es seguro vincular en un solo binario? ¿Por qué?
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 opción
-std
) no hace ninguna diferencia .
¿Por qué? Porque esa es una propiedad importante de nuestra implementación por la que trabajamos duro para garantizar.
Donde tiene problemas es si vincula objetos compilados con diferentes versiones de GCC
y
ha utilizado características inestables de un nuevo estándar de C ++ antes de que se complete el soporte de GCC para ese estándar.
Por ejemplo, si compila un objeto usando GCC 4.9 y
-std=c++11
y otro objeto con GCC 5 y
-std=c++11
, tendrá problemas.
El soporte C ++ 11 fue experimental en GCC 4.x, por lo que hubo cambios incompatibles entre las versiones GCC 4.9 y 5 de las características C ++ 11.
Del mismo modo, si compila un objeto con GCC 7 y
-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 sigue siendo experimental y en evolución.
Por otro lado, cualquier combinación de los siguientes objetos funcionará (aunque vea la nota a continuación sobre
libstdc++.so
version):
-
objeto D compilado con GCC 4.9 y
-std=c++03
-
objeto E compilado con GCC 5 y
-std=c++11
-
objeto F compilado con GCC 7 y
-std=c++17
Esto se debe a que la compatibilidad con 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 funciones de C ++ 17 y, por lo tanto, no hay problemas de compatibilidad con los otros dos objetos (las únicas funciones que comparten provienen de C ++ 03 o C ++ 11, y las versiones utilizadas hacen que esas partes estén bien).
Si luego desea compilar un cuarto objeto, G, usando GCC 8 y
-std=c++17
entonces necesitaría recompilar F con la misma versión (o no vincular a F) porque los símbolos 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
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 en
libstdc++.so
desde GCC 4.9 o GCC 5 De manera similar, si se vinculó al objeto G, creado con GCC 8, necesitaría usar
libstdc++.so
desde GCC 8 para asegurarse de que se encuentren todos los símbolos necesarios para G.
La regla simple es garantizar 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 sobre su pregunta, es que desde GCC 5 hay
dos implementaciones de
std::string
disponibles en libstdc ++.
Las dos implementaciones no son compatibles con enlaces (tienen diferentes nombres destrozados, por lo que no pueden vincularse entre sí) pero pueden coexistir en el mismo binario (tienen diferentes nombres destrozados, así que no entren en conflicto si un objeto usa
std::string
y el otro usa
std::__cxx11::string
).
Si sus objetos usan
std::string
, generalmente deberían compilarse todos con la misma implementación de cadena.
Compile con
-D_GLIBCXX_USE_CXX11_ABI=0
para seleccionar la
gcc4-compatible
original
gcc4-compatible
, o
-D_GLIBCXX_USE_CXX11_ABI=1
para seleccionar la nueva implementación
cxx11
(no se deje engañar por el nombre, también se puede usar en C ++ 03, se llama
cxx11
porque se ajusta a los requisitos de C ++ 11).
La implementación predeterminada depende de cómo se configuró GCC, pero siempre se puede anular en tiempo de compilación con la macro.
Hay dos partes en la respuesta. Compatibilidad a nivel de compilador y compatibilidad a nivel de enlazador. Comencemos con el primero.
supongamos que todos los encabezados se escribieron en C ++ 11
El uso del mismo compilador significa que se utilizarán el mismo encabezado de biblioteca estándar y los archivos de origen (las instancias asociadas con el compilador) independientemente del estándar C ++ de destino. Por lo tanto, los archivos de encabezado de la biblioteca estándar están escritos para ser 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 C ++ 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 directiva
__cplusplus
.
Vea el archivo fuente del
vector
para un ejemplo interesante de cómo se usa.
Del mismo modo, el compilador rechazará cualquier característica sintáctica ofrecida por las nuevas versiones del estándar.
Todo eso significa que su suposición solo puede aplicarse 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 de 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 que apuntan a C ++ 11 y C ++ 14, respectivamente. Cuando se dirige a C ++ 11, la coma dentro de las comillas no se considera un separador de parámetros; solo hay un parámetro Por lo tanto, el código sería equivalente a:
int x[2] = { 0 }; // C++11
Por otro lado, cuando se dirige a C ++ 14, las comillas simples se interpretan como separadores de dígitos. Por lo 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 puro puede dar lugar a 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 cuidadosamente para asegurarse de que sea compatible con versiones posteriores del estándar.
La directiva
__cplusplus
puede ser útil aquí.
Las otras tres cláusulas del estándar incluyen:
C.3.2 Cláusula 3: conceptos básicos
Cambio : Nuevo deslocalizador habitual (sin colocación)
Justificación : Se requiere para la desasignación de tamaño.
Efecto sobre la característica original : el código válido de C ++ 2011 podría declarar una función de asignación de ubicación global y una función de desasignación de la siguiente manera:
void operator new(std::size_t, std::size_t); void operator delete(void*, std::size_t) noexcept;
Sin embargo, en esta Norma Internacional, la declaración de eliminación de operador puede coincidir con una eliminación de operador habitual predefinida (sin colocación) (3.7.4). Si es así, el programa está mal formado, como lo fue para las funciones de asignación de miembros de clase y las funciones de desasignación (5.3.4).
C.3.3 Cláusula 7: declaraciones
Cambio : las funciones miembro constexpr no estáticas no son funciones miembro implícitamente constantes.
Justificación : necesaria para permitir que las funciones de miembro constexpr muten el objeto.
Efecto sobre la característica original : el código válido de C ++ 2011 puede fallar al compilarse en esta Norma Internacional.
Por ejemplo, el siguiente código es válido en C ++ 2011 pero no válido en esta Norma Internacional porque declara la misma función miembro dos veces con diferentes tipos de retorno:
struct S { constexpr const int &f(); int &f(); };
C.3.4 Cláusula 27: biblioteca de entrada / salida
Cambio : gets no está definido.
Justificación : El uso de gets se considera peligroso.
Efecto sobre la característica original : el código válido de C ++ 2011 que usa la función gets puede fallar al compilarse en esta Norma Internacional.
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 a nivel de enlazador. En general, las posibles razones de incompatibilidades incluyen las siguientes:
- El formato de los archivos de objeto.
-
Programe las rutinas de inicio y finalización y el punto de entrada
main
. - Optimización de todo el programa (WPO).
Si el formato del archivo objeto resultante depende del estándar C ++ de destino, el vinculador debe poder vincular los diferentes archivos 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 compilador. De hecho, ninguno de los enlazadores de GCC, LLVM y VC ++ requiere conocimiento sobre el estándar C ++ objetivo. Esto también significa que podemos vincular archivos de objetos 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 a
main
) es diferente para diferentes estándares de C ++ y las diferentes rutinas no son compatibles entre sí, entonces no sería posible vincular los archivos de objetos.
En GCC, LLVM y VC ++, afortunadamente este no es el caso.
Además, la firma de la función
main
(y las restricciones que se aplican a ella, consulte la Sección 3.6 del estándar) es la misma en todos los estándares de C ++, por lo que no importa en qué unidad de traducción exista.
En general, WPO puede no funcionar bien con archivos de objetos compilados usando 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 del estándar en sí.
Por cierto, aunque el compilador de VC ++ ofrece el interruptor 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 la Actualización 3 de Visual C ++ 2013. 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 apuntan a diferentes versiones del estándar C ++, lo que al menos rompería WPO.
PRUEBA: Mi respuesta puede no ser completa o muy precisa.
Los nuevos estándares C ++ vienen en dos partes: características del lenguaje y componentes estándar de la biblioteca.
Como quiere decir con un nuevo estándar , los cambios en el lenguaje en sí (por ejemplo, a distancia) casi no tienen problemas (a veces existen conflictos en los encabezados de bibliotecas de terceros con características de lenguaje estándar más recientes).
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 usar otra implementación de la biblioteca estándar que no sea el compilador proporcionado. 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 enlaza con las bibliotecas de terceros.