#
gcc编译过程分为4个阶段:(源代理*.c)
.预处理(和头文件*.h)
.适当编译(生成目标代理*.o)
.汇编
.链接(和引导代码,库代码)
其他的调试选项包括-p和-pg,它们将剖析(profiling)信息加入二进制文件中.这些信息对于找出代码中的性能瓶颈以及开发高性能的程序非常有
帮助.-p选项在代码中加入prof程序能够读取的剖析符合信息,而-pg选项加入了GNU项目中prof的化身gprof能够解释的符合信息.-a选项
在代码中加入代码块(比如函数)累计使用的次数.
-save-temps选项可以保存在编译过程中生成的中间文件,其中包括目标文件和汇编代码文件.
如果你对编译器到底花费了多少时间来完成它的工作感兴趣,可以考虑使用-Q选项,这个选项让gcc显示编译过程中碰到的每个函数,并提供编译器编译每个函数所花时间的剖析信息.
$gcc -g hello.c -o hello
$ls -l hello
-rwxr-xr-x 1 kwall users 10275 May
21 23:27 hello
$gcc -ggdb hello.c -o hello
$ls -l hello
-rwxr-xr-x
1 kwall users 8135 May 21 23:28
hello
-g选项让二进制文件大小增长到将近三倍,而-ggdb选项也让其大小增加了一倍!尽管会使文件大小增长,仍然建议在执行文件中包含标准的调试符合(使用-g选项创建),以便某些用户在遇到问题时可以调试你的代码.
gcc优化标志
选项 作用
-ffloat-store
禁止在CPU的寄存器中保存浮点变量的值.这能把CPU寄存器节省下来留作它用,而且可以防止产生过分精确但不必要的浮点数.
-ffast-math
产生浮点数学优化,这能提高速度但违反了IEEE或ANSI/ISO标准.如果程序不需要严格遵守IEEE规范,可在编译浮点密集型的程序时考虑采用这一标志
-finline-functions 把所有的"简单"函数在调用它们的函数中就地展开.编译器决定了什么是"简单"函数.减少处理器与函数相关的开销是一种基本的优化技术
-funroll-loops 展开所有能在编译时确定重复次数的循环体.展开循环体后每步循环都能省出几条CPU指令,这样大大减少了执行时间
-fomit-frame-pointer 如果函数不需要则丢掉指针,该指针保存在CPU的一个寄存器中.因为去掉了设置,保存和恢复帧指针所必需的指令,所以加快了处理速度.
-fschedule-insns 记录可能暂停的指令,因为它们正在等候的数据不在CPU中
-fschedule-insns2
执行第二次指令重排序(类似于-fschedule-insns)
-fmove-all-movables
把所有出现在循环体内部但稳定不变的计算移出循环体.这从循环体中去除了不必要的操作,加快了循环的整体运算速度.
内联和循环展开技术都能够大大提高程序的执行速度,因为它们都避免了函数调用和变量查找的开销,但付出的代价往往是大大增加了目标或二进制代码的大小.
一般而言,Linux程序员似乎爱用优化选项-O2.
使用gcc的-g和-ggdb选项在编译后的程序中插入调试信息以方面调试会话过程.能够用1,2或3来限定-g选项来指定产生多少调试信息.默认的级别
是2(-g2),此时的产生的调试信息包括扩展的符号表,行号以及局部或外部变量的信息.这些信息全部保存在二进制文件里.3级调试信息包括所有的2级信
息和源代码中定义的所有宏.相反,1级产生的信息只够创建回溯(backtrace)和堆栈转储(stack dump)之用.
回溯是指一个程序调用函数的历史.堆栈转储是一个通常以原始的十六机制格式保存程序执行环境内容的列表,列表内容主要是CPU寄存器和分配给程序的内存.注意,1级调试不产生局部变量和行号的调试信息.
计算pi的平方根
/*
* pisqrt.c - Calculate the square of PI
100,000,000
*/
#include <stdio.h>
#include
<math.h>
int main(void)
{
double pi = M_PI; /* Defined in
<math.h> */
double pisqrt;
long i;
for(i = 0; i <
10000000; ++i) {
pisqrt = sqrt(pi);
}
return
0;
}
pisqrt的执行时间
标志/优化
平均执行时间
<none>
5.43s
-O1 2.74s
-O2 2.83s
-O3 2.76s
-ffloat-store 5.41s
-ffast-math 5.46s
-funroll-loops
5.44s
-fschedule-insns
5.45s
-fschedule-insns2 5.44s
这个例子说明,除非对处理器的体系结构非常了解或者知道某种特殊的优化专门针对你的程序有影响,否则就应该使用优化选项-O.
pgcc的主要好处是它对Pentium处理器的优化较好.
1.关于可移植性
#ifdef __STRICT_ANSI__
/* use ANSI/ISO C only here
*/
#else
/* use GNU extensions here
*/
#endif
如果用户或是ANSI兼容的编译器定义了__STRICT_ANSI__宏,则表明需施加ANSI兼容的环境,并编译#ifdef语句块的第一部分代码.否则,编译#else后面的代码.
2.GNU扩展
gcc使用long
long 类型来提供64位储存单元:
long long
long_int_var;
内联函数
要使用内联函数,需在函数的返回类型前面插入关键字inline,如下面的代码片段所示,还要在编译时使用-O优化选项.
inline
void swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b
=
tmp;
}
函数和变量属性
关键字attribute通过向gcc指明有关代码的更多信息来帮助代码优化工作进行得更好.例如,标准库函数exit和
abort都不返回调用它们的函数.编译器如果知道它们不返回就能生成效率稍高的代码.当然用户程序也能定义不返回的函数.gcc允许为这些函数指定
noreturn属性,作为编译器在优化该函数时的提示.
例如,假设有个没有返回的函数die_on_error.为了使用函数属性,可以在函数声明后面加上__attribute__((attribute_name)).于是函数die_on_error的声明如下:
void
die_on_error(void) __attribute__ ((noreturn));
函数还和平常一样来定义:
#include
<stdlib.h>
void die_on_error(void)
{
/* your code here
*/
exit(EXIT_FAILURE);
}
也可以对变量指定属性.例如,aligned属性指示编译器在为变量分配内存空间时按指定字节数对齐边界.下列语句:
int
int_var __attribute__ ((aligned 16)) =
0;
使gcc让变量int_var的边界按16字节对齐.packed属性告诉gcc为变量或结构分配最小的内存空间.
如果想要关闭对未用变量发出的所有警告,那么可以对变量使用unused属性,它告诉编译器该变量不准备使用.下面的变量声明会消除警告:
float
big_salary __attribute__
((unused));
使用case区间
case区间是一个非常有用的扩展.其语法如下:
case LOWVAL ...
LOWVAL:
注意,在省略号前后必须有空格.在switch语句中,case区间指定了落在LOWVAL和HIVAL区间内的那些整数值.例如:
switch(int_var)
{
case 0 ... 2:
/* your code here */
break;
case 3 ...
5:
/* more code here */
break;
default:
/* default
code here
*/
}
构造函数名称
把函数名用作字符串是GNU的扩展,它能极大地简化调试工作.gcc预先定义了变量__FUNCTION__为当前函数(控制流程当前所在的位置)的名字,就好像它被写在源代码里去了一样.
使用__FUNCTION__变量
/*
* showit.c
- Illustrate using the __FUNCTION__ variable
*/
#include
<stdio.h>
void foo(void);
int
main(void)
{
printf("The current function is %s\n",
__FUNCTION__);
foo();
return 0;
}
void
foo(void)
{
printf("The current function is %s\n", __FUNCTION__);
}
下面是定义变量的一般方法:
VARNAME=some_text
[...]
把变量用括号起来,并在前面加上"$"符号,就可以引用变量的值:
$(VARNAME)
变量一般都在makefile的头部定义,并且,按照惯例,所有的makefile变量都应该大写.
在makefile中使用变量
OBJS
= howdy.o helper.o
HDRS = helper.h
howdy: $(OBJS) $(HDRS)
gcc $(OBJS) -o howdy
helper.o: helper.c $(HDRS)
gcc -c
helper.c
howdy.o: howdy.c
gcc -c howdy.c
hello: hello.c
gcc
hello.c -o hello
all: howdy hello
clean:
rm howdy hello
*.o
make使用两种变量:递归展开变量和简单展开变量.递归展开变量在引用时逐层展开,即如果在展开式中包含了对其他变量的引用,则这些变量也将被展开,直到没有需要展开的变量为止,这就是所谓的递归展开.
考虑下面的变量定义:
CC
= gcc
CC = $(CC)
-o
CC在被引用时递归展开,从而陷入一个无限循环中:CC将展开为$(CC)的值,从而永远也读不到-o选项.
为了避免这个问题,可以使用简单展开变量.与递归展开变量在引用时展开不同,简单展开变量在定义处展开,并且只展开一次,从而取消了变量的嵌套引用.在定义时,其语法与递归展开变量有细微的不同:
CC
:= gcc -o
CC += -O2
第一个定义使用":="设置CC的值为gcc -o,
第二个定义使用"+="在前面定义的CC后附加了-O2,从而CC最终的值是gcc -o
-O2.
除用户定义变量外,make也允许使用环境变量,自动变量和预定义变量.使用环境变量非常简单.在启动时,make读取已定义的环境变量,并且创建与之同名同值的变量.但是,如果makefile中有同名的变量,则这个变量将取代与之相应的环境变量,所以应当注意这一点.
自动变量
变量
说明
$@
规则的目标所对应的文件名
$<
规则中的第一个相关文件名
$^
规则中所有相关文件的列表,以空格为分界符
$?
规则中日期新于目标的所有相关文件的列表,以空格为分隔符
$(@D)
目标文件的目录部分(如果目标在子目录中)
$(@F)
目标文件的文件名部分(如果目标在子目录中)
用于文件名和标志的预定义变量
变量
说明
AR
归档维护程序,默认值=ar
AS
汇编程序,默认值=as
CC
C编译程序,默认值=cc
CPP
C预处理程序,默认值=
cpp
RM
文件删除程序,默认值="rm -f"
ARFLAGS
传给归档维护程序的标志,默认值=rv
ASFLAGS
传给汇编程序的标志,没有默认值
CFLAGS
传给C编译器的标志,没有默认值
CPPFLAGS
传给C预处理程序的标志,没有默认值
LDFLAGS
传给链接程序(ld)的标志,没有默认值
下面解释make是如何工作的:当遇到目标体clean时,make先查看其是否有依赖体,因为clean没有依赖体,所以make认为目标体是最新的而不执行任何操作.为了编译这个目标体,必须输入make
clean.
然而,如果恰巧有一个名为clean的文件存在,make就会发现它.然后和前面一样,因为clean没有依赖体文件,make就认为这个文件是最新的而不会执行相关命令.为了处理这类情况,需要使用特殊的make目标体.PHONY.
.PHONY的依赖体文件的含义和通常一样,但是make不检查是否存在有文件名和依赖体中的一个名字相匹配的文件,而是直接执行与之相关的命令.在使用了.PHONY之后,前面的例子如下:
howdy:
howdy.o helper.o helper.h
gcc howdy.o helper.o -o howdy
helper.o:
helper.c helper.h
gcc -c helper.c
howdy.o: howdy.c
gcc -c
howdy.c
hello: hello.c
gcc hello.c -o hello
all: howdy
hello
.PHONY : clean
clean:
rm howdy hello *.o
|