【问题标题】:Proper way to include C code from directories other than the current directory从当前目录以外的目录中包含 C 代码的正确方法
【发布时间】:2017-08-14 23:16:53
【问题描述】:

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

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 有以下includes:

#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、man 页面和许多教程上阅读了 SO 上的多个答案,但无法找到明确、明确的答案。


回应@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)

【问题讨论】:

  • 你有两个main.c,想制作一个程序?
  • @aschepler 我有两个main.c,但它们位于不同的目录中。我不打算在编译中使用另一个main.c
  • @aschepler 澄清一下,编译是从searching 目录运行的。
  • 创建一个库。安装它。使用它。
  • 您可以创建一个库(从长远来看通常是最简单的)或在链接命令行上列出要链接的各个目标文件。

标签: c compilation clang


【解决方案1】:

好的,有点混乱。让我们看看如何编译一个简单的多文件项目。

一个接一个,你给编译器一个源文件来处理。例如:

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 的符号(数据或代码),但没有找到它。那是因为该符号是在某处声明的(否则使用它的源不会编译),但它的定义不可用。要么没有源文件实现它,要么包含它的目标文件没有提供给链接器。

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

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

【讨论】:

  • 如果我遗漏了什么,请原谅,但是在单个源文件(没有main)上运行目标文件创建会产生错误。有关详细信息,请参阅我的问题的结尾。
  • @AndreyPortnoy> 抱歉,我的错,我忘记了-c。这是告诉编译器不要尝试链接的标志。固定。
  • 该标准使用术语“翻译单元”来表示您所说的“编译单元”。在标准 §5.1.1.1 程序结构中,它说:¶1...程序的文本保存在称为源文件的单元中,(或预处理文件)在本国际标准中。源文件连同通过预处理指令#include 包含的所有头文件和源文件被称为 预处理翻译单元。 经过预处理后,一个预处理翻译单元称为翻译单元。
  • @JonathanLeffler> 没错。感谢您的关注,已修复:)
猜你喜欢
  • 2013-07-21
  • 1970-01-01
  • 2018-06-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-09
  • 2010-09-24
  • 1970-01-01
相关资源
最近更新 更多