2010-02-07 12 views
10

Tengo un programa en C++ que compilo con mingw (gcc para Windows). Usando la versión TDM de mingw que incluye gcc 4.4.1. Los enlaces ejecutables a dos archivos de biblioteca estática (.a): uno de ellos es una biblioteca de terceros escrita en C; la otra es una biblioteca C++, escrita por mí, que utiliza la biblioteca C y proporciona mi propia API C++ en la parte superior.Definición múltiple de funciones en línea al vincular bibliotecas estáticas

Una parte (en mi opinión, excesiva) de la funcionalidad de la biblioteca C se implementa en funciones en línea. No puede evitar incluir las funciones en línea cuando usa la API de la biblioteca C, pero cuando trato de vincularlo todo, recibo errores de enlace que dicen que hay una definición múltiple de todas las funciones en línea, ambas que tengo llamado en mi biblioteca contenedora de C++ y los que no, básicamente todo lo definido en línea en los encabezados ha obtenido una función creada tanto en la biblioteca C como en la biblioteca C++.

No causa múltiples errores de definición cuando los archivos de inclusión se usan varias veces en diferentes archivos .c o .cpp en el mismo proyecto; el problema es solo que genera una definición por biblioteca.

¿Cómo/por qué el compilador genera funciones y símbolos para estas funciones en línea en ambas bibliotecas? ¿Cómo puedo obligarlo a dejar de generarlos en mi código? ¿Hay alguna herramienta que pueda ejecutar para quitar las funciones duplicadas del archivo .a, o una forma de hacer que el enlazador ignore múltiples definiciones?

(FYI, la biblioteca de terceros incluye guardias #ifdef __cplusplus y extern "C" en todos sus encabezados; de todos modos si ese fuera el problema, no causaría una definición múltiple de símbolo, causaría el problema opuesto porque el símbolo sería indefinido o al menos diferente).

Notablemente, los errores de enlace NO se producen si me enlace con la DLL de la biblioteca C de un tercero; sin embargo, luego recibo extraños errores de tiempo de ejecución que parecen tener que ver con mi código que tiene su propia versión de funciones que debería estar llamando desde la DLL. (Como si el compilador estuviera creando versiones locales de funciones que no solicité)

Se han formulado versiones similares de esta pregunta, sin embargo, no encontré la respuesta a mi situación en ninguna de estas:

la respuesta a esta pregunta fue que el cartel estaba definiendo las variables se multiplican , mi problema es múltiple definición de las funciones en línea: Repeated Multiple Definition Errors from including same header in multiple cpps

este fue un programa de MSVC, pero estoy utilizando MinGW; también, el problema del cartel en esta pregunta era la definición de un constructor de clase C++ fuera del cuerpo de clase en un encabezado, mientras que mi problema es con las funciones C en línea: Static Lib Multiple Definition Problem

Este tonto renombró todo su código C como C++ archivos y su código C no era C++ - segura: Multiple definition of lots of std:: functions when linking

Ésta fue sólo quieren saber por qué violar la regla de una definición no fue un error: unpredictable behavior of Inline functions with different definitions

+1

en cuenta que C99 semántica en línea son diferentes a la de C++: En C , si una de las declaraciones de función en línea se especifica explícitamente como 'extern', crea una definición externa, que ya no es una definición en línea. Estas definiciones externas no pueden aparecer varias veces en el programa. En C++, un 'extern' explícito en dicha función no tiene ningún efecto. Lo mejor es hacer 'static inline' en C. –

+0

Normalmente, las definiciones de funciones en línea están marcadas como símbolos 'débiles' y se supone que el enlazador elimina todos los duplicados. – Omnifarious

+0

Gracias Johannes: utilizando definiciones de preprocesador, los archivos de cabecera de la biblioteca C declaran estas funciones "en línea" si están en los archivos .c de la biblioteca C y "inline en línea" cuando están en mi proyecto. Pero no estoy seguro de por qué lo hicieron de esa manera. La biblioteca C tiene algún código que usa la dirección de una función como valor clave único. Como programador de C++ veo eso como una mala práctica y realmente rezo por que no usaron la dirección de una función en línea de esa manera. Creo que hay casi suficiente información entre estos comentarios para poner en una respuesta, aunque todavía no puedo compilar el programa. – Dennis

Respuesta

12

en primer lugar hay que entender el modelo de línea C99 - tal vez hay algo mal con sus encabezados. Hay dos tipos de definiciones para las funciones en línea con enlace externo (no estático)

  • definición externa
    Esta definición de una función sólo puede aparecer una vez en todo el programa, en un TU designado. Proporciona una función exportada que se puede usar desde otras TU.

  • definición Inline
    Estos aparecen en todos los TU, donde declaró como una definición por separado. Las definiciones do no deben ser idénticas entre sí o a la definición externa. Si se usa internamente en una biblioteca, pueden omitir la comprobación de los argumentos de la función que de otro modo se realizarían en la definición externa.

Cada definición de la función tiene sus propias variables estáticas locales, debido a que sus declaraciones locales tienen ninguna vinculación (no se comparten como en C++). Una definición de una función en línea no estático será una definición en línea si

  • Cada declaración de una función en un TU incluye el especificador inline y
  • Sin declaración de la función en un TU incluye el especificador extern.

De lo contrario, la definición que debe aparecer en esa TU (porque las funciones en línea se deben definir en la misma TU donde se declara) es una definición externa. En una llamada a una función en línea , no se especifica si se usa la definición externa o en línea. Sin embargo, dado que la función definida en todos los casos sigue siendo la misma (porque tiene un enlace externo), la dirección de la misma es igual en todos los casos, sin importar cuántas definiciones en línea aparecen. Entonces, si toma la dirección de la función, es probable que el compilador resuelva la definición externa (especialmente si las optimizaciones están deshabilitadas).

Un ejemplo que demuestra un uso incorrecto de inline, ya que incluye una definición externa de una función de dos veces en dos unidades de formación, causando un error múltiple definición

// included into two TUs 
void f(void); // no inline specifier 
inline void f(void) { } 

El siguiente programa es peligrosa, porque el compilador es libre de usar la definición externa, pero el programa no proporciona una

// main.c, only TU of the program 
inline void g(void) { 
    printf("inline definition\n"); 
} 

int main(void) { 
    g(); // could use external definition! 
} 

he hecho algunos casos de prueba utilizando GCC que demuestran el mecanismo adicional:

main.c

#include <stdio.h> 

inline void f(void); 

// inline definition of 'f' 
inline void f(void) { 
    printf("inline def main.c\n"); 
} 

// defined in TU of second inline definition 
void g(void); 

// defined in TU of external definition 
void h(void); 

int main(void) { 
    // unspecified whether external definition is used! 
    f(); 
    g(); 
    h(); 

    // will probably use external definition. But since we won't compare 
    // the address taken, the compiler can still use the inline definition. 
    // To prevent it, i tried and succeeded using "volatile". 
    void (*volatile fp)() = &f; 
    fp(); 
    return 0; 
} 

main1.c

#include <stdio.h> 

inline void f(void); 

// inline definition of 'f' 
inline void f(void) { 
    printf("inline def main1.c\n"); 
} 

void g(void) { 
    f(); 
} 

main2.c

#include <stdio.h> 

// external definition! 
extern inline void f(void); 

inline void f(void) { 
    printf("external def\n"); 
} 


void h(void) { 
    f(); // calls external def 
} 

Ahora, el programa produce lo que esperábamos.

$ gcc -std=c99 -O2 main.c main1.c main2.c 
inline def main.c 
inline def main1.c 
external def 
external def 

En cuanto a la tabla de símbolos, veremos que el símbolo de una definición en línea no se exporta (de main1.o), mientras que una definición externa se exporta (de main2.o).


Ahora, si sus bibliotecas estáticas tienen cada uno una definición externa de sus funciones en línea (como debe ser), es natural que en conflicto entre sí. La solución es hacer que las funciones en línea estén estáticas o simplemente para cambiarles el nombre. Estos siempre proporcionan definiciones externas (por lo que son completos definiciones de pleno derecho), pero no se exportan porque tienen enlace interno, por lo que no estén en contradicción

static inline void f(void) { 
    printf("i'm unique in every TU\n"); 
} 
+0

Esta es una muy buena respuesta. Desafortunadamente, todavía no he podido aplicar los conocimientos para compilar con éxito mi programa: los intentos de cambiar las líneas externas a líneas estáticas lo hacen fallar con el error de que está mezclando definiciones estáticas y no estáticas de la misma función. Tal vez pueda usar macros en mi propio código para cambiar los nombres de las funciones donde incluyo los encabezados, pero hay aproximadamente 100 de estos elementos en los encabezados ... – Dennis

+0

Para más información, esta discusión indica que el nivel de optimización del compilador puede afectar el problema: http://lkml.indiana.edu/hypermail/linux/kernel/0408.0/1787.html – Dennis

+0

Encontré algo similar al construir con MinGW gcc, y descubrí que los encabezados MinGW parecen ser incompatibles con -std = opción gnu99. Esto provocó una gran cantidad de símbolos definidos de forma múltiple cuando intenté vincular, ya que las definiciones de las funciones se colocaron en cada archivo de objeto creado de esta manera. –

Cuestiones relacionadas