第3章 Linux系统C语言开发工具
3.1 第一个Linux环境下的C语言程序
- 略
3.2 编译C语言程序
gcc编译器
gcc [options] filename-list
# options
-ansi
-c
-l
-m 类型 # 根据给定CPU类型优化代码
-o 文件名
-O[级别] # 指定级别进行优化
-pg # 产生GNU剖析工具gprof使用的信息
-S # 跳过汇编和连接 并保留编译产生的汇编代码 .s
-v # 产生尽可能多的输出信息
- 略
gcc编译流程
源代码 .c
(base) cola@cola-CM:~/cola_study/LinuxProgramming/cha3$ cat 3-3.c
#include <stdio.h>
int main() {
int a, b, sum=0;
printf("a:=");
scanf("%d", &a);
printf("b:=");
scanf("%d", &b);
sum = a + b;
printf("a+b=%d\n", sum);
}
编译预处理 .i
- 只是简单的字符串处理,将
#
相关的语句利用宏汇编的语法处理#include <>
: 将对应的库直接复制到对应位置#define V var
: 将所有var直接宏替换为V条件编译
: …
gcc -E 3-3.c -o 3-3.i
(base) cola@cola-CM:~/cola_study/LinuxProgramming/cha3$ cat 3-3.i
编译后的.i
文件(部分)如下:
extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 858 "/usr/include/stdio.h" 3 4
extern int __uflow (FILE *);
extern int __overflow (FILE *, int);
# 873 "/usr/include/stdio.h" 3 4
# 2 "3-3.c" 2
# 3 "3-3.c"
int main() {
int a, b, sum=0;
printf("a:=");
scanf("%d", &a);
printf("b:=");
scanf("%d", &b);
sum = a + b;
printf("a+b=%d\n", sum);
}
- 加深理解:当只有
#include <stdio.h>
一句代码时,预处理的结果是到低8行结束
编译阶段 .s
- 检查规范型、语法错误,无误后翻译成汇编语言
gcc -S 3-3.i -o 3-3.s
汇编阶段 .o
- 将汇编语言转化为二进制代码(语言的翻译)
gcc -c 3-3.s -o 3.3.o
- 通过
objdump -d 3.3.o
可以将二进制机器码反转成汇编
链接阶段 .out
- 将需要的函数链接到文件
- 编译的时候发现
<stdio.h>
中只有scanf/printf
函数的声明,而没有其实现,故去寻找对应的链接库libc.so.6
,发现其函数实现部分!
- 编译的时候发现
- 最终结果可执行!
- 注
- 动态链接代码是在程序执行的时候加载 -> 节省系统开销
gcc编译器主要参数
- 常用参数
-v # gcc版本信息
-I dir # 添加头文件搜索路径
-L dir # 库文件搜索路径添加dir目录
-static # 静态链接库
-llibrary # 连接名为library的库文件
头文件 -I
#include <>
的头文件默认查找文件夹是/usr/include
-I dir
后会将dir
加入搜索路径
gcc test.c -I /root/ -o test
库文件 -L
gcc main.c -o main -L /root/lib -lsunq
# 其中 -L 只能指定文件夹
# -llibNAME指定库文件 -> -lsunq 则需要sunq这个库
告警和出错参数
参数说明
- todo
常用的
-Wall
: 允许发出gcc
提供的所有有用的告警信息
test.c: In function ‘main’:
test.c:4:12: warning: unused variable ‘tmp’ [-Wunused-variable]
4 | long long tmp = 1;
| ^~~
优化参数
- 编译器发现代码特点,进行重新组合
- 平衡:编译时间、可执行程序尺寸
-O0 # 不优化
-O1
-O2 # 常用
-Os # 优化尺寸
库函数
- 函数库:预先编译好的韩书记和
- 标准系统库文件一般存放在
/lib
和/usr/lib
- 命名规范
- 静态库:
libxxx.a
- 动态库:
libxx.so
- 静态库:
静态库 .a
文件结构
(base) cola@cola-CM:~/cola_study/LinuxProgramming/cha3/libx.a$ ls
ain.c pro1.c pro2.c pro.h
源代码
// cat pro1.c
#include <stdio.h>
void pro1(int a) {
printf("Hello, %d\n", a);
}
// cat pro2.c
#include <stdio.h>
void pro2(char* str) {
printf("Hello, %s\n", str);
}
// cat pro.h
void pro1(int);
void pro2(char*);
// cat main.c
#include "pro.h"
int main() {
pro1(123);
pro2("Cola!");
}
编译过程
gcc -E main.c
- 可以看到
main.c
中的include
只是将pro.h
里面的两个函数说明复制了进来
# 1 "main.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "main.c"
# 1 "pro.h" 1
void pro1(int);
void pro2(char*);
# 2 "main.c" 2
int main() {
pro1(123);
pro2("Cola!");
}
静态库
- 将
pro1.c
和pro2.c
中的函数实现进行归档
$ gcc -c pro1.c pro2.c && ls # 将pro1.c和pro2.c编译成二进制代码
main.c pro1.c pro1.o pro2.c pro2.o pro.h
$ ar crv libColaLib.a pro1.o pro2.o # 将两个文件进行归档
a - pro1.o
a - pro2.o
链接
- 将
main.o
与要使用的两个函数实现 链接 起来 以静态库的形式
$ gcc -c main.c -o main.o && gcc main.o -L . -lColaLib && ls
a.out libColaLib.a main.c main.o pro1.c pro1.o pro2.c pro2.o pro.h
$ ./a.out
Hello, 123
Hello, Cola!
- 链接所有
.o
文件: 等价于将main.o
与libColaLib.a
相链接
$ gcc main.o pro1.o pro2.o -o a2.out && ls a2.out && ./a2.out
a2.out
Hello, 123
Hello, Cola!
- 源代码
main.c
直接与需要使用的静态库相链接
$ gcc main.c -L . -lColaLib -o a3.out && ./a3.out
Hello, 123
Hello, Cola!
共享库 .so
- 通过
gcc main.c -lm
将main.c
与math.h
的共享库链接起来
代码
- 和静态库的相同
$ ls
main.c pro1.c pro2.c pro.h
源文件编译
$ gcc main.c pro1.c pro2.c -o main && ls && ./main
main main.c pro1.c pro2.c pro.h
Hello, 123
Hello, Cola!
- 这里是将所有源文件全部进行编译
- 特点是:所有文件从源文件进行编译,耗时较长
动态库的制作及使用
- 将
prox.c
制作为动态库 - 注意:
pro.h
只是告诉main.c
API原型,在编译预处理的时候使用(复制到main.i
)中,此后都不再使用了
步骤如下
- 组织源文件
$ tree -L 2
.
├── main.c
├── myInclude
│ └── pro.h
├── myLib
└── sourceCode
├── pro1.c
└── pro2.c
3 directories, 4 files
- 将
pro1.c
和pro2.c
制作为动态库到myLib/libpro.so
(base) cola@cola-CM:~/cola_study/LinuxProgramming/cha3/libx.so$ gcc ./sourceCode/* -fPIC -shared -o ./myLib/libpro.so (base) cola@cola-CM:~/cola_study/LinuxProgramming/cha3/libx.so$ tree -L 2 . ├── main.c ├── myInclude │ └── pro.h ├── myLib │ └── libpro.so └── sourceCode ├── pro1.c └── pro2.c 3 directories, 5 files
- 尝试1:直接编译
main.c
(base) cola@cola-CM:~/cola_study/LinuxProgramming/cha3/libx.so$ gcc main.c main.c:1:10: fatal error: pro.h: 没有那个文件或目录 1 | #include "pro.h" | ^~~~~~~ compilation terminated.
- 头文件的查找默认是当前文件夹和
/usr/include
下 - 将
./myInclude
包含进去
(base) cola@cola-CM:~/cola_study/LinuxProgramming/cha3/libx.so$ gcc main.c -I ./myInclude/ /usr/bin/ld: /tmp/ccV38oq1.o: in function `main': main.c:(.text+0xe): undefined reference to `pro1' /usr/bin/ld: main.c:(.text+0x1a): undefined reference to `pro2' collect2: error: ld returned 1 exit status
- 显示
main
中的两个函数未定义 - 可以看到仅仅是将
inlcude
的文件里面的函数原型复制了进去,没有函数定义
(base) cola@cola-CM:~/cola_study/LinuxProgramming/cha3/libx.so$ gcc -E main.c -I ./myInclude/ # 1 "main.c" # 1 "<built-in>" # 1 "<command-line>" # 31 "<command-line>" # 1 "/usr/include/stdc-predef.h" 1 3 4 # 32 "<command-line>" 2 # 1 "main.c" # 1 "./myInclude/pro.h" 1 void pro1(int); void pro2(char*); # 2 "main.c" 2 int main() { pro1(123); pro2("Cola!"); }
- 我们将函数定义放在了动态库
libpro.so
里面,so
ola@cola-CM:~/cola_study/LinuxProgramming/cha3/libx.so$ gcc main.c -I ./myInclude/ -L ./myLib/ -lpro -o main (base) cola@cola-CM:~/cola_study/LinuxProgramming/cha3/libx.so$ ./main ./main: error while loading shared libraries: libpro.so: cannot open shared object file: No such file or directory
- 编译成功了,但是
main
程序的时候发现找不到对应的动态库 - 解决方案见附录:[Linux 指定运行时动态库路径](##Linux 指定运行时动态库路径)
说明
- 链接本身不再包含函数代码,而是在运行时共享代码
- 搜索共享库的其他位置可以在文件
/ets/ld.so.conf
中配置 - 对应Windows中
.DLL
: 动态链接库 ->so
.LIB
: 静态库 ->.a
3.3 make工程文件
make命令
- 目的: 只编译哪些更新过的源代码文件(没有改动过的源代码不需要重新编译,而只需要重新链接)
Makefile
描述了系统中各个模块间的依赖关系
make [options] [make 工程文件]
# options
-d # 显示调试信息
-f # 指定make文件
-n # 不执行Makefile命令知识显示输出这些命令
-s # 执行但不显示任何信息
make规则
规则
target: dependency-list
command-list
- 目标生成
target
,先查看dependency-list
是否全都生成且源文件未变(检查时间戳)(依赖文件在目标文件之后生成则不变),如果变了则需要生成对应的依赖。 - 然后再执行
command-list
生成最终的目标文件
Makefile中的变量
自定义变量的创建和使用
# 创建环境变量
var=abcd
# 引用环境变量
echo ${var}
预定义变量
- todo
test_pre_var:
echo ${AR}
echo ${AS}
echo ${RM}
echo ${ARFLAGS}
自动变量
- todo
$@ # 目标文件的完整名称
$^ # 所有不重复的依赖文件 空格分开
$< # 第一个依赖文件名称
3.4 gdb调试工具
使用格式
gdb [选项] [可执行程序[core文件|进程ID]]
-
跟踪:指定程序的运行、程序运行错误产生的core文件、正在运行的程序进程ID
-
常用选项
-c core文件 -h # 列出命令行选项的简要介绍 -n # 忽略~/.gdbinit文件中指定的执行命令 -q # 禁止显示介绍信息和版权信息 -s 文件 # 使用保存在指定文件中的符号表
使用案例
源代码
main
中使用了myLib
中的两个函数- 还使用了
scanf
和printf
,要么#include<stdio.h>
,要么手动声明两个函数,如下
- 还使用了
// $ cat main.c
#include <myLib.h>
extern int scanf (const char *__restrict __format, ...) ;
extern int printf (const char *__restrict __format, ...);
int main() {
int a, b;
scanf("%d %d", &a, &b);
int c = add(a, b);
printf("c = %d\n", c);
show(a, b);
}
// $ cat myLib.h
int add(int, int);
void show(int, int);
// $ cat myLib.c
#include <stdio.h>
int add(int a, int b) {
return a+b;
}
void show(int a, int b) {
printf("a = %d\tb = %d\n", a, b);
}
编译
$ gcc main.c myLib.c -I.
$ ./a.out
1 2
c = 3
a = 1 b = 2
生成可调试程序
-g
生成可调试程序
(base) cola@cola-CM:~/cola_study/LinuxProgramming/cha3/gdb$ gcc main.c myLib.c -I. -g (base) cola@cola-CM:~/cola_study/LinuxProgramming/cha3/gdb$ gdb a.out GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2 Copyright (C) 2020 Free Software Foundation, Inc. ... Reading symbols from a.out... (gdb)
gdb调试
l(list)
显示代码: 用于查看行号,每次显示10行b 行号
breadk 行号
b 函数名
: 设置断点
(gdb) b main Breakpoint 1 at 0x1189: file main.c, line 5.
info b
: 查看断点信息
(gdb) info b Num Type Disp Enb Address What 1 breakpoint keep y 0x0000000000001189 in main at main.c:5
r
run
: 运行程序
(gdb) r Starting program: /home/cola/cola_study/LinuxProgramming/cha3/gdb/a.out Breakpoint 1, main () at main.c:5 5 int main() {
n
: 下一句(不进入子程序)
(gdb) n 7 scanf("%d %d", &a, &b); (gdb) n 12 12 8 int c = add(a, b); (gdb) 9 printf("c = %d\n", c); (gdb) c = 24 10 show(a, b);
p 变量名
: 查看变量值
(gdb) p a $1 = 12 (gdb) p b $2 = 12
-
set var=10
: 设置变量值 -
c
continue
: 继续运行 -
s
step
: 单步运行 -
q
quit
: 退出调试
附录
Linux 指定运行时动态库路径
搜索动态库的顺序
LD_LIBRARY_PATH
/etc/ld.so.cache
default path /lib, and then /usr/lib.
问题存在
- 文件结构
. ├── main.c ├── myInclude │ └── pro.h ├── myLib │ └── libpro.so └── sourceCode ├── pro1.c └── pro2.c
main.c
使用了pro.h
定义的函数
(base) cola@cola-CM:~/cola_study/LinuxProgramming/cha3/libx.so$ cat main.c #include "pro.h" int main() { pro1(123); pro2("Cola!"); }
- 指定头文件、库文件后编译成功,但是执行的时候发现找不到我自己创建的动态库文件
libpro.so
(base) cola@cola-CM:~/cola_study/LinuxProgramming/cha3/libx.so$ gcc main.c -I ./myInclude/ -L ./myLib/ -lpro -o main && ls && ./main main main.c myInclude myLib sourceCode ./main: error while loading shared libraries: libpro.so: cannot open shared object file: No such file or directory
- 问题在于默认的动态库搜索路径为
/lib
和/usr/lib
和当前路径的文件夹 - 给出以下几种解决方案
方案1: 添加到程序所在文件夹
(base) cola@cola-CM:~/cola_study/LinuxProgramming/cha3/libx.so$ cp ./myLib/libpro.so ./
(base) cola@cola-CM:~/cola_study/LinuxProgramming/cha3/libx.so$ tree -L 2
.
├── libpro.so
├── main
├── main.c
├── myInclude
│ └── pro.h
├── myLib
│ └── libpro.so
└── sourceCode
├── pro1.c
└── pro2.c
3 directories, 7 files
(base) cola@cola-CM:~/cola_study/LinuxProgramming/cha3/libx.so$ ./main
Hello, 123
Hello, Cola!
- 将
.so
文件复制到main
所在文件夹,而后执行成功
方案2: 指定动态库搜索路径
- 修改
LD_LIBRARY_PATH
# 恢复原本报错状态:删除当前文件夹的libpro.so文件
$ rm libpro.so
$ ls
main main.c myInclude myLib sourceCode
$ ./main
./main: error while loading shared libraries: libpro.so: cannot open shared object file: No such file or directory
# 修改$LD_LIBRARY_PATH
$ export LD_LIBRARY_PATH="/home/cola/cola_study/LinuxProgramming/cha3/libx.so/myLib":$LD_LIBRARY_PATH
$ echo $LD_LIBRARY_PATH
/home/cola/cola_study/LinuxProgramming/cha3/libx.so/myLib::/usr/local/lib
# 执行成功
$ ./main
Hello, 123
Hello, Cola!
- 其中修改
LD_LIBRARY_PATH
的方法是export LD_LIBRARY_PATH="MyLibDirPath":$LD_LIBRARY_PATH
方案3: 修改配置文件
- 在配置文件 /etc/ld.so.conf 中指定动态库搜索路径
- 再运行命令 ldconfig 使修改后的配置生效
步骤
- 恢复初始错误状态
$ export LD_LIBRARY_PATH=:/usr/local/lib
$ echo $LD_LIBRARY_PATH
:/usr/local/lib
$ ./main
./main: error while loading shared libraries: libpro.so: cannot open shared object file: No such file or directory
- 将搜索路径一行一行放入
/ets/ld.so.conf
文件中 (root) - 最后执行
ldconfig
(root)
(base) cola@cola-CM:~/cola_study/LinuxProgramming/cha3/libx.so$ ./main Hello, 123 Hello, Cola!
- 最后成功执行
方案3: 指定该程序动态库搜索路径
-Wl,-rpath=./myLib
: 指定动态库搜索路径(注意逗号前后没有空格)
(base) cola@cola-CM:~/cola_study/LinuxProgramming/cha3/libx.so$ gcc main.c -I ./myInclude/ -L ./myLib/ -lpro -o main -Wl,-rpath=./myLib (base) cola@cola-CM:~/cola_study/LinuxProgramming/cha3/libx.so$ ./main Hello, 123 Hello, Cola!
- 注意:这里用的是相对路径!若要在任意位置使用程序则需要使用绝对路径
ldd
- ldd查看程序依赖库 - 简书
ldd - print shared object dependencies
常用于查看缺少某个库文件
$ ldd main
linux-vdso.so.1 (0x00007fff91ff9000)
libpro.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb91c7a4000)
/lib64/ld-linux-x86-64.so.2 (0x00007fb91c9b3000)
- 输出的每一行针对某一个库文件
- 第一列表示某个依赖的库文件名称
- 第二列表示查找到的路径
- 第三列表示库加载的开始地址 -> 可以用于定位问题程序位置