【问题标题】:Multiple Definitions with "Independent" Object Files具有“独立”对象文件的多个定义
【发布时间】:2015-12-09 04:41:45
【问题描述】:

我在空闲时间尝试了一些假 C,我发现了一些我觉得有趣的行为。我希望有人可以帮助回答我提出的一些问题。为了简单起见,让我们将讨论限制在使用 gcc 编译。首先,代码(.h 文件都有包含保护):

啊.h

void print_a();

b.h

void print_b();

c.h

void print_c();

交流

#include "a.h"
#include "c.h"

#include <stdio.h>

void print_c(){
  printf("Printing c from a");
}

void print_a(){
  print_c();
}

b.c

#include "b.h"
#include "c.h"

#include <stdio.h>

void print_c(){
  printf("Printing c from b");
}

void print_b(){
  print_c();
}

ma​​in.c

#include "a.h"
#include "b.h"

int main(int argc, char **argv){
  print_a();
  print_b();
  return 0;
}

首先,我知道 a.c 和 b.c 都有 c.h 的实现。因此,我预计这样的编译会失败,因为编译器不知道 print_c 的哪个实现要绑定到接口:

gcc main.c a.c b.c

但是您会注意到 main.c 对 c.h 没有依赖关系。因此,在单独编译每个组件时,看到在链接器阶段失败,我有点惊讶:

gcc -c a.c
gcc -c b.c
gcc -c main.c
gcc main.o a.o b.o
b.o: In function `print_c':
b.c:(.text+0x0): multiple definition of `print_c'
a.o:a.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status

很明显,由于链接器还没有链接 print_c 的定义,所以我们仍然应该预料到这些错误。也就是说,我可以想到几个实际的用例,这些用例会出现问题。例如,如果我在我的程序中使用了 log.h 接口的一些自定义实现,但我想包含一个在内部实现相同 log.h 接口的库,该怎么办?这引出了我的问题:

  • 有没有办法在单个文件上运行链接器以生成某种中间链接文件,然后在另一个链接步骤中组合它们?
  • 作为上述的替代方案,一个定义良好的 C 应用程序是否会避免使用任何像这样的共享头文件?
    • 如果是这样,如果多个依赖项的方法定义相同但实现不同怎么办?如果从源代码编译依赖项,显然可以重命名函数,但在不可能的情况下,这将如何工作?
  • 这甚至是 C 语言中的问题吗?我来自 OO 背景,这样的问题并不真正存在,但这似乎是抽象的明显障碍。我很可能只是遗漏了一些可以防止此问题的常见内容(如方法命名约定等)。

【问题讨论】:

    标签: c gcc linker


    【解决方案1】:

    头文件包含函数原型。一个函数原型告诉编译器

    • 函数名
    • 函数采用的参数
    • 函数的返回类型

    按照惯例,头文件只包含在一个源文件中定义但在其他源文件中使用的函数的函数原型。

    所以c.h不应该存在,a.hb.h中的函数原型需要参数列表。头文件应该是:

    啊.h

    void print_a( void );
    

    b.h

    void print_b( void );
    

    源文件a.cb.c可以改进如下。首先,始终在您自己的标题之前包含标准标题(请参阅下面的注释)。其次,不需要包含 c.h,因为print_c 在使用之前就已经定义好了。所以函数定义作为函数原型。第三,print_c 需要一个参数列表:void print_c( void )第四,这也是重点,print_c函数应该声明为static static关键字表示该函数只在定义它的文件中可见,即它不是全局可见的。使用 static 关键字可以重新定义每个 .c 文件中的函数。

    所以你的源文件应该是这样的

    交流

    #include <stdio.h>
    #include "a.h"
    
    static void print_c( void ){
        printf("Printing c from a\n");
    }
    
    void print_a( void ){
        print_c();
    }
    

    b.c

    #include <stdio.h>
    #include "b.h"
    
    static void print_c( void ){
        printf("Printing c from b\n");
    }
    
    void print_b( void ){
        print_c();
    }
    

    ma​​in.c

    #include "a.h"
    #include "b.h"
    
    int main( void ){
        print_a();
        print_b();
        return 0;
    }
    

    注意:首先包含标准头的原因是标准头应该没有错误,而您的头文件可能没有。如果您的标头中有语法错误,并且您的标头包含在标准标头之前,则该错误最终可能会被报告为标准标头中的错误,这会变得非常混乱。

    【讨论】:

      【解决方案2】:

      您的程序有一个错误:有两个外部可见的函数,称为print_c

      这会导致 undefined behaviour 不需要诊断。

      尝试通过反复试验来学习 C 的效果并不好,尤其是在这方面:编译器/链接器确实倾向于利用他们的许可而不告诉您您做错了什么。在这种情况下,你是幸运的。


      解决具体问题:

      c.h. 的实现

      那不是一回事。每个功能都与其他功能分开考虑。

      作为上述的替代方案,定义良好的 C 应用程序是否会避免使用任何这样的共享头文件?

      共享头文件没有问题。问题是你有两个同名的函数体。

      如果多个依赖项具有定义相同但实现不同的方法怎么办?

      您可能是指相同的声明,而不是相同的定义。答案是你不能在 C 中做到这一点。但无论如何都没有必要这样做。

      有时两个不同的库会定义一个同名的函数,这会导致这个问题的错误。为避免这种情况,库通常会在其所有名称之前使用前缀。

      【讨论】:

        猜你喜欢
        • 2018-09-13
        • 1970-01-01
        • 1970-01-01
        • 2019-08-09
        • 1970-01-01
        • 2012-06-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多