¿C ++ 11 abordó las preocupaciones al pasar objetos std lib entre límites de biblioteca dinámicos / compartidos? (es decir, dlls y así)?

34

Una de mis principales quejas sobre C ++ es lo difícil que es en la práctica pasar los objetos de la biblioteca estándar fuera de los límites de la biblioteca dinámica (es decir, dll / so).

La biblioteca estándar a menudo es solo de encabezado. Lo cual es genial para hacer algunas optimizaciones increíbles. Sin embargo, para los dll, a menudo se crean con diferentes configuraciones de compilador que pueden afectar la estructura / código interno de los contenedores de una biblioteca estándar. Por ejemplo, en MSVC, un dll puede compilarse con la depuración del iterador activada mientras que otro construye con él desactivado. Estos dos dlls pueden tener problemas para pasar contenedores estándar. Si expongo std::stringen mi interfaz, no puedo garantizar que el código que utiliza el cliente std::stringcoincida exactamente con el de mi biblioteca std::string.

Esto lleva a problemas difíciles de depurar, dolores de cabeza, etc. Usted controla rígidamente la configuración del compilador en su organización para evitar estos problemas o utiliza una interfaz C más simple que no tendrá estos problemas. O especifique a sus clientes la configuración esperada del compilador que deberían usar (lo que apesta si otra biblioteca especifica otras configuraciones del compilador).

Mi pregunta es si C ++ 11 intentó hacer algo para resolver estos problemas.

Doug T.
fuente
3
No sé la respuesta a su pregunta, pero puedo decir que sus preocupaciones son compartidas; son la clave de por qué no usaré C ++ en mis proyectos, ya que valoramos la estabilidad de ABI en lugar de exprimir hasta el último ciclo de eficiencia potencial.
Donal Fellows
2
Por favor distinga Es difícil entre DLLs. Entre SOs siempre funcionó bien.
Jan Hudec
1
Estrictamente hablando, este no es un problema único de C ++. Es posible tener este problema con otros idiomas.
MrFox
2
@ JanHudec Puedo garantizar que entre SO no funciona casi tan mágicamente como parece indicar. Dada la visibilidad de los símbolos y la forma en que a menudo funciona el cambio de nombre, es posible que esté más aislado de un problema, pero compilar uno .so con diferentes banderas / etc., y asumir que puede vincularlo en un programa con otras banderas es un recipiente para el desastre.
sdg el
3
@sdg: con indicadores predeterminados y visibilidad predeterminada, funciona. Si los cambia y se mete en problemas, es su problema y el de nadie más.
Jan Hudec

Respuestas:

20

Tiene razón en que cualquier cosa STL, en realidad, cualquier cosa de cualquier biblioteca de terceros que tenga plantillas, es mejor evitarla en cualquier API pública de C ++. También desea seguir la larga lista de reglas en http://www.ros.org/reps/rep-0009.html#definition para inhibir la rotura de ABI, lo que hace que la programación de API públicas de C ++ sea una tarea difícil.

Y la respuesta con respecto a C ++ 11 es no, este estándar no toca eso. Más interesante es por qué no? La respuesta es porque C ++ 17 es muy conmovedor, y para que se implementen los módulos C ++ necesitamos plantillas exportadas para trabajar, y para eso necesitamos un compilador de tipo LLVM como clang que puede volcar el AST completo en el disco y luego realice búsquedas dependientes de la persona que llama para manejar los muchos casos de violación de ODR en cualquier proyecto grande de C ++, que, por cierto, incluye muchos códigos GCC y ELF.

Por último, veo muchos comentarios de odio de MSVC y pro-GCC. Estos están muy mal informados: GCC en ELF es fundamental e irremediablemente incapaz de producir un código C ++ válido y correcto. Las razones para esto son muchas y muchas, pero citaré rápidamente un ejemplo de caso: GCC en ELF no puede producir con seguridad extensiones de Python escritas usando Boost.Python donde más de una extensión basada en Boost.Python se carga en Python. Esto se debe a que ELF con su tabla de símbolos C global es simplemente incapaz por el diseño de prevenir violaciones de ODR que causan segfaults, mientras que PE y MachO y, de hecho, la especificación de los módulos C ++ propuestos usan tablas de símbolos por módulo, lo que por cierto también significa tiempos de inicio de proceso mucho más rápidos. Y hay muchos más problemas: vea un StackOverflow al que respondí recientemente enhttps://stackoverflow.com/questions/14268736/symbol-visibility-exceptions-runtime-error/14364055#14364055, por ejemplo, cuando los lanzamientos de excepciones de C ++ se rompen irremediablemente en ELF.

Último punto: con respecto a la interoperabilidad de diferentes STL, este es un gran dolor para muchos usuarios corporativos grandes que intentan mezclar bibliotecas de terceros que están estrechamente integradas a alguna implementación de STL. La única solución es un nuevo mecanismo para que C ++ maneje la interoperabilidad STL, y mientras lo hacen, también podría corregir la interoperabilidad del compilador para que pueda (por ejemplo) mezclar archivos de objetos compilados MSVC, GCC y clang y todo funciona . Vería el esfuerzo de C ++ 17 y vería qué sucede allí en los próximos años; me sorprendería si nada sucede.

Niall Douglas
fuente
Gran respuesta! Solo espero que Clang mejore la compatibilidad de Windows, y podría establecer un buen compilador estándar predeterminado. El sistema de inclusión / encabezado textual de C ++ es horrible, estoy deseando que los módulos simplifiquen la organización del código C ++, aceleren infinitamente los tiempos de compilación y mejoren la interoperabilidad del compilador con capturas que violan ODR.
Alessandro Stamatto
3
Personalmente, en realidad espero un aumento sustancial en los tiempos de compilación. Atravesar un AST intramódulo rápidamente es muy difícil, y probablemente necesitemos un caché de memoria compartida en memoria. Sin embargo, casi todo lo que es malo mejora. Por cierto, los archivos de encabezado definitivamente se quedan, los módulos C ++ actuales tienen archivos de interfaz de mapa 1 a 1 a archivos de encabezado. Además, los archivos de interfaz generados automáticamente serán C ++ legales, por lo que un encabezado heredado simplemente filtra las macros de C y las esparce como archivos de interfaz. Bien eh?
Niall Douglas el
¡Guay! Tengo muchas dudas sobre los módulos. ¿El sistema de módulos tomará en consideración la inclusión textual versus la inclusión simbólica? Con la presente directiva de inclusión, el compilador debe recompilar decenas de miles de líneas de código una y otra vez para cada archivo fuente. ¿El sistema de módulos permitirá algún día código sin declaraciones de reenvío? ¿Mejorará / facilitará las herramientas de construcción?
Alessandro Stamatto
2
-1 por sugerir que todas las plantillas de terceros son sospechosas. Cambiar la configuración es independiente de si lo que se configura es una plantilla.
DeadMG
1
@Alessandro: Los módulos C ++ propuestos desactivan explícitamente las macros C Puede usar plantillas, o ahora. Las interfaces propuestas son C ++ legales, simplemente autogeneradas, y pueden precompilarse opcionalmente para la velocidad de repaso, es decir, no espere ninguna aceleración sobre los encabezados precompilados existentes. Las dos últimas preguntas, en realidad no sé: depende :)
Niall Douglas
8

La especificación nunca tuvo este problema. Esto se debe a que tiene un concepto llamado "regla de una definición", que exige que cada símbolo tenga exactamente una definición en el proceso de ejecución.

Las DLL de Windows violan este requisito. Por eso hay todos estos problemas. Por lo tanto, depende de Microsoft solucionarlo, no el comité de estandarización de C ++. Unix nunca tuvo este problema, porque las bibliotecas compartidas funcionan de manera diferente allí y, de manera predeterminada, se ajustan a una regla de definición (puede romperlo explícitamente, pero obviamente solo lo hace si sabe que puede pagarlo y necesita exprimir los pocos ciclos adicionales).

Las DLL de Windows violan una regla de definición porque:

  • Codifican desde qué biblioteca dinámica se usará un símbolo durante el tiempo de enlace estático y resuelven los símbolos estáticamente dentro de la biblioteca que los define. Entonces, si el mismo símbolo débil se genera en múltiples bibliotecas compartidas y esas bibliotecas que se usan en un solo proceso, el vinculador dinámico no tiene la posibilidad de fusionar esos símbolos. Por lo general, dichos símbolos son miembros estáticos o impedimentos de clase de las instancias de plantilla y eso causa problemas al pasar instancias entre códigos en diferentes DLL.
  • Codifican si el símbolo se importará de la biblioteca dinámica ya durante la compilación. Por lo tanto, el código vinculado estáticamente con alguna biblioteca es incompatible con el código vinculado dinámicamente con la misma biblioteca.

Unix que utiliza las exportaciones en formato ELF importa implícitamente todos los símbolos exportados para evitar el primer problema y no distingue entre símbolos resueltos estáticamente y dinámicamente hasta el momento del enlace estático para evitar el segundo.


El otro problema es de las banderas del compilador. Ese problema existe para cualquier programa compuesto de múltiples unidades de compilación, las bibliotecas dinámicas no tienen que estar involucradas. Sin embargo, es mucho peor en Windows. En Unix, realmente no importa si se vincula estática o dinámicamente, de todos modos nadie vincula el tiempo de ejecución estándar estáticamente (en Linux incluso podría ser ilegal) y no hay un tiempo de ejecución de depuración especial, por lo que una compilación es lo suficientemente buena. Pero la forma en que Microsoft implementó la vinculación estática y dinámica, el tiempo de ejecución de depuración y liberación y algunas otras opciones significa que causaron una explosión combinatoria de las variantes de biblioteca necesarias. De nuevo un problema de plataforma en lugar de un problema de lenguaje C ++.

Jan Hudec
fuente
2
@DougT .: GCC no tiene nada que ver con eso. La plataforma ABI tiene. En ELF, el formato de objeto utilizado por la mayoría de los Unices, las bibliotecas compartidas exportan todos los símbolos visibles e importan todos los símbolos que exportan. Entonces, si algo se genera en varias bibliotecas, el vinculador dinámico utilizará la primera definición para todos. Simple, elegante y de trabajo.
Jan Hudec
1
@MartinBa: No hay nada que fusionar, pero no importa mientras sea igual y mientras no se suponga que se fusionen en primer lugar. Sí, si usa configuraciones de compilador incompatibles en una plataforma ELF, obtendrá el mismo desorden que en cualquier lugar y en todas partes. Incluso si no usa bibliotecas compartidas, entonces aquí está algo fuera de tema.
Jan Hudec el
1
@ Jan: es relevante para tu respuesta. Usted escribe: "... una regla de definición ... Las DLL de Windows violan este requisito ... las bibliotecas compartidas funcionan de manera diferente [en UNix] ..." pero la pregunta que se hace se refiere a problemas con cosas std-lib (definidas en encabezados) y la razón por la que no hay problema en Unix no tiene nada que ver con SO vs.DLL pero con el hecho de que en Unix (aparentemente) solo hay una versión compatible de la biblioteca estándar, mientras que en Windows MS eligió tener versiones incompatibles (depuración) (con comprobación extendida, etc.).
Martin Ba
1
@MartinBa: No, la razón principal por la que hay un problema en Windows es que el mecanismo de exportación / importación utilizado en Windows no puede fusionar correctamente los miembros estáticos y el impedimento de clase de las plantillas en todos los casos y no puede fusionar símbolos vinculados estática y dinámicamente. Las múltiples variantes de la biblioteca empeoran aún más, pero el problema principal es que C ++ necesita flexibilidad del vinculador que el vinculador dinámico de Windows no tiene.
Jan Hudec el
44
Creo que esta implicación de que la especificación de DLL está rota y la demanda correspondiente de Msft para 'arreglarlo' está fuera de lugar. El hecho de que las DLL no admitan ciertas características de C ++ no es un defecto de la especificación de DLL. Las DLL son un mecanismo de empaquetado independiente del lenguaje y del proveedor y ABI para exponer los puntos de entrada al código de máquina ('llamadas a funciones') y blobs de datos. Nunca tuvieron la intención de admitir de forma nativa funciones avanzadas de ningún idioma en particular. No es culpa de Msft o de la especificación DLL que algunas personas quieran que sean otra cosa.
Euro Micelli
6

No.

Se está trabajando mucho para reemplazar el sistema de encabezado, característica que se llama Módulos y que podría tener un impacto en esto, pero ciertamente no es grande.

Klaim
fuente
2
No creo que el sistema de encabezado tenga ningún impacto en esto. Los problemas son que las DLL de Windows violan una regla de definición (lo que significa que no siguen las especificaciones de C ++, por lo que el comité de C ++ no puede hacer nada al respecto) y que hay tantas variantes del tiempo de ejecución estándar en Windows, que el comité de C ++ puede ' No hagas nada al respecto.
Jan Hudec
1
No, ellos no. Cómo podrían, la especificación ni siquiera menciona algo de ese tipo. Aparte de eso, cuando un programa (Windows) está vinculado con dlls de Windows, el ODR está satisfecho: todos los símbolos visibles (exportados) deben obedecer al ODR.
Paul Michalik
@PaulMichalik C ++ cubre la vinculación (fase 9) y me parece que al menos la vinculación del tiempo de carga de las DLL / SO cae dentro de la fase 9. Eso significa que los símbolos con vinculación externa (ya sea exportada o no) deben vincularse y ajustarse a la ODR La vinculación dinámica con LoadLibrary / dlopen obviamente no cumple con esos requisitos.
bames53
@ bames53: En mi humilde opinión, las especificaciones son demasiado débiles para permitir declaraciones de ese tipo. Un .dll / .so podría verse como un "programa" por sí solo. Que, las reglas fueron satisfechas. Algo así como cargar otros "programas" en tiempo de ejecución está tan oculto por el estándar que cualquier afirmación al respecto es bastante arbitraria.
Paul Michalik
@PaulMichalik Si un ejecutable requiere un enlace de tiempo de carga, antes del enlace de tiempo de carga, quedan entidades externas sin resolver y falta la información necesaria para la ejecución. LoadLibrary y dlopen están fuera de la especificación, pero el enlace de tiempo de carga claramente debe ser parte de la fase 9.
bames53