概要:在linux环境中我们使用gcc来编译C程序,在面对规模大的C程序时(源文件很多),我们可以书写makefile并使用make命令完成C程序的构建。
第一部分 C程序的编译过程
首先,在阐明编译方法之前,我们应该先了解一下C程序的编译过程有哪些阶段。
1、编译预处理:
编译器读取C源程序,对其中的预处理命令(以#开头)和特殊符号进行处理。预处理命令包括主要包括三种,一是宏定义命令,二是条件编译指令,三是头文件包含指令。采用头文件的目的是使某些定义可以供多个不同的C源程序使用。在需要用到这些定义的C源程序中,只需加上#include语句即可,而不必重新定义一遍。预编译程序将头文件中的代码统统加入到源文件,进而产生输出文件。
除了以上三种预处理命令,还有特殊符号。预编译程序可以识别一些特殊符号。例如在源程序中出现的LINE表示将被解释为十进制表示的当前行号。FILE则被解释为当前编译的源程序的文件名。
预编译程序完成的工作,可以说成是对源程序的“替换”工作。经过这个过程,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。
这里需要注意的是我们应该避免因重复引入头文件而造成重复定义的错误。所以,我们在书写自己的头文件时,应当以
#ifndef _Label_
#define _Label_ 1
开头,以
#endif
结尾。
注意:这个阶段的产出仍是源文件,只不过已经没有了宏定义,条件编译指令,头文件包含指令,特殊符号这些东西。
2、编译、优化:
编译程序的工作是,通过词法分析、语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的汇编代码。
在生成汇编代码过程中,可能涉及到优化处理。优化有两种:一种优化仅涉及代码本身,主要是删除公共表达式、循环优化、代码外提、无用代码赋值的删除 等。另一种优化设计具体的计算机硬件,比如,如何根据机器硬件执行指令的特点对指令进行调整优化,减少目标代码长度,提高执行效率。
注意:这个阶段产出的是对应与源文件的汇编文件。
3、汇编:
汇编代码生成以后,编译程序将中间代码转换为目标机器指令的序列,得到对应于源程序的目标文件。目标文件中存放的也就是与源程序等效的目标机器的机器语言代码。目标文件一般至少包含2个段:代码段和数据段。
注意:这个阶段产出的是对应与源文件的目标文件(机器语言文件)。
3、链接:
由第二阶段生成的若干对应于多个源程序的目标文件,并不能立即就被执行。其中存在一些问题,比如,某个源文件中的函数可能引用了另一个源文件中的某个符号(如变量或者函数等);在一个源文件中可能调用了某个库文件中的函数,等等。这些问题,需要连接程序来解决。
连接程序的主要工作就是将有关的目标文件彼此连接。也就是将在一个文件中引用的符号同该符号在另一个文件中的定义连接起来。使得所有这些目标文件成为一个能够被操作系统执行的一个整体。
注意:这个阶段会产出可执行的文件。
补充:链接库分为2种
静态链接
在这种连接方式下,函数的代码将直接拷贝到最终的可执行文件中。该程序被执行时候,会被装入该进程的虚拟地址空间中。静态链接库实际上是一个或若干目标文件。
动态链接
这种方式下,函数的代码被放到称作动态连接库或共享对象的某个目标文件中。链接程序此时的工作只是在生成的可执行文件中,记录下共享对象的名字以及少量关键信息。动态连接库可以被多个进程共享,在运行时候内存中只有一个实例。
第二部分 GCC
在Linux中可以使用GCC来编译C源文件,使用man gcc可以查看gcc的详细使用说明。
gcc命令可以通过选项支持分别走过上面的四个步骤:
名称 gcc选项 英文名称 gcc调用的程序 示例
预处理
-E
Pre-Processing
cpp
gcc -E test.c -o test.i
编译
-S
Compiling
ccl
gcc -S test.i -o test.s
汇编
-c
Assembling
as
gcc -c test.s -o test.o
连接
无
Linking
ld
gcc test.o -o test
简单一些的使用方式是:gcc hello.c -o hello
如果想要看到编译出错时具体的错误码。可以使用-pass-exit-codes选项。
如果想要看到gcc编译文件的过程,可以使用-v选项。
如果想要看到gcc的编译过程却不想真正的编译文件,可以使用-###选项。
如果在编译过程中不想让gcc创建临时文件,可以使用-pipe选项。
第三部分 Make
在处理复杂一些的构建任务时,手工的执行gcc命令显得有些力不从心。这时就需要make命令出场。make命令会依照makefile文件构建c程序。而且它还具有增量构建的优点。
简单用法是make -k。make命令会按照当前目录下名字为makefile文件的规划逐步构建程序。由于-k选项的存在,它会尽最大努力的完成构建任务,不会因为一个错误就停止之后的所有构建。
如果想指定其他文件为makefile文件,可以使用-f 文件名。
如果想要指定执行的目标,可以使用“make 目标”。单独使用make时会把文件中的第一个目标作为执行的目标。
使用man make可以查看make命令的详细用法。
第四部分 Makefile的书写
在Makefile中由#开始的行都是注释行.Makefile中最重要的是描述文件的依赖关系的说明.一般的格式是: target: components
rule
Makefile有三个非常有用的变量.分别是$@,$^,$<代表的意义分别是: $@--目标文件,$^--所有的依赖文件,$<--第一个依赖文件.
文件中可以使用宏。比如定义
CC=gcc
使用宏的话,可以这样
$(CC) -c -o hello.o hello.c
第五部分 GDB--C程序的调试工具
为了可以使用gdb来调试C程序,在使用gcc编译C源文件时需要使用-g选项。
我们可以在命令行敲入gdb来启动调试器。gdb一旦启动,它就可以从控制台接受命令。推出gdb的命令是quit。通常我们想要调试一个C程序,可以使用这样的命令:gdb program。你也可以先输入gdb,然后通过file命令装入被调试的程序:file 可执行文件名。
接下来,你可以输入run命令,启动被调试的程序。run命令后可以接受参数列表,这些参数会传递给C程序。你可以使用list命令显示源代码:list [form_line_number,to_line_number].比如list 1,15.列出源代码的1到15行.
5.1 如何向被调试程序传入参数
可以在run命令后面紧跟着需要传入的参数。这里需要注意的是,如果你使用不带参数的run命令,gdb就重演你上一次执行的run命令,包括后面的参数。如果你想要修改传入的参数,可以使用set args命令:set args 参数列表。
5.2 断点的管理
使用break命令在程序中加入断点。
使用方法是:
break [filename:]line-number 使程序恰好在执行给定行之前停止。
break [filename:]function-name 使程序恰好在进入指定的函数之前停止。
break [filename:]line-or-function if condition
如果condition(条件)是真,程序到达指定行或函数时.
info break可以查看存在的断点。
“disable breakpoint 编号” / “enable breakpoint 编号” 可以禁用或启用指定编号的断点。
"delete breakpoint" /
"delete breakpoint
编号"可以清除所有断点或清除指定编号的断点。
从断点继续运行:countinue 命令。简写c。
5.3 单步执行
使用next命令执行不进入函数的单步执行。
使用step命令执行进入函数的单步执行。
如果已经进入了某函数,而想完成该函数返回到它的调用函数中,可使用命令finish命令。
5.4 显示数据
利用print 命令可以检查各个变量的值。
(gdb) print p (p为变量名)
whatis 命令可以显示某个变量的类型
(gdb) whatis p
type = int *
print
是gdb的一个功能很强的命令,利用它可以显示被调试的语言中任何有效的表达式。表达式除了包含你程序中的变量外,还可以包含以下内容:
对程序中函数的调用
(gdb) print find_entry(1,0)
数据结构和其他复杂对象
(gdb) print *table_start
$8={e=reference=’\000’,location=0x0,next=0x0}
值的历史成分
(gdb)print $1 ($1为历史记录变量,在以后可以直接引用 $1 的值) .
八.函数的调用
call name 调用和执行一个函数
(gdb) call gen_and_sork( 1234,1,0 )
(gdb) call printf(“abcd”)