Linux_C 总结
⼀、GCC 的使⽤
1.编译流程
GCC 编译器在编译⼀个C语⾔程序时需要经过以下 4 步:
1. 将C语⾔源程序预处理,⽣成.i ⽂件。
预处理:代码在交给编译器之前,会先由预处理器进⾏⼀些⽂本替换⽅⾯的操作,例如宏展开、⽂件包含、删除部分代码等。在正常的情况下,GCC 不会保留预处理阶段的输出⽂件,也即.i ⽂件。然⽽,可以利⽤-E 选项保留预处理器的输出⽂件,以⽤于诊断代码。-E 选项指⽰ GCC 在预处理完毕之后即可停⽌。因为头⽂件可能相当⼤,如果源⽂件包括了多个头⽂件,那么它的预处理器输出可能会庞杂难读。使⽤-C 选项会很有帮助,这个选项可以阻⽌预处理器删除源⽂件和头⽂件中的注释:
2. 预处理后的.i⽂件编译成为汇编语⾔,⽣成.s ⽂件。
编译器的核⼼任务是把C程序翻译成机器的汇编语⾔(assembly language)。汇编语⾔是⼈类可以阅读的编程语⾔,也是相当接近实际机器码的语⾔。由此导致每种 CPU 架构都有不同的汇编语⾔。
实际上,GCC 是⼀个适合多种 CPU 架构的编译器,不会把C程序语句直接翻译成⽬标机器的汇编语⾔,⽽是在输⼊语⾔和输出汇编语⾔之间,利⽤⼀个中间语⾔,称为 RegisterTransfer Language(简称 RTL,寄存器传输语⾔)。借助于这个抽象层,在任何背景下,编译器可以选择最经济的⽅式对给定的操作编码。
⽽且,在交互⽂件中针对⽬标机器的抽象描述,为编译器重新定向到新架构提供了⼀个结构化的⽅式。但是,从 GCC ⽤户⾓度来看,我们可以忽略这个中间步骤。通常情况下,GCC 把汇编语⾔输出存储到临时⽂件中,并且在汇编器执⾏完后⽴刻删除它们。但是可以使⽤-S 选项,让编译程序在⽣成汇编语⾔输出之后⽴刻停⽌。如果没有指定输出⽂件名,那么采⽤-S 选项的 GCC 编译过程会为每个被编译的输⼊⽂件⽣成以.s 作为后缀的汇编语⾔⽂件。如果想把C语⾔变量的名称作为汇编语⾔语句中的注释,可以加上-fverbose-asm 选项:
3. 将汇编语⾔⽂件经过汇编,⽣成⽬标⽂件.o ⽂件(机器语⾔)。
-c 选项表⽰编译、汇编指定的源⽂件(也就是编译源⽂件),但是不进⾏链接。使⽤-c 选项可以将每⼀个源⽂件编译成对应的⽬标⽂件。
4. 将各个模块的.o ⽂件链接起来⽣成⼀个可执⾏程序⽂件。gcc -E test.c -o test.i
1gcc -E -C test.c -o test.i
1gcc -S test.i -o test.s
1gcc -S -fverbose-asm test.i -o test.s
1gcc -c test.o -o test.o
1gcc main.o test.o -o main.out
1
.i ⽂件、.s ⽂件、.o ⽂件可以认为是中间⽂件或临时⽂件,如果使⽤ GCC ⼀次性完成C语⾔程序的编译,那么只能看到最终的可执⾏⽂件,这些中间⽂件都是看不到的,因为 GCC 已经经它们删除了。
⼆、Makefile
简介:
在软件开发中,make通常被视为⼀种软件构建⼯具。该⼯具主要经由读取⼀种为“makefile”或“Makefile”的⽂件来实现软件的⾃动化建构。它会通过⼀种被称之为“target”概念来检查相关⽂件之间的依赖关系,这种依赖关系的检查系统⾮常简单,主要通过对⽐⽂件的修改时间来实现。在⼤多数情况下,我们主要⽤它来编译源代码,⽣成结果代码,然后把结果代码连接起来⽣成可执⾏⽂件或者库⽂件。
只要我们的makefile写得够好,所有的这⼀切,我们只⽤⼀个make命令就可以完成,make命令会⾃动智能地根据当前的⽂件修改的情况来确定哪些⽂件需要重编译,从⽽⾃动编译所需要的⽂件和链接⽬标程序。
Makefile 的规则在讲述这个makefile之前,还是让我们先来粗略地看⼀看makefile的规则。
target
可以是⼀个object file(⽬标⽂件),也可以是⼀个执⾏⽂件,还可以是⼀个标签(label)。对于标签这种特性,在后续的“伪⽬标”章节中会有叙述。
prerequisites ⽣成该target所依赖的⽂件和/或target
command
该target要执⾏的命令(任意的shell命令)target ... : prerequisites ... command ... ...
1
2
3
4
这是⼀个⽂件的依赖关系,也就是说,target这⼀个或多个的⽬标⽂件依赖于prerequisites中的⽂件,其⽣成规则定义在command中。说⽩⼀点就是说:
实际的例⼦
1.初始版本
如果⼀个⼯程有3个头⽂件和8个c⽂件,编译要求如下
1. 如果这个⼯程没有编译过,那么我们的所有c⽂件都要编译并被链接。
2. 如果这个⼯程的某⼏个c⽂件被修改,那么我们只编译被修改的c⽂件,并链接⽬标程序。
3. 如果这个⼯程的头⽂件被改变了,那么我们需要编译引⽤了这⼏个头⽂件的c⽂件,并链接⽬标程序。
对应的MakeFile⽂件如下反斜杠( \ )是换⾏符的意思。这样⽐较便于makefile的阅读。我们可以把这个内容保存在名字为“makefile”或“Makefile”的⽂件中,然后在该⽬录下直接输⼊命令 make 就可以⽣成执⾏⽂件edit。如果要删除执⾏⽂件和所有的中间⽬标⽂件,那么,只要简单地执⾏⼀下 make clean 就可以了。在这个makefile中,⽬标⽂件(target)包含:执⾏⽂件edit和中间⽬标⽂件( *.o ),依赖⽂件(prerequisites)就是冒号后⾯的那些.c ⽂件和 .h ⽂件。每⼀个 .o ⽂件都有⼀组依赖⽂件,⽽这些 .o ⽂件⼜是执⾏⽂件 edit 的依赖⽂件。依赖关系的实质就是说明了⽬标⽂件是由哪些⽂件⽣成的,换⾔之,⽬标⽂件是哪些⽂件更新的。在定义好依赖关系后,后续的那⼀⾏定义了如何⽣成⽬标⽂件的操作系统命令,⼀定要以⼀个 Tab 键作为开头。记住,make并不管命令是怎么⼯作
的,他只管执⾏所定义的命令。make会⽐较targets⽂件和prerequisites⽂件的修改⽇期,如果prerequisites⽂件的⽇期要⽐targets⽂件的⽇期要新,或者target不存在的话,那么,make就会执⾏后续定义的命令。这⾥要说明⼀点的是, clean 不是⼀个⽂件,它只不过是⼀个动作名字,有点像c语⾔中的label⼀样,其冒号后什么也没有,那么,make 就不会⾃动去它的依赖性,也就不会⾃动执⾏其后所定义的命令。要执⾏其后的命令,就要在make命令后明显得指出这个label的名字。这样的⽅法⾮常有⽤,我们可以在⼀个makefile中定义不⽤的编译或是和编译⽆关的命令,⽐如程序的打包,程序的备份,等等。
2.引⼊变量简化于是如果有新的 .o ⽂件加⼊,我们只需简单地修改⼀下 objects 变量就可以了。prerequisites 中如果有⼀个以上的⽂件⽐target ⽂件要新的话,command 所定义的命令就会被执⾏。
1edit : main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o cc -o edit main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o main.o : main.c defs.h cc -c main.c kbd.o : kbd.c defs.h command.h cc -c kbd.c command.o : command.c defs.h command.h cc -c command.c display.o : display.c defs.h buffer.h cc -c display.c insert.o : insert.c defs.h buffer.h cc -c insert.c search.o : search.c defs.h buffer.h cc -c search.c files.o : files.c defs.h buffer.h command.h cc -c files.c utils.o : utils.c defs.h cc -c utils.c clean : rm edit main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
3.引⼊⾃动推导GNU的make很强⼤,它可以⾃动推导⽂件以及⽂件依赖关系后⾯的命令,于是我们就没必要去在每⼀个 .o ⽂件后都写上类似的命令,因为,我们的make会⾃动识别,并⾃⼰推导命令。只要make看到⼀个 .o ⽂件,它就会⾃动的把 .c ⽂件加在依赖关系中,如果make到⼀个 whatever.o ,那么 whatever.c 就会是whatever.o 的依赖⽂件。并且 cc -c whatever.c 也会被推导出来,于是,我们的makefile再也不⽤写得这么复杂。我们的新makefile⼜出炉了。这种⽅法,也就是make的“隐晦规则”。上⾯⽂件内容中, .PHONY 表⽰ clean 是个伪⽬标⽂件。
三、gdb 调试
GDB是⼀个由GNU开源组织发布的、UNIX/LINUX操作系统下的、基于命令⾏的、功能强⼤的程序调试⼯具,对于⼀名Linux下⼯作的c++程序员,gdb是必不可少的⼯具。objects = main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o edit : $(objects) cc -o edit $(objects)main.o : main.c defs.h cc -c main.c kbd.o : kbd.c defs.h command.h cc -c kbd.c command.o : command.c defs.h command.h cc -c command.c display.o : display.c defs.h buffer.h cc -c display.c insert.o : insert.c defs.h buffer.h cc -c insert.c search.o : search.c defs.h buffer.h cc -c se
arch.c files.o : files.c defs.h buffer.h command.h cc -c files.c utils.o : utils.c defs.h cc -c utils.c clean : rm edit $(objects)
1
2
3
4
5
6
7
8
9
10
makefile phony11
12
1314
15
16
17
18
19
20
21
22
23objects = main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o edit : $(objects) cc -o edit $(objects)main.o : defs.h kbd.o : defs.h command.h command.o : defs.h command.h display.o : defs.h buffer.h insert.o : defs.h buffer.h search.o : defs.h buffer.h files.o : defs.h buffer.h command.h utils.o : defs.h .PHONY : clean clean : rm edit $(objects)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
gcc test.c -g -o test.out #编译时加上-g 才能gdb 调试gdb test.out #进⼊gdb 调试b main #break ,
设置断点b n #在第n ⾏设置断点r #运⾏程序到断点处 run s #下⼀步 有函数则进⼊函数n #下⼀步 不进⼊函数p xxx #打印信息display a b #监控a 和b 的值p (*(p+4))@6 #p 指向的第4个地址中6个字节的数据bt #打印当前堆栈中的所有信息(函数调⽤时所有数据会压⼊栈中)f 1 #切换到堆栈1q #退出调试l #显⽰源码 #回车 重复上⼀条命令123456789101112131415
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论