Matthew Note

Include和Static Link的区别和联系

include简介

最简单的对于include的理解可以这样理解,实际上他就是把多个文件组成一个文件,编译的时候include的所有的文件都会包含到C/CPP文件中,这里涉及到一个重复定义的问题#ifndef来解决这个问题,大家应该都会使用。

静态链接简介

静态链接是把多个目标文件链接成一个可执行文件,这些链接过程在编译时候就完成的就是静态链接。下面我们来举例说明静态链接和include的区别。

比较 Case1

我们现在定义三个文件a.h/c main.c
a.h:

1
void funca();

a.c:

1
2
3
4
5
#include <stdio.h>
#include "a.h"
void funca(){
printf("Hello\n");
}

main.c:

1
2
3
4
int main(){
funca();
return 0;
}

下面我们编译一下生成目标文件

1
2
gcc -c a.c
gcc -c main.c

然后我们用objdump来查看下目标文件

1
objdump -t a.o

可以得到如下结果

1
2
3
4
5
6
7
8
9
10
11
SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 a.c
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .rodata 0000000000000000 .rodata
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000000 g F .text 0000000000000010 funca
0000000000000000 *UND* 0000000000000000 puts

我们可以看到我们自己定义的函数funca
在查看下main.o

1
2
3
4
5
6
7
8
9
10
SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 main.c
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000000 g F .text 0000000000000015 main
0000000000000000 *UND* 0000000000000000 funca

main中有一个未定义的函数funca,虽然main.c这里并没有include “a.h” 但是这并不影响最后的连接 我们可以得到一个最终正确的应用程序。

比较 Case2

修改main.c

1
2
3
4
5
#include "a.c"
int main(){
funca();
return 0;
}

在编译之后我们查看目标文件main.o,得到如下结果:

1
2
3
4
5
6
7
8
9
10
11
0000000000000000 l df *ABS* 0000000000000000 main.c
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .rodata 0000000000000000 .rodata
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000000 g F .text 0000000000000010 funca
0000000000000000 *UND* 0000000000000000 puts
0000000000000010 g F .text 0000000000000015 main

可见a.c被简单的直接加入到了main.c中,所以这里的funca 不是一个UND函数(UND 意为 undefined,是找不到这个函数的定义,需要从其他目标文件中寻找)。如果直接gcc -o main main.o是可以直接得到一个正确的可执行文件的。

比较 Case3

把a.h/c中的funca改为static,这样理论上这个函数是不能被其他文件所使用的

1
2
3
4
5
#include <stdio.h>
#include "a.h"
static void funca(){
printf("Hello\n");
}

再重新编译获得main的目标文件,并查看:

1
2
3
4
5
6
7
8
9
10
11
12
SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 main.c
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .rodata 0000000000000000 .rodata
0000000000000000 l F .text 0000000000000010 funca
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000000 *UND* 0000000000000000 puts
0000000000000010 g F .text 0000000000000015 main

可见我们依然可以正确的得到funca的定义。

总结

  • 1.正常函数的声明,默认就为extern
  • 2.static只是针对不同模块间链接的时候有效,并不是以前经常所说的其他文件中,应该叫其他目标文件中。
  • 3.实际上#include和静态链接两者是互通的,或者说作用及其相似,至少以我现在的经验来看,而且他们生成的最终文件大小是相同的。大概静态链接的方法更适合代码的管理吧。
  • 4.所有未被定义的函数和变量在编译时都会被标记为UND,在之后的连接过程中来进行连接,#include和gcc时候用-I folder/header 似乎是有同样的效果的,另外前置声明也可以达到同样效果
  • 5.-L 告诉编译器动态库位置