网络编程系列文章:
第1章 Linux系统编程入门(上)
第1章 Linux系统编程入门(下)
第2章 Linux多进程开发(上)
第2章 Linux多进程开发(下)
第3章 Linux多线程开发
第4章 Linux网络编程
第一章 Linux系统编程入门(上)
1.1 GCC
- GCC 原名为 GNU C 语言编译器(
GNU C Compiler
) - GCC (
GNU Compiler Collection
, GNU 编译器套件)是由 GNU 开发的编程语言译器。 GNU 编译器套件包括C
、C++
、Objective-C
、Java
、Ada
和Go
语言前端,也包括了这些语言的库(如libstdc++
,libgcj
等) - GCC 不仅支持
C
的许多“方言”,也可以区别不同的 C 语言标准;可以使用命令行选项来控制编译器在翻译源代码时应该遵循哪个 C 标准。例如,当使用命令行参数std=c99
启动 GCC 时,编译器支持 C99 标准。 - 安装命令
sudo apt install gcc g++
(版本 > 4.8.5) - 查看版本
gcc/g++ -v/--version
编程语言的发展
GCC 工作流程
gcc 和 g++ 的区别
gcc
和g++
都是 GNU( 组织 ) 的一个编译器。- 误区一:
gcc
只能编译c
代码,g++
只能编译c++
代码。两者都可以,请注意:- 后缀为
.c
的,gcc
把它当作是 C 程序,而g++
当作是 c++ 程序 - 后缀为
.cpp
的,两者都会认为是 C++ 程序,C++
的语法规则更加严谨一些 - 编译阶段,
g++
会调用gcc
,对于C++
代码,两者是等价的,但是因为gcc
命令不能自动和C++
程序使用的库联接,所以通常用g++
来完成链接,为了统 一起见,干脆编译/链接统统用g++
了,这就给人一种错觉,好像cpp
程序只能用g++
似的
- 后缀为
- 误区二:
gcc
不会定义__cplusplus
宏,而g++
会- 实际上,这个宏只是标志着编译器将会把代码按
C
还是C++
语法来解释 - 如上所述,如果后缀为
.c
,并且采用gcc
编译器,则该宏就是未定义的,否则,就是已定义
- 实际上,这个宏只是标志着编译器将会把代码按
- 误区三:编译只能用
gcc
,链接只能用g++
- 严格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用
gcc
/g++
,而链接可以用g++
或者gcc lstdc++
。 gcc
命令不能自动和 C++ 程序使用的库联接,所以通常使用g++
来完成联接。 但在编译阶段, g++ 会自动调用 gcc ,二者等价
- 严格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用
GCC 常用参数选项

# 1. 将test.c文件预处理为test.i文件
gcc test.c -E -o test.i
# 2. 编译,生成test.s文件
gcc test.i -S -o test.s
# 3. 汇编,生成test.o文件
gcc test.s -s -o test.o
# 4. 执行
./test.o
# 也可以直接运行下面代码,生成 test.o文件
gcc test.c
1.2 静态库
(1)什么是库
-
库文件是计算机上的一类文件,可以简单的把库文件看成一种代码仓库,它提供给使用
者一些可以直接拿来用的变量
、函数
或类
。 -
库是特殊的一种程序,编写库的程序和编写一般的程序区别不大,只是库不能单独运行。
-
库文件有两种,静态库 和 动态库(共享库),区别是:
- 静态库在程序的 链接阶段被复制到了程序中;
- 动态库在 链接阶段 没有 被复制到程序中,而是程序在 运行 时由系统动态加载到内存中供程序调用。
-
库的好处:
- 代码保密
- 方便部署和分发
(2)静态库的制作
- 命名规则:
- Linux : libxxx.a
lib
: 前缀(固定)
xxx
: 库的名字,自己起
.a
: 后缀(固定) - Windows : libxxx.lib
- Linux : libxxx.a
- 静态库的制作:
gcc
获得.o
文件- 将
.o
文件打包,使用ar
工具(archive)
ar rcs libxxx.a xxx.o xxx.o
r
:将文件插入备存文件中
c
:建立备存文件
s
:索引
# 1. 编译汇编(不链接)获得 `.o` 文件
gcc -c add.c div.c mult.c sub.c
# 2. 将 .o 文件打包
ar rcs libcalc.a add.o sub.o mult.o div.o
(3)静态库的使用
gcc main.c -o app -I ./include/ -l calc -L ./lib/
1.3 动态库
(1)动态库的制作
-
命名规则:
- Linux : libxxx.so
lib
: 前缀(固定)
xxx
: 库的名字,自己起
.so
: 后缀(固定) - Windows : libxxx.dll
- Linux : libxxx.so
-
动态库的制作:
-
gcc 获得
.o
文件,得到和位置无关的代码gcc -c -fpic/-fPIC a.c b.c
-fpic
用于编译阶段,产生的代码没有绝对地址,全部用 相对地址,这正好满足了共享库的要求,共享库被加载时地址不是固定的。如果不加-fpic
,那么生成的代码就会与位置有关,当进程使用该.so
文件时都需要重定位,且会产生成该文件的副本,每个副本都不同,不同点取决于该文件代码段与数据段所映射内存的位置。 -
gcc 得到动态库
gcc -shared a.o b.o -o libcalc.so
-
(2)动态库的使用
gcc main.c -o main -I ./include/ -l calc -L ./lib/
# 运行,会报错,找不到共享库
./main
# error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory
工作原理
-
静态库:
GCC
进行链接时,会把静态库中代码 打包到 可执行程序中 -
动态库:
GCC
进行链接时,动态库的代码 不会被打包 到可执行程序中 -
程序启动 之后,动态库 会被 动态加载 到内存中,通过
ldd
(list dynamic dependencies
)命令检查动态库依赖关系 -
如何定位共享库文件呢?
当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道 绝对路径。此时就需要系统的 动态载入器 来获取该绝对路径。对于elf
格式的可执行程序,是由ld-linux.so
来完成的,它先后搜索elf
文件的DT_RPATH 段
(无法修改) —>环境变量 LD_LIBRARY_PATH
—>/etc/ld.so.cache 文件列表
—>/lib/
,/usr/lib
目录找到库文件后将其载入内存。
(3)解决动态库加载失败问题
修改上面的搜索路径
a. 修改环境变量 LD_LIBRARY_PATH
-
临时配置
-
在终端直接配置。(关闭当前会话框,将失效)
# 原来的拼接上动态库的绝对路径 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/rmzh/projects/Linux/lesson04/library/lib
-
-
永久配置
-
用户级配置
# 修改 ~/.bashrc 文件 vim ~/.bashrc # 在最后面加入: export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/rmzh/projects/Linux/lesson04/library/lib # 保存退出后,使生效 source ~/.bashrc
-
系统级别
# 修改 /etc/profile 文件 sudo vim /etc/profile # 在最后面加入: export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/rmzh/projects/Linux/lesson04/library/lib # 保存退出后,使生效 source /etc/profile
-
b. 修改 /etc/ld.so.cache
文件
-
该文件是二进制文件,不能直接修改,可以间接修改。
# 修改 /etc/ld.so.conf 文件 sudo vim /etc/ld.so.conf # 加入: /home/rmzh/projects/Linux/lesson04/library/lib # 保存退出后,更新生效 sudo ldconfig
c. 将动态库文件放到 / lib/
,/usr/lib/
目录下
- 不推荐使用!
- 因为这两个目录下本身就包含系统自带的库文件,可能名称会用相同,会覆盖系统文件。
1.4 静态库与动态库的对比
(1)程序编译成可执行程序的过程
- 预处理:头文件展开、删除注释、宏的替换
- 编译:编译成汇编代码
- 汇编:汇编成目标代码
- 链接:处理静态库和动态库,链接成可执行文件
(2)静态库制作过程
(3)动态库制作过程
(4)静态库的优缺点
优点 | 缺点 |
---|---|
静态库被打包到应用程序中加载速度快 | 消耗系统资源,浪费内存 |
发布程序无需提供静态库,移植方便 | 更新、部署、发布麻烦 |
(5)动态库的优缺点
优点 | 缺点 |
---|---|
可以实现进程间资源共享(共享库) | 加载速度比静态库慢 |
更新、部署、发布简单 | 发布程序时需要提供依赖的动态库 |
可以控制何时加载动态库 |
库比较小 的话使用 静态库,比较大 就使用 动态库。
1.5 Makefile
(1)什么是 Makefile
- 一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,Makefile 文件 定义了 一系列的规则 来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 Makefile 文件就像一个 Shell 脚本 一样,也可以执行操作系统的命令。
- Makefile 带来的好处就是 “自动化编译”,一旦写好,只需要一个
make
命令,整个工程完全自动编译,极大的提高了软件开发的效率。make
是一个命令工具,是一个解释 Makefile 文件中指令的命令工具,一般来说,大多数的 IDE 都有这个命令,比如 Delphi 的make
, Visual C++ 的nmake
, Linux 下 GNU 的make
。
(2)Makefile 文件命名和规则
-
文件命名
makefile
或者Makefile
-
Makefile 规则
- 一个
Makefile
文件中可以有一个或者多个规则
目标 … : 依赖 …
命令(Shell 命令)
…- 目标:最终要生成的文件(伪目标除外)
- 依赖:生成目标所需要的文件或是目标
- 命令:通过执行命令对依赖操作生成目标(命令前必须
Tab
缩进)
Makefile
中的其它规则一般都是为 第一条规则 服务的。
- 一个
-
例如:
# 创建 Makefile 文件 vim Makefile # 添加以下内容(第一种) 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 make
(3)工作原理
-
命令在执行之前,需要先检查规则中的 依赖是否存在
-
如果存在,执行命令
-
如果不存在,向下检查其它的规则,检查有没有一个规则是用来生成这个依赖的,
如果找到了,则执行该规则中的命令# 第二种 # 该效率优于第一种,如果其中一个依赖改变,只需编译这一个即可 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
-
-
检测更新,在执行规则中的命令时,会比较目标和依赖文件的时间(修改更新的时间)
- 如果 依赖的时间 比 目标的时间 晚,需要重新生成目标
- 如果 依赖的时间 比 目标的时间 早,目标不需要更新,对应规则中的命令不需要被执行。
(4)变量
- 自定义变量
- 变量名 = 变量值 :
var=hello
- 变量名 = 变量值 :
- 预定义变量
- AR : 归档维护程序的名称,默认值为
ar
- CC : C 编译器的名称,默认值为
cc
- CXX : C++编译器的名称,默认值为
g++
- $@ : 目标的完整名称
- $< : 第一个依赖文件的名称
- $^ : 所有的依赖文件
- AR : 归档维护程序的名称,默认值为
- 获取变量的值
$(变量名)
: $(var)
app:main.c a.c b.c
gcc -c main.c a.c b.c
# 自动变量只能在规则的命令中使用
app:main.c a.c b.c
$(CC) -c $^ -o $@
# 定义变量
src=add.o sub.o mult.o div.o main.o
target=app
$(target):$(src)
$(CC) $(src) -o $(target)
(5)模式匹配
add.o:add.c
gcc -c add.c
sub.o:sub.c
gcc -c sub.c
mult.o:mult.c
gcc -c mult.c
div.o:div.c
gcc -c div.c
main.o:main.c
gcc -c main.c
%.o:%.c
%
: 通配符,匹配一个字符串- 两个
%
匹配的是同一个字符串
# 上面的5种都能匹配
%.o:%.c
gcc -c $< -o $@
(6)函数
-
$(wildcard PATTERN...)
-
功能:获取指定目录下指定类型的文件列表
-
参数:
wildcard
为函数名,PATTERN
指的是某个或多个目录下的对应的某种类型的文件,如果有多个目录,一般使用空格间隔 -
返回:得到的若干个文件的文件列表,文件名之间使用空格间隔
-
示例:
$(wildcard *.c ./sub/*.c)
返回值格式:a.c b.c c.c d.c e.c f.c
# add.c sub.c mult.c div.c main.c src=$(wildcard ./*.c)
-
-
$(patsubst <pattern>, <replacement>, <text>)
-
功能:查找
<text>
中的单词 (单词以“空格”、“ Tab” 或 “回车” “换行”分隔) 是否符合 模式<pattern>
,如果匹配的话,则以<replacement>
替换。 -
<pattern>
可以包括通配符%
,表示任意长度的字串。如果<replacement>
中也包含%
,那么 ,<replacement>
中的这个%
将是<pattern>
中的那个%
所代表的字串。 (可以用\
来转义,以\%
来表示真实含义的%
字符) -
返回:函数返回被替换过后的字符串
-
示例:
$(patsubst %.c, %.o, x.c bar.c)
返回值格式:x.o bar.o
# 第二种 # add.c sub.c mult.c div.c main.c src=$(wildcard ./*.c) objs=$(patsubst %.c, %.o, $(src)) target=app $(target):$(objs) $(CC) $(objs) -o $(target) %.o:%.c gcc -c $< -o $@ # 删除.o文件 # 伪目标 .PHONY:clean clean: rm $(objs) -f # make调用在终端输入:make clean
-
注:仅供学习参考,如有不足,欢迎指正!