【问题标题】:Using a function from another C file placed in another directory?使用另一个目录中的另一个 C 文件中的函数?
【发布时间】:2016-07-04 10:37:38
【问题描述】:

假设我有一个父目录 A 有两个子目录 B 和 C。

子目录C有一个helper.c和helper.h如图:

//helper.c

 void print(){
     printf("Hello, World!\n");
 }


//helper.h

void print();

现在,在子目录 B 中,我有一个 main.c,它只调用打印函数:

//main.c

#include<stdio.h>
#include"../C/helper.h"

void main(){
    print(); 

}

我尝试了以下命令来编译 main.c:

Command 1: gcc main.c  //Gives undefined reference to 'print' error
Command 2: gcc main.c ../C/helper.c   //Compiles successfully

现在我从 main .c 中删除了 #include"../C/helper.h" 并再次尝试了命令 2。它仍然有效。

所以我有以下问题:

i) 是否包含 helper.h 文件有什么区别? helper.c?

ii) 为什么命令 1 失败?

iii) 有没有办法编译我的 C 程序而无需指定 helper.c 每次?

【问题讨论】:

    标签: c compilation linker


    【解决方案1】:

    执行时会发生什么:

    Command 1: gcc main.c  //Gives undefined reference to 'print' error
    

    执行 gcc main.c 时 编译器编译 main.c 并创建目标文件。此文件将包含函数 print() 的未解析链接。因为 main.c 文件中没有函数 print() 的实现。 编译后 gcc 尝试制作完整的可执行文件。为此,gcc 会结合所有目标文件并尝试解析所有未解析的链接。你还记得函数 print() 的链接未解析,gcc 找不到实现并引发错误。

    当你执行时

    Command 2: gcc main.c ../C/helper.c   //Compiles successfully
    

    gcc 编译这两个文件。第二个文件 ../C/helper.c 包含函数 print() 的实现,因此链接器可以找到它并在函数 main() 中解析对它的引用。

    i) 是否包含 helper.h 文件或 helper.c 有什么区别?

    在您的情况下,helper.h 包含函数 print() 的前向声明。这为编译器提供了如何调用函数 print() 的信息。

    ii) 为什么命令 1 失败?

    见上文。

    iii) 有没有办法编译我的 C 程序而不必每次都指定 helper.c?

    使用 make 实用程序。在单独的目标文件 helper.o 中编译 helper.c 并在链接命令中使用它。

    helper.o: ../C/helper.c ../C/helper.h
            gcc -c ../C/helper.c
    
    main.o: main.c main.h
            gcc -c main.c
    
    testprog: main.o helper.o
            g++ main.o helper.o -o testprog
    

    详情请参阅 make 实用程序手册。 命令应按 TAB 缩进。

    【讨论】:

      【解决方案2】:

      首先您需要了解#include 只是将#include 参数中的任何文本添加到语句所在文件中的位置,例如:

      //file1.h
      void foo();
      
      //main.c
      #include "file1.txt"
      int main(int argc, char **argv)
      {
         foo();
         return 0;
      }
      

      会导致预编译生成这个统一文件进行编译:

      //main.c.tmp
      void foo();
      
      int main(int argc, char **argv)
      {
         foo();
         return 0;
      }
      

      所以回答您的第一个和第二个问题
      当你包含一个只包含声明(即函数签名)而没有定义(即函数实现)的头文件(或任何文件)时,如上例所示,链接器将无法找到定义和您将收到“未定义的引用”错误。

      当您包含一个包含定义的 c 代码文件(或任何文件)时,这些定义将合并到您的代码中,并且 链接器 将拥有它们,这就是它起作用的原因。

      至于你的第三个​​问题 将 c 文件直接包含在其他 c 文件中是不好的做法,常见的方法是保留单独的 c 文件,其标题公开它们提供的功能,包含头文件并链接到已编译的 c 文件,例如在您的情况下:

      gcc main.c helper.c -o out

      将允许您在 main.c 中包含 helper.c 并且仍然可以工作,因为您指示编译器编译这两个文件而不是只编译 main.c 所以当链接发生时,将找到编译中的定义并且您不会得到undefined behavior 错误

      简而言之,就是这样。我抽象了很多正在发生的事情来传递一般的想法。 this 是一篇很好的文章,非常详细地描述了编译过程,this 是对整个过程的很好的概述。

      【讨论】:

        【解决方案3】:

        我会尽量回答:

        i) 是否包含 helper.h 文件或 helper.c 有什么区别?

        当您包含一个文件时,您不想公开您的实现,因此最好包含 h 文件,它只包含您的实现的“签名” - api。

        ii) 为什么命令 1 失败?

        编译时必须将所有资源添加到可执行文件中,否则他不会编译。

        iii) 有没有办法编译我的 C 程序而无需指定 helper.c 每次?

        • 您可以使用Makefile 来编译您的程序。或许this 教程可以帮到你。

        【讨论】:

          【解决方案4】:

          i) 是否包含 helper.h 文件有什么区别 还是 helper.c?

          包含 helper.c 意味着每次都会编译 helper.c,就好像它是 main.c 的一部分

          包含 helper.h 让编译器知道函数 print() 采用和返回的参数类型,因此如果您错误地调用 print(),编译器可以给出错误或警告

          ii) 为什么命令 1 失败?

          编译器没有被告知在哪里可以找到打印函数的实际代码。如前所述,包含 .h 文件仅有助于编译器进行类型检查。

          iii) 有没有办法编译我的 C 程序而无需指定 helper.c 每次?

          您可以将它编译成一个目标文件,并且可以选择将该 obj 添加到静态或动态加载的库中。您仍然需要帮助编译器找到该 obj 或库。例如,

          gcc -c helper.c
          gcc main.c helper.o
          

          避免编译不需要编译的模块的正确方法是使用 Makefile。 Makefile 比较一个模块的最后编译时间和它最后修改的时间,这样它就知道什么需要编译,什么不需要。

          【讨论】:

            猜你喜欢
            • 2011-09-30
            • 1970-01-01
            • 2019-02-24
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-09-02
            • 2021-02-15
            • 2018-10-13
            相关资源
            最近更新 更多