我做了一个实验,看看如果我尝试在那里编译两次相同的函数,将会生成什么样的汇编语言。我做了以下事情:
我创建了两个简单的测试文件及其相应的标题。我们称它们为a.c / a.h和b.c / b.h.以下是这些文件的内容:
A.H:
#ifndef __A_H__
#define __A_H__
int a( void );
#endif
b.h:
#ifndef __B_H__
#define __B_H__
int b( void );
#endif
交流转换器:
#include "a.h"
int a( void )
{
return 1;
}
b.c:
#include "b.h"
#include "a.h"
int b( void )
{
return 1 + a();
}
然后我为:
创建了一个静态存档gcc -c a.c -o a.o
ar -rsc a.a a.o
和b相同,包括此时的静态存档:
gcc -c b.c -o b.o
ar -rsc b.a a.a b.o
此时,我反汇编了b的静态存档,以验证它是否包含函数a()和b()的汇编代码。确实如此。
现在,我定义了最后一个文件:
main.c中:
#include <stdio.h>
#include "a.h"
#include "b.h"
int main( void )
{
printf( "%d %d\n", a(), b() );
return 0;
}
我这样编译:
gcc main.c a.a b.a -o main
这很好用。当我反汇编它时,我在代码中看到了a和b的以下定义:
140 0000000000400561 <a>:
141 400561: 55 push %rbp
142 400562: 48 89 e5 mov %rsp,%rbp
143 400565: b8 01 00 00 00 mov $0x1,%eax
144 40056a: 5d pop %rbp
145 40056b: c3 retq
146
147 000000000040056c <b>:
148 40056c: 55 push %rbp
149 40056d: 48 89 e5 mov %rsp,%rbp
150 400570: e8 ec ff ff ff callq 400561 <a>
151 400575: 83 c0 01 add $0x1,%eax
152 400578: 5d pop %rbp
153 400579: c3 retq
154 40057a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
正如你所看到的,代码已经明确地将b定义为调用而不是内联它,但是,代码中只有一个a的定义,没有重复。
似乎gcc有:
我的问题是:这种行为对我的测试来说是间接的还是标准的,我可以期待其他编译器的相同行为吗?显然重复的代码是一个问题,但是也可能存在重复的全局引用。构建具有到同一静态存档的多个依赖路径的大型应用程序是安全/良好的做法吗?是否有不太明显的情况,而不仅仅是重复的符号名称,这样做时会出现问题?
问这个是因为我一直在为我正在进行的项目玩这个想法,并希望做出正确的选择。
答案 0 :(得分:0)
我的问题是:这种行为对我的测试是间接的还是标准的,我可以期待其他编译器的相同行为吗?
就编译器本身而言,没有问题:您的源中的每个函数都有一个定义。
就ar
而言,您也没有任何问题:您构建的档案都不包含任何重复的符号。
然而,不同的接头可能表现出不同的行为。可以想象有些人会拒绝包含重复外部符号的链接存档。典型的UNIX链接器将处理您提供的情况,但它们可能在某些细节上有所不同,例如函数a()
的副本是否包含在二进制文件中。
显然重复的代码是一个问题,但是也可能存在重复的全局引用。构建具有到同一静态存档的多个依赖路径的大型应用程序是安全/良好的做法吗?
“通往同一静态存档的多条路径”似乎不能很好地描述您所呈现的情况。在这两种情况下,您都不会多次提供相同的存档。相反,在b
案例中,您提供了具有重复成员的不同档案。链接器通常在同一链接命令中多次指定相同的存档时没有问题。在某些情况下甚至可能需要这样做;它不应该出现问题。
提供具有重复成员可能的不同存档不会出现问题,除非可能使用重复的函数实现使代码膨胀。这有点不太确定,但我怀疑它在实践中会出现问题。
这种良好做法是否属于意见问题,但我倾向于不这么认为。我也不清楚你在这种方法中看到了什么。另一方面,如果您决定继续进行,我将不会削减任何赌注或准备任何点燃。