Включение файла заголовка из статической библиотеки

Я делаю тестовую настройку статической библиотеки и программы C. Код библиотеки, расположенный в подкаталоге foo моего проекта, содержит следующие файлы:

foo / foo.c:

#include <stdio.h>
void foo(void) {
    printf("something");
}

foo / foo.h:

#ifndef foo_h__
#define foo_h__
extern void foo(void);
#endif

Мой программный код выглядит следующим образом:

test.c:

#include "foo.h"
int main() {
    foo();
    return 0;
}

У меня есть сценарий сборки под названием build, который содержит следующее:

сборка:

#!/bin/bash
gcc -c -Wall -Werror foo/foo.c
ar rcs libfoo.a foo.o
gcc -static -o test test.c libfoo.a # I have also tried -L. -lfoo

Но когда я запускаю сборку, появляется следующая ошибка:

test.c:1:17: fatal error: foo.h: No such file or directory
  #include "foo.h"
                  ^
Compilation terminated

Однако это работает, когда я опускаю строку #include, но я бы предпочел, чтобы я мог использовать файлы заголовков в своих статических библиотеках. Что я делаю не так и как это исправить?


person Ethan McTague    schedule 26.12.2014    source источник


Ответы (1)


Заголовки не хранятся в библиотеках. Заголовки хранятся отдельно от библиотек. Библиотеки содержат объектные файлы; заголовки не являются объектными файлами. По умолчанию стандартные заголовки в системе Unix хранятся в /usr/include - вы обычно найдете, например, /usr/include/stdio.h, /usr/include/string.h и /usr/include/stdlib.h. По умолчанию библиотеки хранятся в /usr/lib (но вы также можете найти некоторые в /lib). Часто компиляторы настроены так, чтобы искать и в других местах. Одно из распространенных альтернативных расположений - под /usr/local, поэтому /usr/local/include для заголовков и /usr/local/lib для библиотек. Также обратите внимание, что в одной библиотеке может быть много заголовков, определяющих службы. Библиотека по умолчанию является примером. Он имеет функции, соответствующие функциям <stdio.h>, <string.h>, <stdlib.h>, а также многих других заголовков.

Глядя на ваш код:

  1. Если ваш заголовочный файл находится в ./foo/foo.h, вам нужно написать:

    #include "foo/foo.h"
    

    Или, если вы продолжите использовать #include "foo.h", вам нужно указать, где найти заголовок в командной строке компилятора с аргументом:

    gcc -Ifoo -o test test.c -L. -lfoo
    

    Я сознательно исключил -static; это необходимо только тогда, когда есть выбор между статической и общей библиотекой, но у вас есть только libfoo.a, поэтому компоновщик все равно будет использовать это.

    Обратите внимание, что проблема связана с ошибкой компиляции, а не с ошибкой связывания. Это будет яснее, если вы разделите построение программы на два этапа: (1) создайте test.o и (2) скомпонуйте программу:

    gcc -c -Ifoo test.c
    gcc -o test test.o -L. -lfoo
    
  2. Ваша защита жатки неисправна. Изначально у вас были (но вы обновили вопрос, поэтому этой опечатки больше нет):

    #ifndef foo_h__
    #define foo_h_
    

    Тебе нужно:

    #ifndef foo_h__
    #define foo_h__
    

    Имена макросов в обеих строках должны совпадать. Обратите внимание, что в этом случае орфографические ошибки в основном безвредны - но в Mac OS X clang (маскируясь под gcc) действительно предупреждал об этом (хотя я заметил это до того, как сделал какую-либо компиляцию). В некоторых других случаях вы не получите защиты, которую обеспечивают защитные ограждения жатки.

    ./foo/foo.h:1:9: warning: 'foo_h__' is used as a header guard here, followed by #define of a
          different macro [-Wheader-guard]
    #ifndef foo_h__
            ^~~~~~~
    ./foo/foo.h:2:9: note: 'foo_h_' is defined here; did you mean 'foo_h__'?
    #define foo_h_
            ^~~~~~
            foo_h__
    1 warning generated.
    

Вы можете справедливо задаться вопросом:

  • Если мне нужно -Ifoo при компиляции test.c, почему в этом не было необходимости при компиляции foo/foo.c?

Хороший вопрос!

  1. Не помешало бы составление foo/foo.c
  2. GCC ищет заголовки в каталоге, где находится исходный код единицы перевода (поэтому при компиляции foo/foo.c он все равно ищет в каталоге foo заголовки, включенные как #include "foo.h".
  3. Исходный файл foo/foo.c тоже должен был включать foo.h; очень важно, чтобы это происходило именно так, как компилятор обеспечивает перекрестную проверку, необходимую для обеспечения согласованности. Если бы вы написали #include "foo.h", компиляция работала бы, как описано. Если бы вы написали (на foo/foo.c) #include "foo/foo.h", то командной строке для создания foo.o потребовалось бы -I., чтобы можно было найти заголовок.
person Jonathan Leffler    schedule 26.12.2014
comment
Но заголовок foo.h - это часть библиотеки libfoo.a, не так ли? Кроме того, проблема с защитой заголовка была опечаткой, когда я перепечатал ее в stackoverflow, что я и сделал, потому что нахожусь в Windows SSHing в системе ubuntu. - person Ethan McTague; 26.12.2014
comment
Нет. Библиотека содержит только объектные файлы. Заголовки - это не объектные файлы. Заголовки не содержатся в библиотеках. (Хорошо: чтобы быть предельно педантично точным, вы можете добавить файл заголовка в библиотеку, но компилятор никогда не будет смотреть в библиотеку - только компоновщик (обычно ld) просматривает библиотеки, и он ищет только объектные файлы, а не заголовки). - person Jonathan Leffler; 26.12.2014
comment
Ох, хорошо. Спасибо, что разъяснили это. Но тогда почему, например, я могу включить, например, ncurses.h из библиотеки ncurses? - person Ethan McTague; 26.12.2014
comment
Вы можете написать #include <ncurses.h>, потому что заголовок установлен в стандартном месте /usr/include, в котором компилятор все равно ищет заголовки. И вы можете написать -lncurses, потому что библиотека находится в стандартном месте, которое компоновщик все равно ищет, обычно /usr/lib. Или это могут быть /usr/local/include и /usr/local/lib. Но заголовки не хранятся в библиотеке; они хранятся независимо от библиотеки. Вы можете установить библиотеки без заголовков. После этого вы не сможете скомпилировать код, использующий заголовок, но можете запустить код, использующий общую библиотеку. - person Jonathan Leffler; 26.12.2014
comment
Что делать, если у меня есть h-файл a.h, а мой файл C вызывает его, используя относительный путь: $include "../../SomeFolderA/SomeFolderB/a.h. Теперь заголовочный файл a.h включает h-файл с именем b.h с использованием SomeFolderC\b.h. Будет ли GCC анализировать его правильно или все пути разрешаются относительно пути к файлу C? - person Royi; 12.10.2017
comment
@Royi: постарайтесь избегать этой нотации (подробнее см. Каковы преимущества относительного пути, такого как ../include/header.h для заголовка? информации. Обратите внимание, что одна из проблем заключается в том, что интерпретация не обязательно согласована между компиляторами, хотя POSIX действительно определяет поведение более строго, и GCC более или менее придерживается этого. AFAICR, GCC сначала будет искать b.h в каталоге, где он найдено a.h, и после этого будет искать в каталогах, указывает, где находится исходный файл, а также параметры -I и путь поиска по умолчанию. - person Jonathan Leffler; 12.10.2017
comment
POSIX говорит для команды c99: -I directory - Измените алгоритм поиска заголовков, имена которых не являются абсолютными путями, для поиска в каталоге, названном по имени пути к каталогу, перед поиском в обычных местах. Таким образом, заголовки, имена которых заключены в двойные кавычки (""), нужно искать сначала в каталоге файла со строкой #include, затем в каталогах, указанных в параметрах -I, и, наконец, в обычных местах. [ … Продолжение…] - person Jonathan Leffler; 12.10.2017
comment
[… Продолжение…] _ Для заголовков, имена которых заключены в угловые скобки (<>), заголовок следует искать только в каталогах, указанных в параметрах -I, а затем в обычных местах. Каталоги, указанные в параметрах -I, должны просматриваться в указанном порядке. Если опция -I используется для указания каталога, который является одним из обычных мест, в которых выполняется поиск по умолчанию, результаты не указаны. Реализации должны поддерживать не менее десяти экземпляров этой опции за один c99 вызов команды._ - person Jonathan Leffler; 12.10.2017
comment
Вопрос в том, если в файле используется #include "SomePathHere", всегда ли путь будет разрешен относительно этого файла? В Windows кажется, что у GCC иногда возникают проблемы с этим, если в пути много ../../... - person Royi; 12.10.2017
comment
По-разному. Это зависит от компилятора, а значит, и от платформы. В целом избегайте относительных путей, включающих .. элементы, и избегайте абсолютных путей. Относительные имена, такие как sys/types.h, допустимы. При необходимости укажите каталог с помощью параметров -I. - person Jonathan Leffler; 12.10.2017
comment
Что значит -I? Что значит -L.? Что поставить за -l для связывания .so файла? - person Aaron Franke; 07.08.2020
comment
@AaronFranke: -I /some/directory указывает компилятору искать файлы заголовков в указанном каталоге перед поиском в стандартных местах. Параметр -L . означает поиск библиотек (.a или .so) в указанном каталоге (., текущий каталог) перед поиском в стандартных расположениях. Параметр -l name ищет либо общую библиотеку libname.so, либо статическую библиотеку libname.a в каталогах, указанных параметрами -L, и в стандартных расположениях. Если и libname.a, и libname.so существуют в одном каталоге, компоновщик по умолчанию выбирает libname.so. - person Jonathan Leffler; 07.08.2020
comment
Значит, с -l он автоматически добавляет lib и автоматически добавляет .so? Моя библиотека называется libgit2.so.1.0.1. Если я поставлю -l git2, разве это не будет искать libgit2.so, а не libgit2.so.1.0.1? Как я могу указать ему, что нужно искать имя файла? - person Aaron Franke; 09.08.2020
comment
Да, искал бы libgit2.so. Вы заставляете его работать, создавая символическую ссылку libgit2.so, указывающую на libgit2.so.1.0.1. Если вы устанавливаете пакет «для разработки» для библиотеки, это одно из действий пакета. Загляните в /usr/local/lib, и вы должны найти .so файлы, которые являются символическими ссылками на файлы с контролем версий. - person Jonathan Leffler; 09.08.2020