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

时间:2017-08-14 23:16:53

标签: c compilation clang

我有两个目录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.c sorting内定义。以下编译成功生成可执行文件:

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<directory>
      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 s:

#include <stdio.h>          
#include <stdlib.h>         

#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)

1 个答案:

答案 0 :(得分:3)

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

逐个给编译器一个源文件。例如:

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

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

  • 编译器在源文件上运行预处理器。除此之外,每次看到#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-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的符号(数据或代码),但却找不到它。那是因为该符号在某处被声明(否则使用它的源将不会编译),但它的定义不可用。没有源文件实现它,或者包含它的目标文件没有提供给链接器。

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

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