使用"独立"的多个定义对象文件

时间:2015-12-09 04:41:46

标签: c gcc linker

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

A.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();
}

的main.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背景,这样的问题确实存在,但这似乎是抽象的明显障碍。我很可能只是缺少可以防止这个问题的常见内容(比如方法命名约定等)。

2 个答案:

答案 0 :(得分:0)

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

  • 功能名称
  • 该函数的参数
  • 函数的返回类型

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

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

<强> A.H

void print_a( void );

<强> b.h

void print_b( void );

源文件 a.c b.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();
}

<强>的main.c

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

int main( void ){
    print_a();
    print_b();
    return 0;
}

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

答案 1 :(得分:0)

你的程序有一个错误:有两个外部可见的函数叫print_c

这导致undefined behaviour无需诊断。

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

解决具体问题:

  

c.h的实现。

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

  

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

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

  

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

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

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