Библиотека C с одним заголовком вызывает ошибки компоновки. Как я могу изменить структуру проекта, чтобы исправить это?

В настоящее время я использую small3dlib с SDL, чтобы попытаться создать простую 3D-игру.

Я включил библиотеку в свой проект C, просто поместив заголовочный файл в исходный каталог, и создал два файла, gfx.c и gfx.h, которые служат для управления графикой для всего проекта.

Моя текущая структура проекта выглядит так:

tsa-3d/
| bin/
| build/
| src/
|-- gfx.c
|-- main.c
|-- include/
|---- gfx.h
|---- logger.h
|---- loggerconf.h
|---- models.h
|---- small3dlib.h
|---- tgc.h
|-- lib/
|---- logger.c
|---- loggerconf.c
|---- tgc.c
|-- models/
|---- carModel.c
|---- cityModel.c
|---- cityTexture.c

Из них только файлы, включающие small3dlib (косвенно), это main.c, gfx.c/.h, models.h и все модели.

Библиотека требует, чтобы несколько макросов были определены перед включением, поэтому мне нужно каждый раз вызывать заголовок моего графического интерфейса gfx.h, а не small3dlib.h.

#ifndef GFX_H
#define GFX_H

#define S3L_FLAT 0
#define S3L_NEAR_CROSS_STRATEGY 3
#define S3L_PERSPECTIVE_CORRECTION 2
#define S3L_SORT 0
#define S3L_STENCIL_BUFFER 0
#define S3L_Z_BUFFER 2

#define S3L_PIXEL_FUNCTION draw_pixel

#define S3L_RESOLUTION_X 640
#define S3L_RESOLUTION_Y 480

#define TEXTURE_W 256
#define TEXTURE_H 256

#define WINDOW_TITLE "hello, world"

#include <SDL2/SDL.h>
#include <stdint.h>
#include "small3dlib.h"
#include "models.h"

...

В результате этого макета у меня есть следующая ошибка связывания для каждой функции, определенной в small3dlib.h:

/usr/bin/ld: src/main.o:(.bss+0x0): multiple definition of `S3L_zBuffer'; src/gfx.o:(.bss+0x0): first defined here
/usr/bin/ld: src/main.o: in function `S3L_zBufferRead':
main.c:(.text+0x7d): multiple definition of `S3L_zBufferRead'; src/gfx.o:gfx.c:(.text+0x7d): first defined here
/usr/bin/ld: src/main.o: in function `S3L_zBufferWrite':
main.c:(.text+0xb9): multiple definition of `S3L_zBufferWrite'; src/gfx.o:gfx.c:(.text+0xb9): first defined here
/usr/bin/ld: src/main.o: in function `S3L_mat4Copy':
...

У меня была такая же проблема и для всех моделей (все они были получены из проекта и изначально имели определения переменных/функций в заголовках, пока я не разделил их только на файлы .c и один общий заголовок.

Я огляделся столько, сколько мог, и я не уверен, что мне нужно сделать, чтобы исправить это. Я знаю, что определения переменных и функций не должны помещаться в файлы заголовков, но вся эта библиотека построена на том, что все определения находятся в одном заголовке. Все примеры программ для этой библиотеки имеют small3dlib.h, включенный только в один файл .c, так что в таком случае, как эта библиотека предназначена для использования, когда она включена в несколько файлов?

Полный код проекта на данный момент можно найти здесь . Это попытка адаптации примера города в репозитории библиотеки.

Я попытался разделить библиотеку на заголовочный файл и исходный файл, чтобы все функции теперь просто прототипировались в заголовке. Этот вариант не удался, потому что сам заголовок невероятно велик, а преобразование каждого определения функции в объявление и знание того, какие функции нужно преобразовать, а какие нет, утомительно и утомительно для 2800 строк C. После попытки сделать это около получаса , я предположил, что, скорее всего, просто делаю что-то не так, пытаясь связать библиотеку, и что библиотека, скорее всего, не виновата в этой проблеме.

Обновлено: это минимальный воспроизводимый пример этого:

Вот пример того, что здесь происходит:

Файловая структура:

project/
| Makefile
| src/
|-- main.c
|-- foo.c
|-- include/
|---- something.h
|---- lib.h

Makefile

# Global

SRC := $(wildcard src/*.c) \
       $(wildcard src/**/*.c) \
       $(wildcard src/**/**/*.c) \
       $(wildcard src/**/**/**/*.c)

OUT := program

# Default Build

EXE := ./build/${OUT}
CFLAGS := -I./src/include
LIBFLAGS := -lSDL2 -ldl
OBJ := ${SRC:.c=.o}

build: clean-obj dir ${OBJ}
    gcc ${OBJ} -o ${EXE} ${CFLAGS} ${LIBFLAGS}
    ${call clean-obj}

%.o: %.c
    gcc -c $< -o $@ ${CFLAGS}

run: build
    ./bin/${OUT}

# Utility

dir:
    mkdir -p ./bin ./build/

clean-obj:
    rm -f ${OBJ} ./bin/*

clean: clean-obj
    rm -f ${EXE}

Lib.h -> представление small3dlib.h

#ifndef LIB_H
#define LIB_H

#include <stdint.h>
#include <stdio.h>

#ifndef SOME_MACRO
    #error SOME_MACRO NOT DEFINED!
#endif

#define ARRAY_LENGTH 3
const uint8_t array[ARRAY_LENGTH] = { 1, 2, 3 };

void hello(uint8_t x) {
    for (uint8_t i = 0; i < x; i++)
        printf("%d", i);
}

#endif

Что-то.ч

#ifndef SOMETHING_H
#define SOMETHING_H

#define SOME_MACRO 3
#include "lib.h"

void bar();

#endif

Main.c

#include "include/something.h"

int main(int argc, char* argv[]) {
    hello(array[2]);
    bar();
}

Foo.c

#include "include/something.h"

void bar() {
    hello(8);
}

Это приводит к:

...
/usr/bin/ld: src/main.o:(.rodata+0x0): multiple definition of `array'; src/foo.o:(.rodata+0x0): first defined here
/usr/bin/ld: src/main.o: in function `hello':
main.c:(.text+0x0): multiple definition of `hello'; src/foo.o:foo.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
make: *** [Makefile:18: build] Error 1

Добро пожаловать в StackOverflow! Пожалуйста, посетите тур , чтобы узнать, как работает этот сайт. Это не форум, поэтому, пожалуйста, добавьте пояснения, отредактировав свой вопрос. Вы также можете прочитать « Как спросить ». -- По-видимому, эта библиотека только для заголовков не может быть включена в несколько единиц перевода приложения. Вот что показывает ошибка. Самое простое решение — включить его только в одну единицу перевода и предоставить функции «обертки» для всех остальных модулей. Вы рассматривали и пробовали это? -- Обычно мы ожидаем, что минимальный воспроизводимый пример сможет воспроизвести вашу проблему. Пожалуйста, рассмотрите возможность добавления одного.

the busybee 16.02.2023 08:17

Не удается получить доступ ко всему коду. Пожалуйста, предоставьте минимальный воспроизводимый пример.

kiner_shah 16.02.2023 08:57

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

the busybee 16.02.2023 16:13

Хорошо. Спасибо за совет. Я новичок в C, поэтому любая помощь, которую я могу получить, полезна! Я собираюсь прочитать о единицах перевода и о том, как ими манипулировать, и посмотреть, смогу ли я двигаться дальше. Если вы хотите, могу ли я получить простой пример того, как это реализовать? Спасибо!

nickel 16.02.2023 16:32
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Веб-скрейпинг, как мы все знаем, это дисциплина, которая развивается с течением времени. Появляются все более сложные средства борьбы с ботами, а...
Калькулятор CGPA 12 для семестра
Калькулятор CGPA 12 для семестра
Чтобы запустить этот код и рассчитать CGPA, необходимо сохранить код как HTML-файл, а затем открыть его в веб-браузере. Для этого выполните следующие...
ONLBest Online HTML CSS JAVASCRIPT Training In INDIA 2023
ONLBest Online HTML CSS JAVASCRIPT Training In INDIA 2023
О тренинге HTML JavaScript :HTML (язык гипертекстовой разметки) и CSS (каскадные таблицы стилей) - две основные технологии для создания веб-страниц....
Как собрать/развернуть часть вашего приложения Angular
Как собрать/развернуть часть вашего приложения Angular
Вам когда-нибудь требовалось собрать/развернуть только часть вашего приложения Angular или, возможно, скрыть некоторые маршруты в определенных средах?
Запуск PHP на IIS без использования программы установки веб-платформы
Запуск PHP на IIS без использования программы установки веб-платформы
Установщик веб-платформы, предлагаемый компанией Microsoft, перестанет работать 31 декабря 2022 года. Его закрытие привело к тому, что мы не можем...
Оптимизация React Context шаг за шагом в 4 примерах
Оптимизация React Context шаг за шагом в 4 примерах
При использовании компонентов React в сочетании с Context вы можете оптимизировать рендеринг, обернув ваш компонент React в React.memo сразу после...
0
4
53
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Библиотеки только для файлов заголовков... хм... с ними не так просто работать. Вы не можете включать их более чем в одну единицу перевода (источник vulgo). В противном случае у вас будет столько же определений, сколько единиц перевода, которые его включили, и, как следствие, ошибки сборки. Кроме того, существует потенциальное несоответствие в их контроле #define.

Поэтому, как и в вашем примере, вам нужно написать реализацию, обеспечивающую единую единицу перевода. Я назвал его "lib_impl.c". Чтобы централизовать необходимые определения, я написал отдельный заголовочный файл с именем «lib_defs.h»:

#ifndef LIB_DEFS_H
#define LIB_DEFS_H

#define SOME_MACRO 3

#endif

К сожалению, вам нужно написать отдельный заголовочный файл, содержащий все объявления объектов, которые вы используете в своем приложении. Вам не нужно объявлять все возможные объекты библиотеки. Я назвал его «lib_decl.h»:

#ifndef LIB_DECL_H
#define LIB_DECL_H

#include <stdint.h>

extern const uint8_t array[];

void hello(uint8_t x);

#endif

Единственный файл реализации:

// lib_impl.c

#include "lib_defs.h"
#include "lib_decl.h"

// Actually, this is a source file, not a header file!
#include "lib.h"

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

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

В вашем примере вы, кажется, используете какой-то «глобальный» включаемый файл «something.h». По-видимому, он собирает объявления нескольких модулей:

#ifndef SOMETHING_H
#define SOMETHING_H

#include "lib_decl.h"
 
void bar();

#endif

Все остальные файлы без изменений, и приложение собирается без проблем.


Примечание 1. Поскольку вы предоставляете компилятору путь к включаемым файлам, вам не нужно использовать «включать» в директивах #include.

Примечание 2. При удалении всех объектных файлов, как правило, промежуточных файлов, вы отказываетесь от основной функции make: запускайте только необходимые команды для создания зависимых целевых файлов.


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

  1. Пользователю необходимо #define все функции, которые он хочет использовать.
  2. Если библиотека становится больше, она становится раздутой частью исходного кода без какой-либо организации на файловом уровне.
  3. Пользователям настоятельно рекомендуется включать его только в одну единицу перевода.

Это фактически устраняет разовые усилия автора библиотеки и передает их каждому пользователю, умножая усилия на количество использований. :-( Судите сами.

Большое спасибо за помощь. Это было очень полезно и информативно, но я просто не могу понять, как применить этот совет к моему проекту. (Я все еще продолжаю сталкиваться с ошибками и т. д. и т. д.) Скорее всего, я просто попытаюсь не использовать эту библиотеку, так как, судя по ее звукам, это пустая трата времени и результат безответственного дизайна библиотеки.

nickel 17.02.2023 07:07

Ну, если у вас такой уровень опыта, было бы хорошим решением отказаться от этой библиотеки. Тем не менее, не стесняйтесь публиковать еще один вопрос с новыми проблемами, если вы стремитесь подняться по крутой кривой обучения. Просто убедитесь, что вы приложили к этому достаточно усилий, так как это сайт QnA, а не обучающий форум. ;-) - Возможно, вы захотите пометить этот ответ как «принятый», чтобы будущие посетители могли его использовать.

the busybee 17.02.2023 07:44

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