Множественное определение встроенных функций при связывании статических библиотек - PullRequest
11 голосов
/ 07 февраля 2010

У меня есть программа на C ++, которую я компилирую с помощью mingw (gcc для Windows). Использование TDM-релиза mingw, включающего gcc 4.4.1. Исполняемый файл ссылается на два файла статической библиотеки (.a): один из них - сторонняя библиотека, написанная на C; другая, написанная мной, библиотека C ++, использующая библиотеку C, обеспечивает мой собственный C ++ API сверху.

(На мой взгляд, чрезмерная) часть функциональности библиотеки C реализована во встроенных функциях. Вы не можете избежать включения встроенных функций, когда используете API библиотеки C, но когда я пытаюсь связать все это вместе, я получаю ошибки ссылки, говорящие о множественном определении всех встроенных функций - обе у меня есть Вызванный в моей библиотеке-оболочке C ++, и в тех, которые у меня нет, в основном, что-то определенное в заголовках inline, получило функцию, созданную для него как в библиотеке C, так и в библиотеке C ++.

Не вызывает многократных ошибок определения, когда включаемые файлы используются несколько раз в разных файлах .c или .cpp в одном и том же проекте; проблема только в том, что она генерирует одно определение для библиотеки.

Как / почему компилятор генерирует функции и символы для этих встроенных функций в обеих библиотеках? Как я могу заставить это прекратить генерировать их в моем коде? Можно ли запустить инструмент для удаления дублирующихся функций из файла .a или способ заставить компоновщик игнорировать несколько определений?

(К вашему сведению, сторонняя библиотека включает в себя защиту #ifdef __cplusplus и extern "C" во всех своих заголовках; в любом случае, если бы это было проблемой, это не вызвало бы множественное определение символа, это вызвало бы противоположную проблему, потому что символ будет неопределенным или, по крайней мере, другим.)

Примечательно, что ошибки связи НЕ возникают, если я ссылаюсь на стороннюю библиотеку C библиотеки; однако затем я получаю странные сбои во время выполнения, которые, похоже, связаны с моим кодом, имеющим собственную версию функций, которые он должен вызывать из DLL. (Как будто компилятор создает локальные версии функций, которые я не просил.)

Подобные версии этого вопроса задавались ранее, однако я не нашел ответа на мою ситуацию ни в одном из них:

Ответ на этот вопрос состоял в том, что в плакате было многократное определение переменных , моя проблема - множественное определение встроенных функций: Повторные ошибки множественного определения из-за включения одного и того же заголовка в несколько cpps

Это была программа MSVC, но я использую mingw; Кроме того, проблемой автора в этом вопросе было определение конструктора класса C ++ вне тела класса в заголовке, в то время как моя проблема с встроенными функциями C: Задача множественного определения Static Lib

Этот дурак переименовал весь свой код C в файлы C ++, и его код C не был безопасным для C ++: Множественное определение множества функций std :: при связывании

Этот просто хотел знать, почему нарушение одного правила определения не было ошибкой: непредсказуемое поведение встроенных функций с разными определениями

1 Ответ

14 голосов
/ 07 февраля 2010

Сначала вы должны понять встроенную модель C99 - возможно, что-то не так с вашими заголовками. Существует два вида определений для встроенных функций с внешней (нестатической) связью

  • Внешнее определение
    Это определение функции может появиться только один раз во всей программе, в назначенном TU. Он предоставляет экспортированную функцию, которую можно использовать из других TU.

  • Встроенное определение
    Они появляются в каждом TU, где объявлены как отдельное определение. Определения не должны быть идентичны друг другу или внешнему определению. При использовании внутреннего в библиотеке они могут не проверять аргументы функции, которые в противном случае выполнялись бы во внешнем определении.

Каждое определение функции имеет свои собственные локальные статические переменные , потому что их локальные объявления не имеют связи (они не разделяются, как в C ++). Определение нестатической встроенной функции будет встроенным определением, если

  • Каждое объявление функции в TU включает спецификатор inline и
  • Нет объявления функции в TU, включая спецификатор extern.

В противном случае определение, которое должно появиться в этом TU (потому что встроенные функции должны быть определены в том же TU, где объявлено), является внешним определением. При вызове встроенной функции не указано, используется ли внешнее или встроенное определение . Однако, поскольку функция, определенная во всех случаях, остается одинаковой (поскольку она имеет внешнюю связь), ее адрес сравнивается одинаково во всех случаях, независимо от того, сколько появляется встроенных определений. Поэтому, если вы берете адрес функции, скорее всего, компилятор преобразуется во внешнее определение (особенно если оптимизации отключены).

Пример, демонстрирующий неправильное использование inline, поскольку он включает внешнее определение функции дважды в двух TU, что вызывает ошибку множественного определения

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

Следующая программа опасна, поскольку компилятор может свободно использовать внешнее определение, но программа не предоставляет его

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

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

Я сделал несколько тестовых случаев с использованием GCC, которые дополнительно демонстрируют механизм:

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
}

Теперь программа выводит то, что мы ожидали!

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

Глядя на таблицу символов, мы увидим, что символ встроенного определения не экспортируется (из main1.o), а внешнее определение экспортируется (из main2.o).


Теперь, если каждая из ваших статических библиотек имеет внешнее определение своих встроенных функций (как и должно быть), они естественным образом конфликтуют друг с другом. Решение состоит в том, чтобы сделать встроенные функции статическими или просто переименовать их. Они всегда предоставляют внешние определения (поэтому они являются полноценными определениями), но они не экспортируются, поскольку имеют внутреннюю связь, поэтому не конфликтуют

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