C语言学习·编译过程

1
2
3
4
5
6
7
8
# 预处理器 cpp, 展开通文件, 宏替换, 去掉注释
gcc -E hello.c -o hello.i
# 编译器 gcc, c文件 -> 汇编文件
gcc -S hello.i -o hello.s
# 汇编器 as, 汇编文件 -> 二进制文件
gcc -c hello.s -o hello.o
# 链接器 ld, 将函数库中的代码组合到目标文件中
gcc hello.o -o hello

编译参数

  • 指定宏
    1
    2
    3
    4
    5
    6
    7
    #include <iostream>
    int main() {
    #ifdef DEBUG
    std::cout << "hello world\n";
    #endif
    return 0;
    }Copy to clipboardErrorCopied
1
gcc sum.c -o sum -D DEGUBCopy to clipboardErrorCopied
  • 输出警告信息

    1
    gcc sum.c -o sum -WallCopy to clipboardErrorCopied
  • 优化代码

    1
    2
    # 优化等级分为o1、 o2、 o3
    gcc sum.c -o sum -o3 Copy to clipboardErrorCopied
  • 生成调试信息

    1
    gcc sum.c -o sum -g

静态库

创建静态库

1
2
3
4
5
6
// include/head.h 
#ifndef HEAD_H
#define HEAD_H
int sum(int, int);
int mul(int, int);
#endifCopy to clipboardErrorCopied
1
2
3
4
5
6
7
8
9
10
// ./sum.c
#include "head.h"
int sum(int a, int b) {
return a + b;
}
// ./mul.c
#include "head.h"
int mul(int a, int b) {
return a * b;
}Copy to clipboardErrorCopied

生成.o文件

1
gcc -c *.c -I includeCopy to clipboardErrorCopied

打包静态库

1
ar rcs lib/libtest.a *.oCopy to clipboardErrorCopied

使用静态库

1
2
3
4
5
6
7
8
9
10
// ./main.c
#include "head.h"
#include "stdio.h"
int main()
{
int a = sum(2, 4);
int b = mul(2, 4);
printf("%d %d\n", a, b);
return 0;
}Copy to clipboardErrorCopied

使用静态库编译文件

1
2
gcc main.c lib/libtest.a -I include -o main
gcc main.c -I include -L lib -l test -o mainCopy to clipboardErrorCopied

查看查看文件中符号

1
nm mainCopy to clipboardErrorCopied

优缺点

静态库在使用的时候,会根据程序中用的到函数,将对应函数所在的.o打包到程序中。

  • 发布程序的时候,不需要提供对应的库。
  • 加载库的速度快

将这些库打包到可执行文件中同时也会造成:

  • 可执行程序的体积大
  • 库发生改变,需要重新编译程序

动态库

创建动态库

生成与位置无关的目标文件

1
gcc -fPIC -c *.c -I includeCopy to clipboardErrorCopied

打包动态库.so

1
gcc -shared -o lib/libtest.so *.o -I includeCopy to clipboardErrorCopied

使用动态库

1
2
gcc main.c lib/libtest.so -I include -o main
gcc main.c -L lib -l test -I include -o mainCopy to clipboardErrorCopied

在使用第二种方法,执行可执行文件时,会出现找不到路径的问题,这是因为动态库在加载的时候,需要动态链接器进行加载,可以适应ldd命令查看可执行程序需要的动态库。

动态链接器在加载库的时候,根据环境变量中的目录惊进行查找,可以添加自己的目录到环境变量中。

动态库的搜索路径的顺序

  • 编译目标代码时指定的动态库搜索路径
  • 环境变量LD_LIBRARY_PATH指定的动态库搜索路径
  • 配置文件/etc/ld.so.conf中指定的动态库搜索路径
  • 默认的动态库搜索路径/lib和/usr/lib

添加到LD_LIBRARY_PATH,这种方法是临时的。

1
export LD_LIBRARY_PATH=./lib:$LD_LIBRARY_PATHCopy to clipboardErrorCopied

可以将命令写到bash配置文件里面,这样每次加载shell之前,都会配置目录。

1
2
vim ~/.bashrc
export LD_LIBRARY_PATH=./lib:$LD_LIBRARY_PATHCopy to clipboardErrorCopied

另外也可以配置ld.so.conf

1
2
3
sudo vim /etc/ld.so.conf
/home/user/linux/lib
sudo ldconfig -vCopy to clipboardErrorCopied

查看可执行文件依赖的动态库

1
ldd appCopy to clipboardErrorCopied

优缺点

动态库只是在内存地址的共享库中标记,在程序运行时才加载。

  • 可执行程序体积小
  • 动态库更新,不需要重新编译
  • 发布程序需要将库提供给用户
  • 动态库没有打包应用程序中,加载速度相对慢

GDB调试

全称 功能
list l 显示多行源代码
break b 设置断点
delete d 删除断点
info i 描述程序的状态
run r 开始运行程序
display disp 跟踪查看某个变量,每次停下来都显示它的值
undisplay 停止追踪
step s 执行下一条语句,如果该语句为函数调用,则进入函数执行其中的第一条语句
finish 跳出本次函数
next n 执行下一条语句,如果该语句为函数调用,不会进入函数内部执行(即不会一步步地调试函数内部语句)
print p 打印内部变量值
ptype 打印变量类型
continue c 继续程序的运行,直到遇到下一个断点
u 跳出本次循环
set var name=v 设置变量的值
start st 开始执行程序,在main函数的第一条语句前面停下来
file 装入需要调试的程序
kill k 终止正在调试的程序
watch 监视变量值的变化
backtrace bt 产看函数调用信息(堆栈)
frame f 查看栈帧
quit q 退出GDB环境

生成调试程序

1
gcc *.c -o app -gCopy to clipboardErrorCopied

查看代码

1
l main.c:mainCopy to clipboardErrorCopied

设置断点

  • 设置断点
1
2
b 22
b 15 if i == 15Copy to clipboardErrorCopied
  • 查看断点
1
i bCopy to clipboardErrorCopied
  • 删除断点
1
2
d 1
d 1-5Copy to clipboardErrorCopied

执行程序

  • 开始
1
startCopy to clipboardErrorCopied
  • 单步
1
nCopy to clipboardErrorCopied
  • 下个断点
1
cCopy to clipboardErrorCopied

单步调试

  • 进入函数内部
1
sCopy to clipboardErrorCopied
  • 跳出本次循环
1
uCopy to clipboardErrorCopied
  • 退出函数
1
finishCopy to clipboardErrorCopied

查看变量

  • 查看变量的值
1
p varCopy to clipboardErrorCopied
  • 查看变量类型
1
ptype varCopy to clipboardErrorCopied
  • 设置变量的值
1
set var i=10Copy to clipboardErrorCopied

追踪变量

  • 追踪变量
1
2
display i
display jCopy to clipboardErrorCopied
  • 查看追踪的变量
1
i displayCopy to clipboardErrorCopied
  • 删除追踪的变量
1
undisplay 1 # 对应变量的编号Copy to clipboardErrorCopied

多进程调试

在fork之前设置mode。

  • set follow-fork-mode child,跟踪子进程
  • set follow-fork-mode parent 跟踪父进程

makefile

普通版

1
2
3
4
5
6
7
8
app: main.o sum.o sub.o
gcc main.o sum.o sub.o -o app
main.o: main.c
gcc -c main.c
sum.o: sum.c
gcc -c sum.c
sub.o: sub.c
gcc -c sub.cCopy to clipboardErrorCopied

自动变量

1
2
3
$@: 规则中的目标
$<: 规则中的第一个依赖
$^: 规则中的所有依赖Copy to clipboardErrorCopied
1
2
3
4
5
6
7
8
obj = main.o sum.o sub.o
target = app
# makefile系统变量
CC = gcc
$(target):$(obj)
$(CC) $(obj) -o $(target)
%.o: %.c
$(CC) -c $< -o $@Copy to clipboardErrorCopied

makefile函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
target = app
# 得到所有.c文件
src = $(wildcard ./*.c)
# 将.c替换为.o
obj = $(patsubst ./%.c, ./%.o, $(src))
CC = gcc
$(target): $(obj)
$(CC) $(obj) -o $(target)
%.o: %.c
$(CC) -c $< -o $@
# 声明伪目标
.PHONY:clean
clean:
rm $(obj) $(target)Copy to clipboardErrorCopied

静态模型

c194a9eg(objects)中获取, “%.o”表示以”.o”结尾的目标, 即foo.o bar.o, 依赖模式就是把”%.o”中的”%”拿来加上”.c”

1
2
3
4
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) &< -o $@Copy to clipboardErrorCopied

等价于

1
2
3
4
foo.o : foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.oCopy to clipboardErrorCopied
有用就打赏一下作者吧!