book_note for《Linux程序设计》chapter3 Linux系统C语言开发工具

第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.cpro2.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.olibColaLib.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 -lmmain.cmath.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.cAPI原型,在编译预处理的时候使用(复制到main.i)中,此后都不再使用了

步骤如下

  • 组织源文件
$ tree -L 2
.
├── main.c
├── myInclude
│   └── pro.h
├── myLib
└── sourceCode
    ├── pro1.c
    └── pro2.c

3 directories, 4 files
  • pro1.cpro2.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中的两个函数
    • 还使用了scanfprintf,要么#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 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)
  • 输出的每一行针对某一个库文件
  • 第一列表示某个依赖的库文件名称
  • 第二列表示查找到的路径
  • 第三列表示库加载的开始地址 -> 可以用于定位问题程序位置
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值