Linux C 预备知识

特别项: nm [库文件或者可执行文件] #可查看库文件或可执行文件内含的函数。 ldd [可执行文件] #查看改程序用到的动态库的状态(是否找到动态库)。 重点: 结构体的对齐原则

  1. 结构体中元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。从结构体存储的首地址开始,每一个元素放置到内存中时,它都会认为内存是以它自己的大小来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始(以结构体变量首地址为0计算)。
  2. 在经过第一原则分析后,检查计算出的存储单元是否为所有元素中最宽的元素的长度的整数倍,是,则结束;若不是,则补齐为它的整数倍。

1.gcc编译器

  • gcc参数:
    • -I [目录] #指定头文件路径(可无空格间隔)。
    • -D [宏名] #编译时指定宏(比如打印debug信息的时候使用)。
    • -L [目录] #指定库目录。
    • -l [库名] #指定需要引入的库名(需要去头去尾).
    • -Ox #指定优化等级为x, x等级分为0,1,2,3,0表示不优化,3最高,x数值超过3也只相当于3。
    • -o [文件名] #指定生成的文件名。
    • -Wall #编译时提示警告信息(warring)。
    • -g #添加调试信息,gdb调试时使用,编译后文件会比正常编译大很多。
    • -E,-S,-c #可参照下边的编译流程内容。常用-c,动静态链接库时使用较多。

编译流程: 预处理(cpp) -> 编译(gcc) -> 汇编(as) -> 链接(ld)

  • gcc -E [源代码文件名] -o [生成文件名] #预处理(.i)
  • gcc -S [预处理生成文件名] -o [生成文件名] #编译(.s)
  • gcc -c [编译生成文件名] -o [生成文件名] #汇编(.o)
  • gcc [编译生成文件名] -o [生成文件名] #链接(生成可执行文件)

2.静态库

优点: 寻址方便,速度快,直接打包到可执行程序中。 缺点: 如需变更,需要重新编译,体积大,发布时可直接发布程序。

  1. 命名规则: lib[库名].a
  2. 生成步骤:
    1. 生成.o文件: gcc -c xxx.c
    1. 打包.o文件: ar rcs [库名(libTest.a)] [所有.o]
  1. 发布头文件和生成的库文件。
  2. 使用:
    1. 包含头文件,调用函数。
    1. 编译:
      1. 方法一: 编译时加上静态库的路径,如:
1
gcc main.c lib/libTest.a -I./include -o app 
      1. 方法二: 格式: gcc [源文件] -L [库目录] -l [需要引入的库名(去头去尾)] #例如:
1
gcc main.c -L lib -l Test -I./include -o app 

3.动态库(共享库)

优点: 体积小,更新方便,可直接替换库文件。 缺点: 发布程序时需要同时发布动态库,加载速度慢一点。

  1. 命名规则: lib[库名].so
  2. 生成步骤:
    1. 生成与位置无关的代码(生成与位置无关的.o(gcc加参数 -fPIC)),例如:
1
gcc -fPIC -c xxx.c
    1. 打包: gcc -shared -o lib[库名].so *.o -Iinclude
  1. 发布头文件和生成的库文件。
  2. 使用:
    1. 包含头文件,调用函数。
    1. 编译:
      1. 方法一: 编译时加上动态库的路径,如:
1
gcc main.c lib/libTest.so -I./include -o app 
      1. 方法二: 格式: gcc [源文件] -L [库目录] -l [需要引入的库名(去头去尾)] #例如:
1
gcc main.c -L lib -l Test -I./include -o app 
    1. 如果提示没有库需要配置动态库。
    • 解决方案:
      1. 将动态库放到/lib中(容易覆盖系统的库,不推荐)。
      1. 添加动态库目录到环境变量LD_LIBRARY_PATH中(可临时,也可写入环境变量配置文件)。
      1. 添加动态库目录到ld.so.conf.d,并更新ld(sudo ldconfig -v, -v是为了显示输出信息)。

4.gdb

需要先gcc -g 生成可执行文件。

  1. gdb [可执行文件] #进入调试
  2. gdb命令:
  • [无命令] #无命令时执行最后一次执行的命令。
  • l [源文件名]:[行数或者函数名] #查看源代码,没有后面的参数时默认当前调试文件(最初为main函数文件)处前10行或者继续紧接上文显示后续10行。
  • b [行数] #等同于break命令,在某行打一个断点。
  • b [行数] if [判断] #条件断点,当条件成立时才断点,比如当循环内i等于20时断点:
    • b 15 if i=20
  • d [断点id] #删除断点,断点id可通过i查看。
  • i [gdb命令] #info,查看信息,比如查看断点信息: i b,查看自动追踪变量信息: i display,
  • start #开始运行。
  • n #next,下一步。
  • s #进入到函数体内部。
  • c #continue, 一直执行到断点时。
  • p [变量名] #查看变量的值。
  • ptype [变量名] #查看变量类型。
  • display [变量名] #添加自动追踪的变量(添加的变量会在每次答应执行到的位置时打印这些变量值)。
  • undisplay [追踪变量的id] #取消自动追踪,追踪变量的id可以通过i display查看。
  • u #跳出当前循环。
  • finish #跳出当前函数,需要先取消函数内的断点。
  • set var [变量名]=[变量值] #设置变量值。
  • quit #退出调试。

5.makefile(注意不能是空格缩进而是tab缩进)

  1. 规则:
  • 规则三要素: 目标、依赖、命令
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#[目标]:[依赖]
#    [命令]
#(命令前要有缩进)例如: 
#简陋版:
app:main.c add.c del.c
    gcc main.c add.c del.c -o app
#好一点版本(重新编译时只会编译修改过的文件):
app:main.o add.o del.o
    gcc main.o add.o del.o -o app
main.o:main.c
    gcc -c main.c -o main.o
add.o:add.c
    gcc -c add.c -o add.o
del.o:del.c
    gcc -c del.c -o del.o
  1. [变量名]=[变量值] #定义变量,例子:
1
2
#可用$(obj)去替换上方main.o add.o del.o
obj=main.o add.o del.o
  1. 模式规则:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#可将上方好一点版本中各种.o规则换成如下:
obj=main.o add.o del.o
app:$(obj)
    gcc %^ -o %@
%.o:%.c
    gcc -c %< -o %@
#解析:
#当app的依赖找不到时会自动匹配下方公式"%.o:%.c"
#以下部分只适用于命令
# %<    匹配当前规则的第一条依赖
# %@    匹配当前规则的目标
# %^    匹配当前规则的所有依赖
  1. makefile维护的一些变量(一般都是大写的,用户可以修改这些默认值)
  • CC : 默认值cc
  • CPPFLAGS : 预处理器需要的选项,如: -I
  • CFLAGS : 编译时使用的参数 -Wall -g -c
  • LDFLAGS : 链接库时使用的参数 -L -l
  1. makefile中的函数:
  • 调用方式: [变量名]=$([函数名] [参数])
  • wildcard #获取某目录下的文件,可使用通配符,如:
1
src=$(wildcard ./*.c)
  • patsubst #替换字符串,如:
1
2
3
#替换.c为.o
#注意参数之间有逗号,函数名与参数之间没有逗号。
obj=$(patsubst ./%.c, ./%.o, $(src))

6.clean目标:

1
2
3
4
5
#删除编译文件,仅当make时指定目标clean执行。
#---省略上文---
.PHONY:clean    #声明clean为伪目标,否则会提示目标已是最新。
clean:
    rm $(obj) app -f
  1. .PHONY:[目标] #声明目标为伪目标,不会再比较是否已是最新。
  2. - #在命令前加上"-“表示如果该命令执行失败时忽略该命令继续执行,否则会直接终止。