Множественное определение встроенных функций при связывании статических библиотек

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

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

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

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

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

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

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

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

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

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

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

Ответ 1

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

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

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

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

  • Каждое объявление функции в 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");
}