从当前目录以外的目录中包含C代码的正确方法

我有两个目录, sortingsearching (同一目录的孩子),有.c源文件和.h头文件:

 mbp:c $ ls sorting array_tools.c bubble_sort.c insertion_sort.c main selection_sort.c array_tools.h bubble_sort.h insertion_sort.h main.c selection_sort.h mbp:c $ ls searching array_tools.c array_tools.h binary_search.c binary_search.h linear_search.c linear_search.h main main.c 

searching ,我正在构建一个需要使用insertion_sort函数的可执行文件,在insertion_sort.h声明并在insertion_sort.h定义。 以下编译成功生成可执行文件:

 mbp:searching $ clang -Wall -pedantic -g -iquote"../sorting" -o main main.c array_tools.c binary_search.c linear_search.c ../sorting/insertion_sort.c 

但是,我希望能够通过使用#include包含标头然后为编译器提供搜索路径来包含来自任意目录的函数。 我是否需要预先将.c文件预编译为.o文件? clang的man页列出了以下选项:

 -I Add the specified directory to the search path for include files. 

但是下面的编译失败了:

 mbp:searching $ clang -Wall -pedantic -g -I../sorting -o main main.c array_tools.c binary_search.c linear_search.c Undefined symbols for architecture x86_64: "_insertion_sort", referenced from: _main in main-1a1af0.o ld: symbol(s) not found for architecture x86_64 clang: error: linker command failed with exit code 1 (use -v to see invocation) 

main.c有以下几个include

 #include  #include  #include "linear_search.h" #include "binary_search.h" #include "array_tools.h" #include "insertion_sort.h" 

我不明白头文件,源文件和目标文件之间的联系。 要包含.c文件中定义的函数,只要.c文件与标题位于同一目录中,是否足以包含同名头文件? 我在SO上阅读了多个答案,clang的man页和一些教程,但无法找到明确,明确的答案。


回应@ spectras

您可以逐个为编译器提供源文件。 例如:

 cc -Wall -Ipath/to/some/headers foo.c -o foo.o 

运行

 mbp:sorting $ clang -Wall insertion_sort.c -o insertion_sort.o 

产生以下错误:

 Undefined symbols for architecture x86_64: "_main", referenced from: implicit entry/start for main executable ld: symbol(s) not found for architecture x86_64 clang: error: linker command failed with exit code 1 (use -v to see invocation) 

好吧,它有点混淆了。 让我们看看一个人通常如何编译一个简单的多文件项目。

您可以逐个为编译器提供源文件。 例如:

 cc -c -Wall -Ipath/to/some/headers foo.c -o foo.o 

-c标志告诉编译器您想要一个目标文件,因此它不应该运行链接器。

  • 编译器在源文件上运行预处理器。 除此之外,每次看到#include指令时,它都会搜索命名文件的include路径,并基本上复制粘贴它,将#include替换为内容。 这是递归完成的。

    这是您包含的所有.h合并到源文件中的步骤。 我们把整个事情称为翻译单位。

    您可以使用-E标志查看此步骤的结果并检查结果,例如:

     cc -Wall -Ipath/to/some/headers foo.c -E -o foo.test 
  • 让我们简短一点,因为其他步骤与您的问题无关。 然后,编译器从结果源代码创建目标文件。 目标文件包含翻译单元中所有代码和数据的二进制版本,以及用于将所有内容放在一起的元数据以及其他一些内容(如调试信息)。

    您可以使用objdump -xd foo.o检查目标文件的内容。

请注意,由于这是针对每个源文件执行的,这意味着标题会一次又一次地被解析和编译。 这就是他们应该只声明东西并且不包含实际代码的原因:你最终会在每个目标文件中使用该代码。

完成后,将所有目标文件链接到可执行文件中,例如:

 cc foo.o bar.o baz.o -o myprogram 

此步骤将收集所有内容,解决依赖关系并将所有内容写入可执行二进制文件。 您也可以使用-l -lrt外部目标文件,就像执行-lrt-lm

例如:

  • foo.c包括bar.h
  • bar.h包含函数do_bar的声明: void do_bar(int);
  • foo.c可以使用它,编译器会正确生成foo.o
  • foo.o将有占位符和它需要的信息do_bar
  • bar.c定义了do_bar的实现。
  • 所以bar.o将有信息“嘿,如果有人需要do_bar ,我在这里得到它”。
  • 链接步骤将用实际调用do_bar替换占位符。

最后,当您将多个.c文件传递给编译时就像在您的问题中一样,编译器基本上做同样的事情,只是它不会生成中间对象文件。 总体过程表现相同。

那么,你的错误呢?

 Undefined symbols for architecture x86_64: "_insertion_sort", referenced from: _main in main-1a1af0.o ld: symbol(s) not found for architecture x86_64 clang: error: linker command failed with exit code 1 (use -v to see invocation) 

看到? 它说连接步骤失败了。 这意味着上一步进展顺利。 #include工作了。 它只是在链接步骤中,它正在寻找一个名为_insertion_sort的符号(数据或代码),但却找不到它。 那是因为该符号在某处被声明(否则使用它的源将不会被编译),但它的定义不可用。 没有源文件实现它,或者没有为链接器提供包含它的目标文件。

=>您需要使_insertion_sort的定义可用。 通过将../sorting/insertion_sort.c添加到您传递的源列表中,或者将其编译为目标文件并传递它。 或者将它构建到一个库中,这样它就可以被你的两个二进制文件共享(否则它们每个都会嵌入一个副本)。

当你到达那里时,通常开始使用像CMake这样的构建工具CMake是一个好主意。 它将为您处理所有细节。