Несколько библиотек определений и только заголовков

У меня есть программа на C с несколькими файлами c и h. Я решил сделать одну часть программы "только для заголовков", поэтому переместил код с c на h. Теперь я получаю множественные проблемы с определением, и я понятия не имею, почему. например:

main.c includes utils.h
vector.c includes utils.h

Я переместил все в utils.c в utils.h (и, конечно, удалил utils.c из проекта). utils.h начинается с

#ifndef UTILS_H_
#define UTILS_H_

// and end with:
#endif

Чтобы убедиться, что моя охрана была уникальной, я попытался изменить ее (например: UTILS718171_H_), но она не работает.

Тем не менее, компилятор жалуется:

/tmp/ccOE6i1l.o: In function `compare_int':
ivector.c:(.text+0x0): multiple definition of `compare_int'
/tmp/ccwjCVGi.o:main.c:(.text+0x660): first defined here
/tmp/ccOE6i1l.o: In function `compare_int2':
ivector.c:(.text+0x20): multiple definition of `compare_int2'
/tmp/ccwjCVGi.o:main.c:(.text+0x6e0): first defined here
/tmp/ccOE6i1l.o: In function `matrix_alloc':
ivector.c:(.text+0x40): multiple definition of `matrix_alloc'
/tmp/ccwjCVGi.o:main.c:(.text+0x0): first defined here
...

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

4 ответа

Решение

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

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

Вы упускаете точку конструкции #ifndef _FOO_H / #define _FOO_H / #endif. Это защищает только от нескольких включений в одном файле. Например, они защищают от этого:

foo.h:

  #ifndef _FOO_H 
  #define _FOO_H

  /* some C stuff here */

  #endif /* _FOO_H */

foo.c:

   #include <foo.h>
   #include <bar.h>
   ...

bar.h:

   #include <foo.h>
   ...

обратите внимание, что foo.c и bar.h оба включают foo.h; здесь #ifdef _FOO_H / #define _FOO_H / #endif защищает от этого двойного включения в foo.c (foo.h, включенный в bar.h, не переопределяет вещи)

Теперь следующая часть.

Зачем вам перемещать реализацию функции из utils.c в utils.h? Кроме того, почему вы решили сделать его "только для заголовков"?

Вы можете, в зависимости от того, поддерживает ли ваш компилятор static inline функции делают это; но даже тогда это НЕ рекомендуется, так как более чем вероятно, это сделает вашу программу излишне раздутой, потому что скорее всего ваши утилиты довольно сложны и не могут быть встроены в любом случае.

Так что измените его обратно на utils.c и utils.h construct, который у вас был до этого, который, я полагаю, БЫЛ работающим и наслаждающимся программным обеспечением.

Если вы не заинтересованы в utils.h функции, которые нужно "скопировать" в каждое место, где он используется, просто используйте static функции в шапке. (static inline в C99)

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

Вы просто не можете сделать что-то вроде этого:

// header.h
void foo() { }

// file1.c
#include "header.h"

// file2.c
#include "header.h"

Единственное реальное решение - объявить void foo(); в заголовке и определите его только один раз (обычно в выделенном foo.c). То же самое касается глобальных переменных, которые должны быть объявлены как extern в заголовке и определяется в исходном файле.

Включать охранников тут ни при чем. Они служат только для предотвращения рекурсивного самостоятельного включения или избыточного множественного включения в одном блоке перевода.

Другие вопросы по тегам