目录
1.gcc编译器的使用
1.1gcc软件包
1.2一个基本实例
1.3gcc的主要选项
1.3.1指定函数库和包含文件的查找路径
1.3.2出错检查及警告
1.3.3优化选项
优化带来的问题
1.3.4调试选项
2.GNU C扩展简介
3.GNU make管理项目
3.1make简介
使用make管理项目的原因
4.编写Makefile文件的规则
4.1伪目标
4.2变量
4.3make变量
1.环境变量
2.自动变量
4.3预定义变量
4.4隐式规则
4.5模式规则
4.6make命令
4.7宏
1.gcc编译器的使用
gcc是GNU项目的编译器套件,能够编译用C、C++和Objective C编写的程序,此外gcc在g77帮助下也能够编译Fortran程序。
使用gcc,程序员能够对编译过程有更多的控制。
编译过程分为四个阶段:预处理、编译、汇编、链接。
程序员可以在编译的任何阶段结束后停止整个编译过程,以检查编译器在该阶段的输出信息。
gcc可以在生成的二进制执行文件中加入不同数量和种类的调试码,也能优化代码。
gcc能够在生成调试信息的同时对代码进行优化,但不建议使用该特性,因为优化后的代码中静态变量可能被取消,循环也可能被展开,优化后的代码与源代码已经不是行行对应了。
1.1gcc软件包
名称 | 功能描述 |
cpp | C预处理器 |
g++ | C++编辑器 |
gcc | C编译器 |
gccbug | 创建bug报告的shell脚本 |
gcov | 是覆盖测试工具,用来分析在程序的哪里做优化的效果最好 |
libgcc | gcc的运行库 |
libstdc++ | 标准c++库,包含多常用的函数 |
libsupc++ | 提供支持c++语言的函数库 |
gcc有30多个警告和3个“call-all”警告级。同时,gcc是一个交叉平台编译器,所以能够在当前CPU平台上为不同体系结构的硬件系统开发软件。
gcc对c和C++作了大量扩展,这些扩展大部分能够提高程序执行效率,或有助于编译器进行代码优化, 或使编程更加容易,但不建议使用,因为这是以降低移植性为代价的。
gcc的基本用法: gcc[options][filenames]
1.2一个基本实例
//test.c
#include <stdio.h>
void main()
{
print("Hello World!\n");
}
使用gcc编译器编译一下:
gcc test.c
用户将会在同一目录下得到一个名为a.out的文件,然后在命令提示符下执行:
$./a.out
就可以显示结果Hello world!。注意:a.out是默认生成的目标文件名。如果在同一个目录下,编译另外一个源程序且没有指明生成的目标文件名的话,原先的a.out文件会被覆盖。可以使用-o选项指定生成的目标文件名,如:
gcc -o test test.c
这样在同一目录下会生成名为test.o的目标文件,然后执行$./test即可,会得到同样的输出Hello World!。
1.3gcc的主要选项
gcc有一百多个编译选项,并支持多个选项同时使用,只要不互相冲突即可。
-o 选项的使用:-o FILE告诉gcc把输出定向到FILE文件而不论是否生成输出数据。如果不指定-o选项,对于名为FILE.SUFFIX的输入文件,其生成可执行程序名为a.out,目标文件代码是FILE.o,汇编代码在FILE.s。
1.3.1指定函数库和包含文件的查找路径
如果需要链接函数库或不在标准位置下的包含文件,可以使用-L{Dirname}和-I{Dirname}选项指定文件所在目录,以确保该目录的搜索顺序在标准目录之前。例如:要编译的程序要包含一个文件paint.h,而这个文件位于/usr/include/zw目录下,不在默认路径下,可以使用如下命令:
gcc -Wall -l/usr/include/zw -o paint -c paint.c
预处理器就能找到需要的paint.h文件。
类似地,如果要使用/home/sst/目录下的库文件和头文件编译自己的程序可以使用如下命令:
gcc test.c -L/home/sst/lib -l/home/sst/include -o test -lnew
使用该命令编译时,gcc首先分别在/home/sst/lib和/home/sst/include下寻找所需要的库文件和头文件。该命令中选项-I表示要链接的库文件名为libnew.so(把函数库命名为lib是UNIX的惯例,gcc也遵循此约定)
gcc在默认情况下只链接动态共享库,如果要链接静态库,需要使用-static选项,如果上例中文库文件libnew为静态库,则应该使用如下命令:
gcc test.c -L/home/sst/lib –I/home/sst/include -o test –lnew –static
命令中-static选项表明要链接的库文件为静态库libnew.a。链接了静态库的可执行程序比链接动态库时要大很多,不过链接静态库时,程序在任何情况下都可以执行,而链接动态共享库的程序只有在系统中包含了所需的共享库时才可以运行。
1.3.2出错检查及警告
选项-pedantic表示gcc只发出ANSI/ISO C标准所列出的所有警告,-pedantic -errors与-pedantic相近,但它针对的是错误。选项-ansi支持ANSI/ISO C的标准语法,取消GNU的语法中与该标准有冲突的部分,但该选项并不能保证生成ANSI兼容代码。
//test2.c
/* a simple sample*/
#include <stdio.h>
void main()
{
long long i=1;
printf(“a simple sample”);
return i;
}
1.3.3优化选项
代码优化选项可以改进程序的执行效率,但代价需要更长的编译时间和在编译期间需要更多的内存。
-O选项与-O1等价,告诉gcc对代码长度和执行时间都进行优化。
在这个级别上执行的优化类型取决于目标处理器,但一般都包括线程跳转和延迟退栈这两种优化。线程跳转优化的目的是减少跳转操作的次数。而延迟退栈是指在嵌套函数调用时推迟退出堆栈的时间,如不做这种优化,函数调用每次完成后都要弹出堆栈中的函数参数,做这种优化后,在栈中保留了参数,直到完成所有的递归调用后才同时弹出栈中积累的函数参数。
-O2选项包含-O1级所做的优化,还包括安排处理器指令时序。
使用-O2优化时,编译器保证处理器在等待其他指令的结果或来自高速缓存或主存的数据延迟时仍然有可执行的指令,它的实现与处理器密切相关。
-O3级优化包含所有-O2级优化,同时还包含循环展开以及其他一些与处理器结构有关的优化内容。
依据对目标处理器系列相关信息的多少,还可以使用-f{<flag}选项来指定具体优化类型。常用的有3种类型:-fastmath、-finline-functions、-funroll-loops。
-fastmath对浮点数学运算进行优化以提高速度,但是这种优化违反IEEE和ANSI标准。
-finline-functions选项把所有简单函数像预处理宏一样展开,当然是由预处理器决定什么是简单函数。
-funroll-loops选项用于让编译器展开在编译期间就可以确定循环次数的循环。
使用以上选项后,因为展开函数和确定性的循环避免了部分变量的查找和函数调用工作,因而理论上可以提高执行速度,但是,上述展开可能会导致目标文件或可执行代码的急剧增长,因而必须试验才能决定使用以上选项是否合适。用户可以使用time命令来查看程序执行时间。
优化带来的问题
优化级别越高程序执行得越快,但它同时带来了一个负面后果:即程序编译时间也将越长。
优化级别越高程序量越大,特别是-O3级在优化时需要有一定的交换空间,它和程序对RAM要求成正比,在一定程度下,过于庞大的程序会带来负面效果。
在使用优化时,程序调试会变得困难。因为优化器会删除最终版本中不用的代码,或者重组更多声明,跟踪可执行文件变得困难,所以在调试代码时不要使用优化选项。
注意:在一般情况下,使用-O2优化已经足够。
1.3.4调试选项
2.GNU C扩展简介
GNU C在很多方面扩展了ANSI标准,如果不介意编写非标准代码,其中一些扩展会很有用。下面只介绍在Linux系统头文件和源代码中常见的那些GNU扩展。
(1)gcc使用long long数据类型来提供64位存储单元,如:
long long long_int_var
(2)内联函数,只要足够小,内联函数就能像宏一样在代码中展开,从而减少函数调用的开销,同时编译器在编译时对内联函数进行类型检查,所以使用内联函数比宏安全。但是,在编译时至少要使用-o选项才能够使用内联函数。
(3)attribute关键字指明代码相关信息以方便优化。很多标准库函数没有返回值,如果编译器知道函数没有返回值,可生成较为优化的代码。自定义函数也没有返回值,gcc提供noreturn属性来标识这些属性,以提示gcc在编译时对其进行优化。如:
void exit_on_error(void) _ _attribute_ _((noreturn)) #定义和平常一样
void exit_on_error(void)
{
exit(1);
}
也可以对变量指定属性:Aligned属性指定编译器在为变量分配内存空间时按指定字节边界对齐。如:int int_var _ _attribute__((aligned 16))=0;使int_var变量的边界按16字节对齐。packed属性使gcc为变量或结构分配最小空间,定义结构时,packed属性使得gcc不会为了对齐不同变量的边界而插入额外的字节。
(4)gcc还对case做了扩展。在ANSI C中,case语句只能对应于单个值的情况,扩展后的case语句可以对应于一个范围,使用语法:在case关键字后列出范围的两个边界值。边界值之间用空格-省略号-空格分开,即:case LOWVAL … HIGHVAL:在这里省略号前后的空格是必须的。如:
switch (i){
case 0 … 10:
/*事物处理代码*/
break;
case 11 … 100:
/*事物处理代码*/
break;
default:
/*默认情况下的事务处理代码*/
}
3.GNU make管理项目
3.1make简介
当使用GNU中的编译语言如gcc、GNU C++编程开发应用时,绝大多数情况要使用make管理项目。
使用make管理项目的原因
首先,包含多个源文件的项目在编译时都有长而复杂的命令行,使用make可以通过把这些命令行保存在makefile文件中而简化这个工作;其次,使用make可以减少重新编译所需要的时间,因为它可以识别出那些被修改的文件,并且只编译这些文件;最后,make在一个数据库中维护了当前项目中各文件的相互关系,从而可以在编译前检查是否可以找到所有需要的文件。
要使用make进行项目管理必须编写Malefile文件(一般不使用自动生成工具)。
Makefile文件是一个文本形式的数据库文件,其中包含的规则指明make编译哪些文件以及怎样编译这些文件。一套规则包含3方面内容:make要创建的目标文件(target),编译目标文件所需的依赖文件列表(dependencies),通过依赖文件创建目标文件所需要执行的命令组(commands)。
make在执行时按序查找名为GNUmakefile、makefile和Makefile文件进行编译,为保持与Linux操作系统源代码开发的一致性,建议用户使用Makefile。
Makefile规则通用形式如下:
target: dependency file1 dependency file2[...]
command1
command2
[...]
注意: 每一个命令行必须是Tab制表符,仅使用8个空格是不够的,除非特别指定,否则make的工作目录就是当前目录。
一个简单的Makefile实例:
printer: printer.o shape.o op_lib.o
gcc -o printer printer.o shape.o op_lib.o
printer.o: printer.c printer.h shape.h op_lib.h
gcc -c printer.c
shape.o:shape.c shape.h
gcc -c shape.c
op_lib.o:op-lib.c op_lib.h
gcc -c op_lib.c
clean:
rm printer *.o
第一行中的三个依赖文件并不存在,如果是在shell上用命令行编译则会出错并退出,但在make管理项目中,在执行gcc时会先检查依赖文件是否存在,若不存在,则会先执行别的规则,以生成缺少的依赖文件,最后生成相关的目标文件。如果依赖文件已经存在,则并不急于执行后面的命令重新得到它们,而是比较这些依赖文件以及与其对应的源文件的生成时间,如果有一个或多个源文件比相应依赖文件新,则重新编译这些文件以反映相关源文件的变化,否则,使用旧的依赖文件生成目标文件。
4.编写Makefile文件的规则
4.1伪目标
在编写Makefile文件时既可以建立普通目标,也可以建立伪目标,伪目标不对应实际文件,如上面的clean就是一个伪目标。
由于伪目标没有依赖文件,它不会自动执行。
原因是:make在执行到目标clean时,make先检查它的依赖文件是否存在,由于clean没有依赖文件,make就认为该目标clean是最新版本,需要重新创建。
想要启动伪目标必须使用如下命令:
make[virtual target]
上例中是执行make clean。
实际上可以使用特殊的make目标.PHONY的依赖文件含义通常一样,但make不会检查是否存在它的依赖文件而直接执行.PHONY所对应规则的命令。例如:
printer:printer.o shape.o op_lib.o
gcc -o printer printer.o shape.o op_lib.o
printer.o:printer.c printer.h shape.h op_lib.h
gcc -c printer.c
shape.o:shape.c shape.h
gcc -c shape.c
op_lib.o:op-lib.c op_lib.h
gcc -c op_lib.C
.PHONY:clean
clean:
rm printer *.o
4.2变量
编写Makefile变量时也可以使用变量,所谓变量就是用指定文本串在Makefile中定义的一个名字,Makefile中变量一般用大写,并用等号给它赋值。引用时只需用括号将变量名括起来并在括号前加上$符号。如:VARNAME=some-text
当编写大型应用程序的Makefile时,其中涉及的依赖文件和规则繁多,如果使用变量表示某些依赖文件的路径,则会大大简化Makefile。一般在Makefile文件开始就定义文件中所需的所有变量,这样使Makefile文件清晰且便于修改。如(#在Makefile文件中表示后面的内容是注释):
一个简答的Makefile文件:
OBJECT = printer.o shape.o op_lib.o
HEADER=printer.h shape.h op_lib.h
printer:$(OBJECT)
gcc -o printer $(OBJECT)
printer.o:printer.c $(HEADER)
gcc -c printer.c
shape.o:shape.c shape.h
gcc-c shape.c
op_lib.o:op_lib.c op_lib.h
gcc -c op_lib.c
.PHONY:clean
clean:
rm printer*.o
以上变量都是自定义变量,make管理项目也允许在Makefile中使用特殊变量即make变量。
4.3make变量
make变量包括环境变量、自动变量和预定义变量。
1.环境变量
就是系统环境变量,make命令执行时会读取系统环境变量并创建与其同名的变量,但是如果Makefile有同名变量,则用户定义变量会覆盖系统的环境变量值。
2.自动变量
make管理项目允许使用的自动变量全部以美元符号"$"开头,如下所示:
4.3预定义变量
make管理项目支持的预定义变量主要用于定义程序名,以及传给这些程序的参数和标志值。
变量含义如下:
4.4隐式规则
以上介绍的Makefile规则都是用户自己定义的,其实make还有一系列隐式规则(或称为预定义规则)集。如有下面有一个Makefile文件:
#a simple Makefile
OBJECT=printer.o shape.o op_lib.o
printer:$(OBJECT)
gcc -o printer $(OBJECT)
.PHONY:clean
clean
rm printer*·o
默认目标printer的依赖文件是print.o shape.o op_lib.o,但在Makefile中并没有提及如何生成这些目标的规则,这时,make使用所谓的隐式规则。
实际上,对每一个名为somefile.o的目标文件,make先寻找与之对应的somefile.c文件,并用gcc-c somefile.c -o somefile.o编译生成这个目标文件。目标文件(.o)可以从C、Pascal、FORTRAN等源代码中生成,所以make符合实际情况的文件。 例如如果在该目录下有printer.p shape.p op_Iib.p(Pascal源文件),make就会激活Pascal编译器来编译它们。 注意:如果在项目中使用多种语言时,不要使用隐式规则。因为使用隐式规则得到的结果可能与预期结果不同。
4.5模式规则
模式规则是指用户自定义的隐式规则。
隐式规则和普通规则格式一致,但是目标和依赖文件必须带有百分号"%"。该符号可以和任何非空字符串匹配,例如:%.o %.c。实际上make已经对一些模式规则进行了定义,如:
%.o:%.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
此规则表示所有的Object文件都由C源码生成,使用该规则时,它利用自动变量$<和$@代替第一个依赖文件和Object文件,变量CC、CFLAGS、CPPFLAGS使用系统预定阈值。
4.6make命令
建立Makefile文件后,就可以使用make命令生成和维护目标文件。
命令格式为:
make [options][macrodef][target]
选项options指定make的工作行为;
选项macrodef(宏定义)指定执行Makefile时的宏值;
目标target是更新的文件列表,这些参数都是可选的,参数之间用空格隔开。
通过在命令行中指定make命令的选项(options),可以使make以不同的方式运行,常用选项列表如下所示:
4.7宏
在make中使用宏,首先要定义宏,在makefile中引用宏的定义格式为:
宏名 赋值符号 宏值
宏名由用户指定,可以使用字母、数字、下画线(_)的任意组合,不过不能以数字开头,习惯上一般使用大写字母,并使名字有意义,便于阅读和维护。 赋值符号有如下三种:
= 直接将后面的字符串赋给宏。
:= 后面跟字符串常量,将它的内容赋给宏。
+= 宏原来的值加上一个空格,再加上后面的字符串,作为新的宏值。
一般常用的是第一种。除了空格,赋值符号的前面不能有制表符或其他任何分隔符号,否则都会被make当作宏名的一部分,从而引起语法错误。
宏的引用有如下两种格式:
$(宏名)或${宏名}
当宏名只有单个字符时,可以省略括号,如$A就等于$(A)。
由于make将$符号作为宏引用的开始,因此要表示$符号需要 用两个$(即$$)。
make处理时会先扫描一遍整个makefile,确定所有宏的值。因 此注意宏的引用可以在定义之后,而且使用的是最后一次赋予的值。例如:
All:printl1 print2
var1 =hello
print1:;@echo$(var1)
var1 += world
print2:;@echo$(var1)
则两次打印的都是hello world。
宏还允许嵌套使用,处理时依次展开。例如:
HEADFILE = myfile.h
HEADFILE1 = myfile2.h
INDEX = 1
现在引用$(HEADFILE$(INDEX)),首先展开宏INDEX,得到$(HEADFILE1),最后的结果是myfile2.h。
宏的定义可以出现在三个地方:一种是在makefile中定义,一种是在mde命令行中指明,一种是载入环境中的宏定义。
在makefile中定义只需直接书写上面的定义式,也可以用前面介绍过的伪目标.include从其他文件中获得宏定义。在make命令行中定义时,应放在“属性”之后,“目标文件”之前。