1 gcc的编译过程
从sum.c到生成可执行程序的过程
首先定义一个sum.c文件
#include <stdio.h>
#include "head.h"
#define DEBUG
int main(void){
int a = NUM1;
int aa;
int b = NUM2;
int sum = a + b;
#ifdef DEBUG
// 打印输出值
printf("this sum value is: %d + %d = %d\n", a, b, sum);
#endif
return 0;
}
再定义一个head.h文件
#ifndef __HEAD_H__
#define __HEAD_H__
#define NUM1 10
#define NUM2 20
#endif
1.1 预处理器
头文件展开,宏替换,去掉注释
root@iZ2ze9ftqv2b7zbety6qd8Z:~/learn_gcc# ls
head.h sum.c
root@iZ2ze9ftqv2b7zbety6qd8Z:~/learn_gcc# gcc -E sum.c -o sum.i
root@iZ2ze9ftqv2b7zbety6qd8Z:~/learn_gcc# ls
head.h sum.c sum.i
打开sum.i文件,在文件的最后,可以看到定义的main函数
# 6 "sum.c"
int main(void){
int a = 10;
int aa;
int b = 20;
int sum = a + b;
printf("this sum value is: %d + %d = %d\n", a, b, sum);
return 0;
}
main函数中的注释已经被去掉,NUM1和NUM2也被替换成了数字。
1.2 编译器处理
此过程是将C文件编译成汇编文件
root@iZ2ze9ftqv2b7zbety6qd8Z:~/learn_gcc# gcc -S sum.i -o sum.s
root@iZ2ze9ftqv2b7zbety6qd8Z:~/learn_gcc# ls
head.h sum.c sum.i sum.s
打开sum.s, 都是一些汇编指令
.file "sum.c"
.text
.section .rodata
.align 8
.LC0:
.string "this sum value is: %d + %d = %d\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl $10, -12(%rbp)
movl $20, -8(%rbp)
movl -12(%rbp), %edx
movl -8(%rbp), %eax
addl %edx, %eax
1.3 汇编器处理
此过程是将汇编文件编译成二进制文件
root@iZ2ze9ftqv2b7zbety6qd8Z:~/learn_gcc# gcc -c sum.s -o sum.o
root@iZ2ze9ftqv2b7zbety6qd8Z:~/learn_gcc# ls
head.h sum.c sum.i sum.o sum.s
以下是二进制文件中的内容
^?ELF^B^A^A^@^@^@^@^@^@^@^@^@^A^@>^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^P^C^@^@^@^@^@^@^@^@^@^@@^@^@^@^@^@@^@^M^@^L^@UH<89>åH<83>ì^PÇEô
^@^@^@ÇEø^T^@^@^@<8b>Uô<8b>Eø^AÐ<89>Eü<8b>Mü<8b>Uø<8b>Eô<89>ÆH<8d>=^@^@^@^@¸^@^@^@^@è^@^@^@^@¸^@^@^@^@ÉÃ^@^@^@^@this sum value is: %d + %d = %d
1.4 链接器处理
此过程是将函数库中相应的代码组合到目标文件中
root@iZ2ze9ftqv2b7zbety6qd8Z:~/learn_gcc# gcc sum.o -o app
root@iZ2ze9ftqv2b7zbety6qd8Z:~/learn_gcc# ls
app head.h sum.c sum.i sum.o sum.s
执行这个可执行程序,输出结果
root@iZ2ze9ftqv2b7zbety6qd8Z:~/learn_gcc# ./app
this sum value is: 10 + 20 = 30
1.5 gcc参数
1.5.1 指定文件目录
root@iZ2ze9ftqv2b7zbety6qd8Z:~/learn_gcc# tree
.
├── include
│ └── head.h
└── sum.c
1 directory, 2 files
root@iZ2ze9ftqv2b7zbety6qd8Z:~/learn_gcc# gcc sum.c -o app
sum.c:2:10: fatal error: head.h: No such file or directory
#include "head.h"
^~~~~~~~
指定文件目录
root@iZ2ze9ftqv2b7zbety6qd8Z:~/learn_gcc# gcc sum.c -I ./include -o app
root@iZ2ze9ftqv2b7zbety6qd8Z:~/learn_gcc# ls
app include sum.c
root@iZ2ze9ftqv2b7zbety6qd8Z:~/learn_gcc# ./app
this sum value is: 10 + 20 = 30
1.5.2 添加宏
先把宏注释掉然后编译
//#define DEBUG
重新编译sum.c,什么也不输出
root@iZ2ze9ftqv2b7zbety6qd8Z:~/learn_gcc# gcc sum.c -I ./include -o app
root@iZ2ze9ftqv2b7zbety6qd8Z:~/learn_gcc# ./app
添加宏
root@iZ2ze9ftqv2b7zbety6qd8Z:~/learn_gcc# gcc sum.c -o app -I include -D DEBUG
root@iZ2ze9ftqv2b7zbety6qd8Z:~/learn_gcc# ./app
this sum value is: 10 + 20 = 30
1.5.3 优化
-Wall 提示
-O 优化
-O 优化的最高级
root@iZ2ze9ftqv2b7zbety6qd8Z:~/learn_gcc# gcc sum.c -o app -I include -D DEBUG -o3 -Wall
sum.c: In function ‘main’:
sum.c:8:6: warning: unused variable ‘aa’ [-Wunused-variable]
int aa;
^~
在代码中,定义了aa,没有引用到。
生成调试信息
-g
root@iZ2ze9ftqv2b7zbety6qd8Z:~/learn_gcc# ls
app include sum.c
root@iZ2ze9ftqv2b7zbety6qd8Z:~/learn_gcc# gcc sum.c -o app1 -I include -D DEBUG -O3 -Wall -g
sum.c: In function ‘main’:
sum.c:8:6: warning: unused variable ‘aa’ [-Wunused-variable]
int aa;
^~
root@iZ2ze9ftqv2b7zbety6qd8Z:~/learn_gcc# ls -l
total 32
-rwxr-xr-x 1 root root 8304 Jan 26 16:59 app
-rwxr-xr-x 1 root root 11416 Jan 26 17:13 app1
drwxr-xr-x 2 root root 4096 Jan 26 16:45 include
-rw-r--r-- 1 root root 238 Jan 26 16:57 sum.c
可以看出-g 生成调试信息的app1比app占用的空间大
-rwxr-xr-x 1 root root 8304 Jan 26 16:59 app
-rwxr-xr-x 1 root root 11416 Jan 26 17:13 app1
1.5.4 gcc参数总结
-g:包含调试信息
-c:只编译子程序
-D:编译时定义宏
-Wall:提示更多警告信息
-E:生成预处理文件
-o:产生目标文件
编译优化: -OO: 没有优化, -O1:缺省值 -O3:优化级别最高
-I + 目录:指定头文件目录
2 静态库的制作和使用
2.1 定义.c文件和头文件
head.h
#ifndef HEAD_H
#define HEAD_H
#include <stdio.h>
int add(int, int);
int sub(int, int);
int mul(int, int);
#endif
add.c
#include <head.h>
int add(int a, int b)
{
int result = a + b;
return result;
}
sub.c
#include <head.h>
int sub(int a, int b)
{
return a - b;
}
mul.c
#include <head.h>
int mul(int a, int b)
{
return a * b;
}
目录结构如下
```cpp
root@iZ2ze9ftqv2b7zbety6qd8Z:~/static_library# tree
.
├── include
│ └── head.h
├── lib
└── src
├── add.c
├── mul.c
└── sub.c
3 directories, 4 files
12.2 将.c打包出.o
root@iZ2ze9ftqv2b7zbety6qd8Z:~/static_library# cd src
root@iZ2ze9ftqv2b7zbety6qd8Z:~/static_library/src# gcc *.c -c -I ../include
root@iZ2ze9ftqv2b7zbety6qd8Z:~/static_library/src# ls
add.c add.o mul.c mul.o sub.c sub.o
12.3 将.o打包成静态库.a
静态库的命名规则
lib + 库的名字 + .a
例如:libCaculate.a
root@iZ2ze9ftqv2b7zbety6qd8Z:~/static_library/src# ar rcs libCaculate.a *.o
root@iZ2ze9ftqv2b7zbety6qd8Z:~/static_library/src# ls
add.c add.o libCaculate.a mul.c mul.o sub.c sub.o
12.4 将.a文件移动到lib文件夹下
root@iZ2ze9ftqv2b7zbety6qd8Z:~/static_library/src# mv libCaculate.a ../lib
root@iZ2ze9ftqv2b7zbety6qd8Z:~/static_library/src# cd ..
查看文件夹结构
root@iZ2ze9ftqv2b7zbety6qd8Z:~/static_library# tree
.
├── include
│ └── head.h
├── lib
│ └── libCaculate.a
└── src
├── add.c
├── add.o
├── mul.c
├── mul.o
├── sub.c
└── sub.o
12.5 测试静态库的使用
1 创建main.c
#include <stdio.h>
#include <head.h>
int main(void)
{
int a = 5;
int b = 6;
printf("a = %d, b = %d\n", a, b);
int sumValue = add(a, b);
printf("add(a, b) = %d\n", sumValue);
int mulValue = mul(a, b);
printf("mul(a, b) = %d\n", mulValue);
int subValue = sub(a, b);
printf("mul(a, b) = %d\n", subValue);
return 0;
}
将之前的动态库和main.c打包出可执行程序caculate
gcc main.c lib/libCaculate.a -o caculate -I include/
也可以这么写
gcc main.c -I include -L lib -l Caculate -o caculate
整体目录结构
root@iZ2ze9ftqv2b7zbety6qd8Z:~/static_library# tree
.
├── caculate
├── include
│ └── head.h
├── lib
│ └── libCaculate.a
├── main.c
└── src
├── add.c
├── add.o
├── mul.c
├── mul.o
├── sub.c
└── sub.o
执行结果
root@iZ2ze9ftqv2b7zbety6qd8Z:~/static_library# ./caculate
a = 5, b = 6
add(a, b) = 11
mul(a, b) = 30
mul(a, b) = -1
3 静态库的优缺点
每个.c文件对应一个.o文件。所有的.o文件可打包出一个.a文件。最后打包出可执行程序时,是直接引入所依赖的.o文件。用到哪些.o文件,就将哪些.o文件打包到可执行程序中。用不到的.o则不会打包到程序中。
静态库的优点:
1 发布程序时,不需要提供对应的库
2 加载库的速度快
缺点:
1 库被打包到应用程序中,导致库的体积很大
2 库发生了改变,需要重新编译程序。
4 动态库的制作和使用
先看下整体文件结构
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library# tree
.
├── include
│ └── head.h
├── lib
└── src
├── add.c
├── mul.c
└── sub.c
4.1 将.c文件打包出.o文件
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library# cd src/
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library/src# gcc -fPIC -c *.c -I ../include
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library/src# ls
add.c add.o mul.c mul.o sub.c sub.o
4.2 将.o文件打包出.so文件
gcc -shared -o libCaculate.so *.o -I include
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library/src# ls
add.c add.o libCaculate.so mul.c mul.o sub.c sub.o
将动态库移动到上一级的lib文件夹中
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library/src# mv libCaculate.so ../lib/
查看整体结构
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library# tree
.
├── include
│ └── head.h
├── lib
│ └── libCaculate.so
└── src
├── add.c
├── add.o
├── mul.c
├── mul.o
├── sub.c
└── sub.o
4.3 打包生成app程序
添加main.c文件
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library# cat main.c
#include <stdio.h>
#include <head.h>
int main(void)
{
int a = 5;
int b = 6;
printf("a = %d, b = %d\n", a, b);
int sumValue = add(a, b);
printf("add(a, b) = %d\n", sumValue);
int mulValue = mul(a, b);
printf("mul(a, b) = %d\n", mulValue);
int subValue = sub(a, b);
printf("mul(a, b) = %d\n", subValue);
return 0;
}
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library# tree
.
├── app
├── include
│ └── head.h
├── lib
│ └── libCaculate.so
├── main.c
└── src
├── add.c
├── add.o
├── mul.c
├── mul.o
├── sub.c
└── sub.o
打包生成app程序
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library# gcc main.c lib/libCaculate.so -o app -I include/
执行生成的程序
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library# ./app
a = 5, b = 6
add(a, b) = 11
mul(a, b) = 30
mul(a, b) = -1
4.4 另一种打包方式
先移除生成好的app程序
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library# rm app
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library# ls
include lib main.c src
重新打包生成app
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library# gcc main.c -I include/ -L ./lib/ -l Caculate -o app
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library# ls
app include lib main.c src
4.5 解决链接失败
执行app
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library# ./app
./app: error while loading shared libraries: libCaculate.so: cannot open shared object file: No such file or directory
查看程序所依赖的所有的动态库
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library# ldd app
linux-vdso.so.1 (0x00007ffd2c593000)
libCaculate.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffa0a6dc000)
/lib64/ld-linux-x86-64.so.2 (0x00007ffa0accf000)
其中
/lib64/ld-linux-x86-64.so.2 (0x00007ffa0accf000)
是动态连接器,动态链接器会将以下动态库
linux-vdso.so.1 (0x00007ffd2c593000)
libCaculate.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffa0a6dc000)
加载到依赖程序中。
4.5.1 将动态库拷贝到系统lib中
动态库找不到,可以将生成的.so动态库放到系统lib目录中
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library# cp lib/libCaculate.so /lib
再执行
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library# ./app
a = 5, b = 6
add(a, b) = 11
mul(a, b) = 30
mul(a, b) = -1
此时再查看程序所依赖的动态库,可以正常链接到
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library# ldd app
linux-vdso.so.1 (0x00007ffe075ea000)
libCaculate.so => /lib/libCaculate.so (0x00007f948c6af000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f948c2be000)
/lib64/ld-linux-x86-64.so.2 (0x00007f948cab3000)
但是不推荐这么做,所以暂时先删掉
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library# rm /lib/libCaculate.so
4.5.2 临时把库导入系统环境变量
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library# echo $LD_LIBRARY_PATH
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library# export LD_LIBRARY_PATH=./lib
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library# echo $LD_LIBRARY_PATH
./lib
再来查看依赖,现在可以正常链接到库
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library# ldd app
linux-vdso.so.1 (0x00007ffed67fd000)
libCaculate.so => ./lib/libCaculate.so (0x00007f4d795c6000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4d791d5000)
/lib64/ld-linux-x86-64.so.2 (0x00007f4d799ca000)
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library# ./app
a = 5, b = 6
add(a, b) = 11
mul(a, b) = 30
mul(a, b) = -1
这么做只是临时的不是永久的,只适合测试用,终端关闭后,下次就链接不到了。
4.5.3 修改用户环境变量
这种方式不常用,永久设置。
打开.bashrc文件
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library# cd ~
root@iZ2ze9ftqv2b7zbety6qd8Z:~# vi .bashrc
再将lib目录导入到.bashrc文件中。然后需要重启终端。
这里就不写了,这个也不推荐。
4.5.4 添加到链接器的配置文件中
这种方式比较常用
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library# cd /etc
root@iZ2ze9ftqv2b7zbety6qd8Z:/etc# ls -l ld.so.conf
-rw-r--r-- 1 root root 34 Jan 27 2016 ld.so.conf
添加动态库的路径
include /etc/ld.so.conf.d/*.conf
#root/share_library/lib
然后更新链接库
Zroot@iZ2ze9ftqv2b7zbety6qd8Z:/etc# ld config -v
此时可以查看动态库
root@iZ2ze9ftqv2b7zbety6qd8Z:/etc# ldconfig -v
此时再来查看依赖是否正常
root@iZ2ze9ftqv2b7zbety6qd8Z:~/share_library# ldd app
linux-vdso.so.1 (0x00007ffdd13e1000)
libCaculate.so => ./lib/libCaculate.so (0x00007f917c3ea000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f917bff9000)
/lib64/ld-linux-x86-64.so.2 (0x00007f917c7ee000)
5 动态库的优缺点
优点:
1 执行程序体积小
2 动态库更新了,不需要更新编译程序
缺点:
1 发布程序的时候,需要将动态库提供给用户
2 动态库没有被打包到应用程序中,加载的速度相对较慢