LinuxC语言编程02:编译与调试C程序

编译器与调试器

编译器GCC

GCC编译过程

GCC编译过程分为4步:

  1. 预处理(Pre-Processing): 进行头文件展开,宏替换,条件编译,生成预处理文件*.i.
  2. 编译(Compiling): 将预处理文件编译成汇编代码*.s.
  3. 汇编(Assembling): 将汇编代码编译成二进制库文件*.o.
  4. 链接(Linking): 将二进制文件链接起来,生成可执行文件a.out.

在这里插入图片描述

GCC的运行

GCC命令格式如下:

gcc [options] [filenames]

常用参数如下:

参数意义
-o <filename>指定生成的可执行文件名为filename,默认生成的可执行文件名为a.out
-Wall显示所有警告
-E只激活预处理,需要将生成重定义到一个文件中
-S只激活预处理和编译,生成汇编代码
-c只激活预处理,编译和汇编,生成obj文件
-O优化编译链接,生成的可执行文件效率较高,但是编译,链接的速度会变慢
-I <dirname>指定优先在dirname目录下查找头文件,作用于预编译过程
-L <dirname>指定优先在dirname目录下查找库文件,作用链接过程

调试器GDB

在使用gcc命令编译时,加上参数-g,生成的可执行文件就可以用GDB进行调试了.使用gdb <可执行文件名>命令进入调试状态.

gcc hello.c -o hello
gdb hello

在调试界面,输入指令即可执行对应的功能:

指令简写功能
list [num]l [num]从显示第num行开始的源代码
break [num]b [num]在第num行设置断点
runr重新从头运行程序
continuec继续运行程序
nextn单步执行,不进入函数体内部
steps单步执行,进入函数体内部
print [var]p [var]打印变量var的值和地址
set <var>=<value>s <var>=<value>将变量var的值设为value
backtracebt查看当前的函数栈帧
info breakpointsinfo b查看当前设置的所有断点

构建工具make

使用GNU make工具可以管理项目并进行自动化编译.它可以根据时间戳自动发现更新过的文件,在每次执行编译时,只编译那些改动的代码文件,而不用完全编译,以减少编译的工作量.

关于make的使用,推荐一篇文章跟我一起写Makefile(https://seisman.github.io/how-to-write-makefile/index.html).

Makefile的基本结构

make工具根据项目目录下名为Makefile的配置文件中定义的编译规则来执行编译过程.一个Makefile文件是由很多条规则组成的,一条规则的语法如下:

target : prerequisites
	command
  • target: 生成的目标文件(object file)或一个标签(label).
  • prerequisites: 生成该target所依赖的文件或其它target.
  • command: 生成target过程中要执行的shell命令.

使用make <targetname>命令以执行targetname目标文件对应的指令(若存在依赖文件,则先执行依赖文件对应的指令);若不指定targetname,则构建Makefile文件中的第一个目标.

下面例子演示一个典型的Makefile文件的编写:

  • 编写C文件file1.c,file2.c,head.hmain.c,内容分别如下:

    void print_file1() {
        printf("this is file1\n");
    }
    
    void print_file2() {
        printf("this is file2\n");
    }
    
    void print1();
    void print2();
    
    #include<stdio.h>
    #include"head.h"
    
    int main(const int argc, const char *argv[]) {
        print1();
        print2();
    
        printf("this is main\n");
    
        return 0;
    }
    
  • 根据上述文件结构,编写Makefile文件如下:

    test : file1.o file2.o main.o		# 目标文件为可执行文件test
    	gcc file1.o file2.o main.o -o test
    file2.o : file2.c		# 目标文件为库文件file2.o
    	gcc -c -Wall file2.c -o file2.o 
    file1.o : file1.c		# 目标文件为库文件file1.o
    	gcc -c -Wall file1.c -o file1.o 
    main.o : main.c			# 目标文件为库文件main.o
    	gcc -c -Wall main.c -o main.o
    .PHONY : clean		# 声明目标clean是伪目标,没有生成对应的文件
    clean :		# 没有目标文件,该目标的作用是清理编译过程中产生的垃圾文件	
    	rm -f *.o test
    
  • 执行makemake test以编译生成可执行文件test,在这个过程中依次执行了test,file1.o,file2.o,main.o所对应的命令,命令行输出如下:

    gcc -c -Wall file1.c -o file1.o 
    gcc -c -Wall file2.c -o file2.o 
    gcc file1.o file2.o main.o -o test	
    gcc -c -Wall main.c -o main.o
    

    因为clean没有被关联依赖到,所以不会被执行.当程序执行完我们可以显示调用make clean来清理编译过程产生的垃圾文件.

clean这种不对应文件输出的目标被称为伪目标,为防止当前目录下的同名文件干扰编译过程,需要使用.PHONY声明其为伪目标.

make的运行参数

make命令的常用运行参数如下:

运行参数意义
-C <dir>指定读取目录<dir>下的Makefile文件
-f <filename>指定读取名为filename的Makefile文件
-I <dir>指定在目录<dir>下搜索被包含的Makefile文件
-n只打印要被执行的命令,但不执行它们(模拟执行)
-i在执行时忽略所有的错误
-s在执行时不显示执行的命令
-w在执行过程中若改变目录,则打印目录名

变量

在Makefile中定义和使用变量本质上是在进行字符串展开,类似C语言的宏定义.

变量的定义与使用

  • 变量的声明有两种方式:

    1. 简单方式: 使用:=运算符为变量赋值.使用这种赋值方式只能引用前面已经定义好的变量,如:

      x := foo
      y := $(x) bar
      x := later
      

      变量x的值为"later",变量y的值为"foo bar".

    2. 递归展开方式: 使用=运算符为变量赋值.这种定义方式可以引用在脚本任何地方定义的变量(包括后面定义的变量),这有可能带来循环引用的问题.

      例如下面的代码,变量A与变量B互相引用,带来了循环引用的问题,make会报错.

      A = $(B)
      B = $(A)
      
  • 使用变量的方式: 使用$(变量名)引用变量.

通过定义变量,我们可以改进之前的Makefile:

OBJS = file1.o file2.o main.o
CFLAGS = -c -Wall

test : $(OBJS)			# 目标文件为可执行文件test
	gcc $(OBJS) -o test
file2.o : file2.c		# 目标文件为库文件file2.o
	gcc $(CFLAGS) file2.c -o file2.o 
file1.o : file1.c		# 目标文件为库文件file1.o
	gcc $(CFLAGS) file1.c -o file1.o 
main.o : main.c			# 目标文件为库文件main.o
	gcc $(CFLAGS) main.c -o main.o
.PHONY : clean		# 声明目标clean是伪目标,没有生成对应的文件
clean :		# 没有目标文件,该目标的作用是清理编译过程中产生的垃圾文件	
	rm -f *.o test

自动化变量

自动化变量含义
$@目标文件的完整名称
$%若目标是库文件,则表示库文件的完整名称
$<第一个依赖文件的名称
$^所有不重复的目标依赖文件,以空格分隔
$?所有更新的不重复的目标依赖文件,以空格分隔
$+所有目标依赖文件(有可能重复),以空格分隔

通过使用自动化变量,我们可以改进之前的Makefile:

OBJS = file1.o file2.o main.o
CFLAGS = -c -Wall

test : $(OBJS)		
	gcc $^ -o $@
file2.o : file2.c
	gcc $(CFLAGS) $^.c -o $@
file1.o : file1.c	
	gcc $(CFLAGS) $^ -o $@
main.o : main.c		
	gcc $(CFLAGS) $^ -o $@

.PHONY : clean		
clean :		
	rm -f *.o test

环境变量

make在启动时会读取当前系统中的环境变量,并支持在Makefile文件中改变它们.因此要注意我们定义的变量名不应覆盖现有的环境变量名.

隐含规则

编译C程序的隐含规则

<n>.o目标的依赖目标会自动推导为<n>.c,并且其生成命令是$(CC) –c $(CPPFLAGS) $(CFLAGS).其中预定义变量CC为C编译器的名称,默认为cc,变量CPPFLAGSCFLAGS变量默认为空.

根据该隐含规则,我们可以改进之前的Makefile:

OBJS = file1.o file2.o main.o
CFLAGS = -c -Wall

test : $(OBJS)		
	gcc $^ -o $@
	
# 根据隐含规则,省略对file1.o,file2.o,main.o文件生成规则的声明

.PHONY : clean		
clean :				
	rm -f *.o test

链接库文件的隐含规则

<n>目标的依赖目标会自动推导为<n>.o,并且其生成命令是$(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS).其中预定义变量CC为C编译器的名称,默认为cc,变量CPPFLAGSCFLAGS变量默认为空.

例如对于下面的规则:

x : y.o z.o

并且当x.c,y.c,z.c文件都存在时,隐含规则将执行如下命令:

cc -c x.c -o x.o
cc -c y.c -o y.o
cc -c z.c -o z.o
cc x.o y.o z.o -o x
rm -f x.o
rm -f y.o
rm -f z.o

虚路径

变量VPATH用于指定虚路径,其值应该是一系列以:为分隔的路径,在编译时,make会到VPATH指定的目录下寻找依赖文件.

例如我们将上面例子中的文件file1.c移动到路径src1/下,文件file2移动到路径src2/下,文件main.c移动到路径main/下,则我们的Makefile文件应该变成这样:

CFLAGS = -c -Wall

# 依赖的目标都需要写出完整路径
test : src1/file1.o src2/file2.o main/main.o		
	gcc src1/file1.o src2/file2.o main/main.o -o $@
src2/file2.o : src2/file2.c
	gcc $(CFLAGS) $^.c -o $@
src1/file1.o : src1/file1.c	
	gcc $(CFLAGS) $^ -o $@
main/main.o : main/main.c		
	gcc $(CFLAGS) $^ -o $@

.PHONY : clean		
clean :		
	find ./ -name "*.o" -exec rm {} ;; rm test

当我们定义VPATH变量,指定寻找目标文件的路径后,Makefile会变得更简洁:

CFLAGS = -c -Wall
VPATH = src1:src2:main
# 依赖的目标都需要写出完整路径
test : file1.o file2.o main.o		
	gcc file1.o file2.o main.o -o $@
file2.o : file2.c
	gcc $(CFLAGS) $^.c -o $@
file1.o : file1.c	
	gcc $(CFLAGS) $^ -o $@
main.o : main.c		
	gcc $(CFLAGS) $^ -o $@

.PHONY : clean		
clean :		
	find ./ -name "*.o" -exec rm {} ;; rm test
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值