gcc以及静态库和动态库

本文最后更新于:2021年3月3日 下午

概览:gcc命令与工作流程,静态库的制作与使用,动态库的制作与使用,动态库加载失败原因与解决方案。

gcc简单命令与其工作流程

1
2
3
4
5
6
7
8
9
10
//prog.c

#include <stdio.h>

int main(){

printf("10 + 11 = %d \n",10+11);
return 0;
}

生成可执行目标文件

对于上述文件,在shell中输入命令:

1
gcc -o prog prog.c

即可生成可执行的目标文件prog

而实际上这一简单的过程有四步主要步骤:预处理,编译,汇编,以及最后的链接,才能生成可执行的目标文件。

1.预处理 gcc -E

1
gcc -E prog.c -o prog.i

gcc - E 表示进行预处理,调用了cpp预处理器,进行头文件展开,宏替换,以及去掉注释等操作。

进行预处理之后的仍然是一个文本文件,后缀名.i,大致能看懂一些。

-o 后接要生成的目标文件名,这里的prog.i即为自己要生成的文件名。

上述命令可以使用cpp prog.c prog.i来代替。

2.编译 gcc -S

1
gcc -S prog.i -o prog.s

gcc - S 表示进行预处理,调用了cc1 C编译器,将.i文件编译成为ASCII汇编语言文件.s.

上述命令可以使用cc1 prog.i -o prog.s来代替。

生成的汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
	.file	"prog.c"
.text
.section .rodata
.LC0:
.string "10 + 11 = %d \n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $21, %esi
leaq .LC0(%rip), %rdi
movl $0, %eax
call printf@PLT
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 9.2.1-9ubuntu2) 9.2.1 20191008"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:

3.汇编 gcc -c

1
gcc -c prog.s -o prog.o

gcc -c 表示进行汇编的工作,调用了as 汇编器,将.s文件编译成为二进制文件 —— 可重定位目标文件(relocatable object file) .o.

上述命令可以使用as -o prog.o prog.s来代替。

.o文件是二进制文件,普通的文本查看工具不能打开,这里可以使用linux中的工具objdump来查看。

输入命令:

1
objdump -d prog.o

得到了.o文件的二进制以及对应的汇编语言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
prog.o:     文件格式 elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
0: f3 0f 1e fa endbr64
4: 55 push %rbp
5: 48 89 e5 mov %rsp,%rbp
8: be 15 00 00 00 mov $0x15,%esi
d: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 14 <main+0x14>
14: b8 00 00 00 00 mov $0x0,%eax
19: e8 00 00 00 00 callq 1e <main+0x1e>
1e: b8 00 00 00 00 mov $0x0,%eax
23: 5d pop %rbp
24: c3 retq

4.链接

1
gcc -o prog prog.o

这一步gcc将会调用链接器程序ld,将prog.o以及某些必要的系统目标文件组合起来,最终创建创建一个可执行的目标文件(executable object file)。

上述命令可以使用ld -o prog [一些系统文件与参数] prog.o来代替。

同样可以使用objdump来反编译来进行查看汇编代码:

1
objdump -d prog

内容比较多:

点击查看/折叠代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
prog:     文件格式 elf64-x86-64


Disassembly of section .init:

0000000000001000 <_init>:
1000: f3 0f 1e fa endbr64
1004: 48 83 ec 08 sub $0x8,%rsp
1008: 48 8b 05 d9 2f 00 00 mov 0x2fd9(%rip),%rax # 3fe8 <__gmon_start__>
100f: 48 85 c0 test %rax,%rax
1012: 74 02 je 1016 <_init+0x16>
1014: ff d0 callq *%rax
1016: 48 83 c4 08 add $0x8,%rsp
101a: c3 retq

Disassembly of section .plt:

0000000000001020 <.plt>:
1020: ff 35 9a 2f 00 00 pushq 0x2f9a(%rip) # 3fc0 <_GLOBAL_OFFSET_TABLE_+0x8>
1026: f2 ff 25 9b 2f 00 00 bnd jmpq *0x2f9b(%rip) # 3fc8 <_GLOBAL_OFFSET_TABLE_+0x10>
102d: 0f 1f 00 nopl (%rax)
1030: f3 0f 1e fa endbr64
1034: 68 00 00 00 00 pushq $0x0
1039: f2 e9 e1 ff ff ff bnd jmpq 1020 <.plt>
103f: 90 nop

Disassembly of section .plt.got:

0000000000001040 <__cxa_finalize@plt>:
1040: f3 0f 1e fa endbr64
1044: f2 ff 25 ad 2f 00 00 bnd jmpq *0x2fad(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
104b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)

Disassembly of section .plt.sec:

0000000000001050 <printf@plt>:
1050: f3 0f 1e fa endbr64
1054: f2 ff 25 75 2f 00 00 bnd jmpq *0x2f75(%rip) # 3fd0 <printf@GLIBC_2.2.5>
105b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)

Disassembly of section .text:

0000000000001060 <_start>:
1060: f3 0f 1e fa endbr64
1064: 31 ed xor %ebp,%ebp
1066: 49 89 d1 mov %rdx,%r9
1069: 5e pop %rsi
106a: 48 89 e2 mov %rsp,%rdx
106d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
1071: 50 push %rax
1072: 54 push %rsp
1073: 4c 8d 05 66 01 00 00 lea 0x166(%rip),%r8 # 11e0 <__libc_csu_fini>
107a: 48 8d 0d ef 00 00 00 lea 0xef(%rip),%rcx # 1170 <__libc_csu_init>
1081: 48 8d 3d c1 00 00 00 lea 0xc1(%rip),%rdi # 1149 <main>
1088: ff 15 52 2f 00 00 callq *0x2f52(%rip) # 3fe0 <__libc_start_main@GLIBC_2.2.5>
108e: f4 hlt
108f: 90 nop

0000000000001090 <deregister_tm_clones>:
1090: 48 8d 3d 79 2f 00 00 lea 0x2f79(%rip),%rdi # 4010 <__TMC_END__>
1097: 48 8d 05 72 2f 00 00 lea 0x2f72(%rip),%rax # 4010 <__TMC_END__>
109e: 48 39 f8 cmp %rdi,%rax
10a1: 74 15 je 10b8 <deregister_tm_clones+0x28>
10a3: 48 8b 05 2e 2f 00 00 mov 0x2f2e(%rip),%rax # 3fd8 <_ITM_deregisterTMCloneTable>
10aa: 48 85 c0 test %rax,%rax
10ad: 74 09 je 10b8 <deregister_tm_clones+0x28>
10af: ff e0 jmpq *%rax
10b1: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
10b8: c3 retq
10b9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)

00000000000010c0 <register_tm_clones>:
10c0: 48 8d 3d 49 2f 00 00 lea 0x2f49(%rip),%rdi # 4010 <__TMC_END__>
10c7: 48 8d 35 42 2f 00 00 lea 0x2f42(%rip),%rsi # 4010 <__TMC_END__>
10ce: 48 29 fe sub %rdi,%rsi
10d1: 48 89 f0 mov %rsi,%rax
10d4: 48 c1 ee 3f shr $0x3f,%rsi
10d8: 48 c1 f8 03 sar $0x3,%rax
10dc: 48 01 c6 add %rax,%rsi
10df: 48 d1 fe sar %rsi
10e2: 74 14 je 10f8 <register_tm_clones+0x38>
10e4: 48 8b 05 05 2f 00 00 mov 0x2f05(%rip),%rax # 3ff0 <_ITM_registerTMCloneTable>
10eb: 48 85 c0 test %rax,%rax
10ee: 74 08 je 10f8 <register_tm_clones+0x38>
10f0: ff e0 jmpq *%rax
10f2: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
10f8: c3 retq
10f9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)

0000000000001100 <__do_global_dtors_aux>:
1100: f3 0f 1e fa endbr64
1104: 80 3d 05 2f 00 00 00 cmpb $0x0,0x2f05(%rip) # 4010 <__TMC_END__>
110b: 75 2b jne 1138 <__do_global_dtors_aux+0x38>
110d: 55 push %rbp
110e: 48 83 3d e2 2e 00 00 cmpq $0x0,0x2ee2(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
1115: 00
1116: 48 89 e5 mov %rsp,%rbp
1119: 74 0c je 1127 <__do_global_dtors_aux+0x27>
111b: 48 8b 3d e6 2e 00 00 mov 0x2ee6(%rip),%rdi # 4008 <__dso_handle>
1122: e8 19 ff ff ff callq 1040 <__cxa_finalize@plt>
1127: e8 64 ff ff ff callq 1090 <deregister_tm_clones>
112c: c6 05 dd 2e 00 00 01 movb $0x1,0x2edd(%rip) # 4010 <__TMC_END__>
1133: 5d pop %rbp
1134: c3 retq
1135: 0f 1f 00 nopl (%rax)
1138: c3 retq
1139: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)

0000000000001140 <frame_dummy>:
1140: f3 0f 1e fa endbr64
1144: e9 77 ff ff ff jmpq 10c0 <register_tm_clones>

0000000000001149 <main>:
1149: f3 0f 1e fa endbr64
114d: 55 push %rbp
114e: 48 89 e5 mov %rsp,%rbp
1151: be 15 00 00 00 mov $0x15,%esi
1156: 48 8d 3d a7 0e 00 00 lea 0xea7(%rip),%rdi # 2004 <_IO_stdin_used+0x4>
115d: b8 00 00 00 00 mov $0x0,%eax
1162: e8 e9 fe ff ff callq 1050 <printf@plt>
1167: b8 00 00 00 00 mov $0x0,%eax
116c: 5d pop %rbp
116d: c3 retq
116e: 66 90 xchg %ax,%ax

0000000000001170 <__libc_csu_init>:
1170: f3 0f 1e fa endbr64
1174: 41 57 push %r15
1176: 4c 8d 3d 3b 2c 00 00 lea 0x2c3b(%rip),%r15 # 3db8 <__frame_dummy_init_array_entry>
117d: 41 56 push %r14
117f: 49 89 d6 mov %rdx,%r14
1182: 41 55 push %r13
1184: 49 89 f5 mov %rsi,%r13
1187: 41 54 push %r12
1189: 41 89 fc mov %edi,%r12d
118c: 55 push %rbp
118d: 48 8d 2d 2c 2c 00 00 lea 0x2c2c(%rip),%rbp # 3dc0 <__init_array_end>
1194: 53 push %rbx
1195: 4c 29 fd sub %r15,%rbp
1198: 48 83 ec 08 sub $0x8,%rsp
119c: e8 5f fe ff ff callq 1000 <_init>
11a1: 48 c1 fd 03 sar $0x3,%rbp
11a5: 74 1f je 11c6 <__libc_csu_init+0x56>
11a7: 31 db xor %ebx,%ebx
11a9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
11b0: 4c 89 f2 mov %r14,%rdx
11b3: 4c 89 ee mov %r13,%rsi
11b6: 44 89 e7 mov %r12d,%edi
11b9: 41 ff 14 df callq *(%r15,%rbx,8)
11bd: 48 83 c3 01 add $0x1,%rbx
11c1: 48 39 dd cmp %rbx,%rbp
11c4: 75 ea jne 11b0 <__libc_csu_init+0x40>
11c6: 48 83 c4 08 add $0x8,%rsp
11ca: 5b pop %rbx
11cb: 5d pop %rbp
11cc: 41 5c pop %r12
11ce: 41 5d pop %r13
11d0: 41 5e pop %r14
11d2: 41 5f pop %r15
11d4: c3 retq
11d5: 66 66 2e 0f 1f 84 00 data16 nopw %cs:0x0(%rax,%rax,1)
11dc: 00 00 00 00

00000000000011e0 <__libc_csu_fini>:
11e0: f3 0f 1e fa endbr64
11e4: c3 retq

Disassembly of section .fini:

00000000000011e8 <_fini>:
11e8: f3 0f 1e fa endbr64
11ec: 48 83 ec 08 sub $0x8,%rsp
11f0: 48 83 c4 08 add $0x8,%rsp
11f4: c3 retq

然后再输入命令./prog即可执行编译出来的文件.

1
2
linux> ./prog
10 + 11 = 21

其他gcc命令参数

-I dir : 用于指定include包含文件的搜索目录。

-g : 在编译时生成调试信息,该程序可以被调试器调试。

-D : 在程序编译时,指定一个宏。

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int main(){

#ifdef DEBUG
printf("调试信息打印中\n");
#endif

printf("正常输出\n");
return 0;
}

若使用gcc时指定了DEBUG宏,则实际运行时将会打印调试信息,实际上这种方式也是调试的一种好方式。

  • -D DEBUG这里可以有空格。
1
2
3
4
5
6
7
colourso@c:~/桌面/gcctest$ gcc -o prog prog.c -DDEBUG
colourso@c:~/桌面/gcctest$ ./prog
调试信息打印中
正常输出
colourso@c:~/桌面/gcctest$ gcc -o prog prog.c
colourso@c:~/桌面/gcctest$ ./prog
正常输出

-w:不生成任何警告信息。

-Wall:生成所有的警告信息。

-On:n的取值范围是0-3,表示编译器优化选项的几个级别,-O1为缺省值,-O3为优化级别最高。-o0-Og表示没有优化。

-l xx :在程序编译的时候,指定使用的库。

-L dir:指定编译的时候,搜索的库的路径。

-fPIC/fpic:生成与位置无关的代码,一般用于动态库的制作。

-shared:生成共享目标文件,一般用于动态库的制作。

-std=c99:指定c方言,gcc默认方言是GUN C

静态库

文件:head.h,add.c,sub.c,mult.c,div.c,main.c

1
2
3
4
5
6
7
8
9
10
//head.h
#ifndef HEAD_H
#define HEAD_H

int add(int a,int b);
int sub(int a,int b);
int mult(int a,int b);
int div(int a,int b);

#endif
1
2
3
4
5
6
7
//add.c
#include <stdio.h>
#include "head.h"

int add(int a,int b){
return a+b;
}
1
2
3
4
5
6
7
//sub.c
#include <stdio.h>
#include "head.h"

int sub(int a,int b){
return a-b;
}
1
2
3
4
5
6
7
//mult.c
#include <stdio.h>
#include "head.h"

int mult(int a,int b){
return a*b;
}
1
2
3
4
5
6
7
//div.c
#include <stdio.h>
#include "head.h"

int div(int a,int b){
return a/b;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//main.c
#include <stdio.h>
#include "head.h"

int main(){

int a = 10;
int b = 5;

printf("%d + %d = %d\n",a,b,add(a,b));
printf("%d - %d = %d\n",a,b,sub(a,b));
printf("%d * %d = %d\n",a,b,mult(a,b));
printf("%d / %d = %d\n",a,b,div(a,b));

return 0;
}

静态库的命名规则

Linux:libxxx.a

  • lib :前缀,固定
  • xxx :库的名字,自己起
  • .a :后缀,固定

Windows:libxxx.lib

制作

  1. 使用gcc -c生成.o文件;
  2. 使用ar工具(archive)打包生成静态库。
1
ar rcs libxxx.a aaa.o bbb.o
  • r :将文件插入备存文件中
  • c :建立备存文件
  • s :索引

对于上述文件,生成静态库:

1
2
3
4
5
6
7
8
colourso@c:~/桌面/ch1linux/05staticlib$ ls
add.c div.c head.h main.c mult.c sub.c
colourso@c:~/桌面/ch1linux/05staticlib$ gcc -c add.c sub.c mult.c div.c
colourso@c:~/桌面/ch1linux/05staticlib$ ls
add.c add.o div.c div.o head.h main.c mult.c mult.o sub.c sub.o
colourso@c:~/桌面/ch1linux/05staticlib$ ar rcs libmycalc.a add.o sub.o mult.o div.o
colourso@c:~/桌面/ch1linux/05staticlib$ ls
add.c add.o div.c div.o head.h libmycalc.a main.c mult.c mult.o sub.c sub.o

使用

1
2
3
4
5
6
7
8
colourso@c:~/桌面/ch1linux/05staticlib$ gcc -o prog main.c -L . -l mycalc
colourso@c:~/桌面/ch1linux/05staticlib$ ls
add.c add.o div.c div.o head.h libmycalc.a main.c mult.c mult.o prog sub.c sub.o
colourso@c:~/桌面/ch1linux/05staticlib$ ./prog
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
10 / 5 = 2

对于命令gcc -o prog main.c -L . -l mycalc

-L .表示在当前目录下搜索库。
-l mycalc表示使用库libmycalc.a,这里只需要指定库的名字即可。

动态库

动态库的命名规则

Linux:libxxx.so

  • lib :前缀,固定
  • xxx :库的名字,自己起
  • .sp :后缀,固定

Windows:libxxx.dll

动态库的制作

  1. 使用gcc -c -fpic生成.o文件,但是要得到位置无关的文件;
  2. 使用gcc -shared xx.o -o libxxx.so生成动态库。

先将上述文件重新整理后放到新的文件夹下:

1
2
3
4
5
6
7
8
9
10
.
├── include
│   └── head.h
├── lib
├── main.c
└── src
├── add.c
├── div.c
├── mult.c
└── sub.c
1
2
3
4
5
6
colourso@c:~/桌面/ch1linux/06dll/src$ gcc -c -fpic *.c -I ../include
colourso@c:~/桌面/ch1linux/06dll/src$ ls
add.c add.o div.c div.o mult.c mult.o sub.c sub.o
colourso@c:~/桌面/ch1linux/06dll/src$ gcc -shared *.o -o libmycalc.so
colourso@c:~/桌面/ch1linux/06dll/src$ ls
add.c add.o div.c div.o libmycalc.so mult.c mult.o sub.c sub.o

然后将.so文件移动至lib目录下。

动态库的使用

文件分布:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.
├── include
│   └── head.h
├── lib
│   └── libmycalc.so
├── main.c
└── src
├── add.c
├── add.o
├── div.c
├── div.o
├── mult.c
├── mult.o
├── sub.c
└── sub.o

使用动态库,生成可执行程序,但是在执行程序的时候却报错了。

1
2
3
4
5
6
colourso@c:~/桌面/ch1linux/06dll$ gcc -o main main.c -I ./include -L lib/ -l mycalc
colourso@c:~/桌面/ch1linux/06dll$ ls
include lib main main.c src
colourso@c:~/桌面/ch1linux/06dll$ ./main
./main: error while loading shared libraries: libmycalc.so: cannot open shared object file: No such file or directory

然后使用ldd命令(list dynamic dependencies)检查动态库依赖关系,可以看到libmycalc.so未找到。

1
2
3
4
5
colourso@c:~/桌面/ch1linux/06dll$ ldd main
linux-vdso.so.1 (0x00007ffc44d16000)
libmycalc.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa620ff8000)
/lib64/ld-linux-x86-64.so.2 (0x00007fa621200000)

动态库报错原因

  • 静态库:GCC进行链接时,会把静态库中代码打包到可执行程序中;
  • 动态库:GCC进行链接时,动态库的代码不会被打包到可执行程序中,而是在程序启动之后,动态库会被动态加载到内存之中,再被原先程序调用。

当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系统的动态载入器来获取该绝对路径。

对于elf格式的可执行程序,是由ld-linux.so来完成的,它会先后搜索elf文件的DT_RPATH段 ——> 环境变量LD_LIBRARY_PATH ——> /etc/ld.so.cache文件列表 ——> /lib//usr/lib目录找到库文件后将其载入内存。

解决办法

DT_RPATH段一般不考虑,我们一般改变不了。

配置环境变量LD_LIBRARY_PATH

  1. 临时配置,在当前的终端之内有效

当终端关掉或者更换一个终端时就会失效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
colourso@c:~/桌面/ch1linux/06dll$ echo $LD_LIBRARY_PATH
【空白,什么也没有】
colourso@c:~/桌面/ch1linux/06dll$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/colourso/桌面/ch1linux/06dll/lib
colourso@c:~/桌面/ch1linux/06dll$ echo $LD_LIBRARY_PATH
:/home/colourso/桌面/ch1linux/06dll/lib
colourso@c:~/桌面/ch1linux/06dll$ ldd main
linux-vdso.so.1 (0x00007ffef9dc2000)
libmycalc.so => /home/colourso/桌面/ch1linux/06dll/lib/libmycalc.so (0x00007f82b4f5b000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f82b4d5a000)
/lib64/ld-linux-x86-64.so.2 (0x00007f82b4f67000)
colourso@c:~/桌面/ch1linux/06dll$ ./main
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
10 / 5 = 2
  1. 用户级别的配置

在用户目录~下有一个文件.bashrc,通过修改文件,添加配置即可解决问题。

通过vi编辑,在其文件末尾添加命令export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/colourso/桌面/ch1linux/06dll/lib,然后要使修改生效,输入命令source .bashrc即可。

1
2
3
colourso@c:~/桌面/ch1linux/06dll$ cd ~
colourso@c:~$ vi .bashrc
colourso@c:~$ source .bashrc

此时再执行原来的文件,也没有任何问题。

  1. 系统级别的配置

先将上述用户级别的配置删除。
修改文件/etc/profile,和上述相同的操作来实现配置。

1
2
3
4
5
6
7
colourso@c:~/桌面/ch1linux/06dll$ sudo vi /etc/profile
colourso@c:~/桌面/ch1linux/06dll$ source /etc/profile
colourso@c:~/桌面/ch1linux/06dll$ ldd main
linux-vdso.so.1 (0x00007fffe52ef000)
libmycalc.so => /home/colourso/桌面/ch1linux/06dll/lib/libmycalc.so (0x00007fbfaf973000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbfaf772000)
/lib64/ld-linux-x86-64.so.2 (0x00007fbfaf97f000)

配置/etc/ld.so.cache文件列表

配置之前,先将上述过程的配置修改为原样。

实际上/etc/ld.so.cache是一个二进制文件不能修改,我们通常配置的是/etc/ld.so.conf文件。

打开文件之后,只需要在文件末尾添加路径即可/home/colourso/桌面/ch1linux/06dll/lib。之后更新配置sudo ldconfig.

1
2
3
4
5
6
7
colourso@c:~/桌面/ch1linux/06dll$ sudo vi /etc/ld.so.conf
colourso@c:~/桌面/ch1linux/06dll$ sudo ldconfig
colourso@c:~/桌面/ch1linux/06dll$ ldd main
linux-vdso.so.1 (0x00007ffeb4979000)
libmycalc.so => /home/colourso/桌面/ch1linux/06dll/lib/libmycalc.so (0x00007f5ca4cab000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5ca4aba000)
/lib64/ld-linux-x86-64.so.2 (0x00007f5ca4cc7000)

/lib/以及/usr/lib目录

一般不建议将自己的动态库放置到这两个目录下,因为原本它就含有很多的库文件,如果一不小心重名覆盖,将会有很大的隐患。

静态库与动态库对比

  1. 链接阶段不同,链接的方式分别称为静态链接方式和动态链接方式。
  • 静态链接方式:GCC进行链接时,会把静态库中代码打包到可执行程序中;
  • 动态链接方式:GCC进行链接时,动态库的代码不会被打包到可执行程序中,而是在程序启动之后,动态库会被动态加载到内存之中,再被原先程序调用。
  1. 制作过程
  • 静态库:生成.o文件,ar工具制作
  • 动态库:使用命令-fpic生成位置无关的.o文件,然后再用GCC命令-shared生成动态库。
  1. 动态库使用时需要额外的配置

  2. 静态库优点

  • 静态库被打包到应用程序,加载速度快
  • 发布程序不需要提供静态库,移植也比较方便
  1. 静态库缺点
  • 消耗系统资源,浪费内存。例如10个程序都需要使用静态库a,那么这个库就会被加载10份在内存之中。
  • 更新,部署、发布麻烦。一旦静态库更新,整个程序都要重新编译。
  1. 动态库优点
  • 可以实现进程间的资源共享。多个程序只需要一份动态库被加载到内存即可。
  • 更新、部署、发布简单。
  • 可以控制何时加载动态库。
  1. 动态库缺点
  • 加载速度比静态库慢。
  • 发布程序的时候需要提供依赖的动态库。

makefile文件

一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,
Makefile 文件定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 Makefile 文件就像一个 Shell 脚本一样,也可以执行操作系统的命令。

Makefile 带来的好处就是自动化编译 ,一旦写好,只需要一个 make 命令,整个工程完全自动编译,极大的提高了软件开发的效率。

make 是一个命令工具,是一个解释 Makefile 文件中指令的命令工具,一般来说,大多数的 IDE 都有这个命令,比如 Delphi 的 make,Visual C++ 的 nmake,Linux 下 GNU 的 make。

文件命名makefile 或者 Makefile

规则

目标 : 依赖
(tab) 命令

  • 目标:最终要生成的文件(伪目标除外)
  • 依赖:生成目标所需要的文件或是目标
  • 命令:通过执行命令对依赖操作生成目标(命令前必须 Tab 缩进)
  • 一个makefile文件之中可以有多个规则
  • Makefile中的其它规则一般都是为第一条规则服务的。

工作原理

命令在执行之前,需要先检查规则中的依赖是否存在

  • 如果存在,执行命令
  • 如果不存在,向下检查其它的规则,检查有没有一个规则是用来生成这个依赖的,

如果找到了,则执行该规则中的命令

  • 检测更新,在执行规则中的命令时,会比较目标和依赖文件的时间
    • 如果依赖的时间比目标的时间晚,需要重新生成目标
    • 如果依赖的时间比目标的时间早,目标不需要更新,对应规则中的命令不需要被执行

实例

依然使用静态库所用过的那几个代码。

第一版makefile文件:

1
2
app:add.c sub.c mult.c div.c main.c
gcc add.c sub.c mult.c div.c main.c -o app

然后再终端之中执行make.

1
2
3
4
5
6
7
8
9
10
11
colourso@colourso-virtual-machine:~/桌面/ch1linux/07make$ ls
add.c div.c head.h main.c makefile mult.c sub.c
colourso@colourso-virtual-machine:~/桌面/ch1linux/07make$ make
gcc add.c sub.c mult.c div.c main.c -o app
colourso@colourso-virtual-machine:~/桌面/ch1linux/07make$ ls
add.c app div.c head.h main.c makefile mult.c sub.c
colourso@colourso-virtual-machine:~/桌面/ch1linux/07make$ ./app
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
10 / 5 = 2

第二版本makefile文件:

在这之前,随便增加一个test.c的命令在makefile文件之中,并且不放在第一条。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
app:add.o sub.o mult.o div.o main.o
gcc add.o sub.o mult.o div.o main.o -o app

add.o:add.c
gcc -c add.c -o add.o

sub.o:sub.c
gcc -c sub.c -o sub.o

mult.o:mult.c
gcc -c mult.c -o mult.o

div.o:div.c
gcc -c div.c -o div.o

main.o:main.c
gcc -c main.c -o main.o

test.o:test.c
gcc -c test.c -o test.o

执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
colourso@colourso-virtual-machine:~/桌面/ch1linux/07make$ ls
add.c div.c head.h main.c makefile mult.c sub.c
colourso@colourso-virtual-machine:~/桌面/ch1linux/07make$ make
gcc -c add.c -o add.o
gcc -c sub.c -o sub.o
gcc -c mult.c -o mult.o
gcc -c div.c -o div.o
gcc -c main.c -o main.o
gcc add.o sub.o mult.o div.o main.o -o app
colourso@colourso-virtual-machine:~/桌面/ch1linux/07make$ ./app
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
10 / 5 = 2

通过上述执行效果可以看到test.o没有生成,也没有报错,Makefile中的其它规则一般都是为第一条规则服务的,规则没有用到的依赖对应的规则,是不会被执行的。

再尝试更改main.c文件,使其和原来略微有所区别,然后重新make查看变化。

1
2
3
4
5
6
7
8
9
10
11
12
colourso@colourso-virtual-machine:~/桌面/ch1linux/07make$ make
gcc -c main.c -o main.o
gcc add.o sub.o mult.o div.o main.o -o app
colourso@colourso-virtual-machine:~/桌面/ch1linux/07make$ ls
add.c ap.c div.c head.h main.o mult.c sub.c
add.o app div.o main.c makefile mult.o sub.o
colourso@colourso-virtual-machine:~/桌面/ch1linux/07make$ ./app
Makefile test
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
10 / 5 = 2

可以看到,仅仅main.o被重新生成了,这就是make的规则,检查时间来决定需不需要更新。

从上面的例子也可以看到第二版本的makefile比第一版本的makefile要好一些,仅会对更新过的文件进行编译,节省资源和时间。

makefile一些语法

  • #后可以接注释

  • 自定义变量:变量名=变量值 例如var=hello

  • 预定义变量

    • AR : 归档维护程序的名称,默认值为 ar
    • CC : C 编译器的名称,默认值为 cc
    • CXX : C++ 编译器的名称,默认值为 g++
  • 自动变量,只能在规则的命令中使用。

    • $@ : 目标的完整名称
    • $< : 第一个依赖文件的名称
    • $^ : 所有的依赖文件
  • 获取变量的值:$(变量名)

  • 模式匹配:

    • %.o:%.c
    • %: 通配符,匹配一个字符串
    • 两个%匹配的是同一个字符串

第三版本makefile文件:

1
2
3
4
5
6
7
8
9
10
# src、target为变量名
src=add.o sub.o mult.o div.o main.o
target=app

$(target):$(src)
$(CC) $(src) -o $(target)

#采用模式匹配,匹配*.o *.c
%.o:%.c
$(CC) -c $< -o $@

执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
colourso@colourso-virtual-machine:~/桌面/ch1linux/07make$ ls
add.c div.c head.h main.c makefile mult.c sub.c
colourso@colourso-virtual-machine:~/桌面/ch1linux/07make$ make
cc -c add.c -o add.o
cc -c sub.c -o sub.o
cc -c mult.c -o mult.o
cc -c div.c -o div.o
cc -c main.c -o main.o
cc add.o sub.o mult.o div.o main.o -o app
colourso@colourso-virtual-machine:~/桌面/ch1linux/07make$ ls
add.c app div.o main.c makefile mult.o sub.o
add.o div.c head.h main.o mult.c sub.c
colourso@colourso-virtual-machine:~/桌面/ch1linux/07make$ ./app
Makefile test
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
10 / 5 = 2

makefile常用函数

  1. $(wildcard PATTERN...)
  • 功能:获取指定目录下指定类型的文件列表
  • 参数:PATTERN 指的是某个或多个目录下的对应的某种类型的文件,如果有多个目录,一般使用空格间隔
  • 返回:得到的若干个文件的文件列表,文件名之间使用空格间隔
  • 示例:
    $(wildcard *.c ./sub/*.c)
    返回值格式: a.c b.c c.c d.c e.c f.c
  1. $(patsubst <pattern>,<replacement>,<text>)
  • 功能:查找中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合
  • 模式,如果匹配的话,则以替换。可以包括通配符%,表示任意长度的字串。如果中也包含%,那么,中的这个%将是中的那个%所代表的字串。(可以用\来转义,以\%来表示真实含义的%字符)
  • 返回:函数返回被替换过后的字符串
  • 示例:
    $(patsubst %.c, %.o, x.c bar.c)
    返回值格式: x.o bar.o

第四版本makefile文件:

1
2
3
4
5
6
7
8
9
10
11
12
# 获取当前路径下所有.c文件
src=$(wildcard ./*.c)
# 将.c文件转为.o文件
objs=$(patsubst %.c,%.o,$(src))
target=app

$(target):$(objs)
$(CC) $(objs) -o $(target)

#采用模式匹配,匹配*.o *.c
%.o:%.c
$(CC) -c $< -o $@

运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
colourso@colourso-virtual-machine:~/桌面/ch1linux/07make$ ls
add.c div.c head.h main.c makefile mult.c sub.c
colourso@colourso-virtual-machine:~/桌面/ch1linux/07make$ make
cc -c mult.c -o mult.o
cc -c main.c -o main.o
cc -c add.c -o add.o
cc -c div.c -o div.o
cc -c sub.c -o sub.o
cc ./mult.o ./main.o ./add.o ./div.o ./sub.o -o app
colourso@colourso-virtual-machine:~/桌面/ch1linux/07make$ ls
add.c app div.o main.c makefile mult.o sub.o
add.o div.c head.h main.o mult.c sub.c
colourso@colourso-virtual-machine:~/桌面/ch1linux/07make$ ./app
Makefile test
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
10 / 5 = 2

make clean

上述make执行之后,会生成许多的.o文件,而我们并用不上。可以添加命令来将其清除。

在makefile文件末尾添加

1
2
clean:
rm $(objs) -f

然后输入命令make clean即可清除.o文件。

但若是本地也有一个文件也叫clean时,将无法清除。会显式make: “clean”已是最新。

原因是:make的规则是检查更新,clean没有依赖,则按照依赖的时间总是早于目标的时间,此时就不会再执行这条规则。

解决方法:使用伪目标.PHONY:clean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 获取当前路径下所有.c文件
src=$(wildcard ./*.c)
# 将.c文件转为.o文件
objs=$(patsubst %.c,%.o,$(src))
target=app

$(target):$(objs)
$(CC) $(objs) -o $(target)

#采用模式匹配,匹配*.o *.c
%.o:%.c
$(CC) -c $< -o $@

#伪目标,则其不会生成clean文件,从而解决冲突
.PHONY:clean
clean:
rm $(objs) -f

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!