Chinaunix首页 | 论坛 | 博客
  • 博客访问: 19292
  • 博文数量: 9
  • 博客积分: 1400
  • 博客等级: 上尉
  • 技术积分: 95
  • 用 户 组: 普通用户
  • 注册时间: 2009-11-21 12:21
文章分类
文章存档

2011年(1)

2010年(3)

2009年(5)

我的朋友

分类: C/C++

2009-12-06 09:32:04

先来看看这样定义的一个宏:
#define sqrt(x) x * x
#include
int main(int args, char** argv)
{
       printf(“the result is: %d\n”, sqrt(5 + 1));
       return 0;
}
 
有经验的读者一眼就可以看出这段代码的问题,但是很多时候却往往因为粗枝大叶犯下这样的错误。所以养成一个良好的编码习惯对于一个程序员是很重要的,编码质量不高,程序需要反复调试,很多时候都是很低级的错误——习惯不好造成的。
       上面这段代码编译时没有任何错误或者告警,但实际运行效果却跟期望有很大的差别。实际上,宏是编译器在预处理的时候进行替换,上面的宏也就被替换成了5 + 1 * 5 + 1,得到的结果是11而不是期望的36。有人说我可以这样来定义这个宏:
#define sqrt(x) (x) * (x)
此时这个宏应该可以得到大多数人的认可了。没错,乍一看是没问题,修改一下测试代码,你又会发现还是没有完全解决问题:
#include
int main(int args, char** argv)
{
       printf(“the result is: %d\n”, sqrt(5 + 1) / sqrt(6 + 1));
       return 0;
}
 
编译器在预处理时候替换成:
(5 + 1) * (5 + 1) / (6 + 1) * (6 + 1) = 6 * 6 / 7 * 7 = 5 * 7 = 35
而期望的是(6 * 6) / (7 * 7) = 0,重新定义的宏计算出来的结果还是与实际不相符。
最后将这个宏定义为:
#define sqrt(x) ((x) * (x))
这个宏才能对任何应用都能按照我们的意图正常工作。
 
何时使用宏
简单的说,我们使用宏大都是为了“偷懒”。有时候相同的代码根本没必要每次都写上一篇,聪明的人立刻想到写一个宏来代替这些重复的代码。这是一件很好的事情,要学会让编译器尽可能的帮我们多做事情。
使用宏的另外一个原因是为了代码的可读性和可维护性。当你的代码里面出现了一堆让其他人看不懂的数字(华为人称之为“魔鬼数字”,还为此曾经轰轰烈烈地开展过消灭“魔鬼数字”的运动),这个时候就非常有必要定义一些能代表其实际含义的宏,这样的代码才具有可读性和可维护性。如果一个人写出来的代码让人看不懂或者看起来很费解,这样的代码基本上没有什么价值。举个例子:假如要判断客户浏览器类型是否是MSIE6.0,用到如下语句:
if (0x64 == browser_type)
{
       ...................
}
 
此时0x64代码浏览器类型是MSIE6.0,但是在没有注释的情况下其他人是看不懂的。假如定义一个宏来代表浏览器类型MSIE6.0
#define BROWSER_TYPE_MSIE60 0x64
if (BROWSER_TYPE_MSIE60 == browser_type)
{
       ...................
}
 
这个代码的可读性和可维护性都大大提高了。另外还有一点小建议,定义这样的宏时最好采用如下方式:
#define BROWSER_TYPE_MSIE60 (1UL << 6)
       这样写的有点在于在定义很多浏览器类型的时候不会出错,而且对已经定义类型的个数一目了然。
 
跟内联函数的比较
       当使用宏的时候,编译器对传入的“参数”是不会作类型检查的,而内联函数,模版编译器却要作严格的参数类型检查。没有了编译器的类型检查,这就需要你在写代码的时候格外小心,要严格保证宏“参数”的合法性和正确性。
       前面已经多次提到,宏是在编译器预处理的时候进行替换的,预处理器只是将宏在“调用点”上机械的展开(所有才会出现一些问题)。
使用内联函数的情况却是要复杂一些。当编译选项的优化选项打开时,编译器才将内联函数进行替换并优化(并不像宏那样机械的替换,而是经过了优化);当编译选项的优化选项关闭时,编译器只是将内联函数当作一个普通的函数来进行调用。可通过如下编译器生成的汇编验证这个说法:
原始C代码如下,保存为inline_test.c
#include
 
inline int max(int x, int y)
{
       return x > y ? x : y;
}
 
int main(int args, char** argv)
{
       printf("the result is %d\n", max(100, 50));
       return 0;
}
 
使用gcc –S inline_test.c-o inline_test.s命令,将C代码生成汇编代码:
       .file  "inline_test.c"
       .text
.globl _max
       .def  _max;     .scl  2;    .type       32;   .endef
_max:
       pushl       %ebp
       movl       %esp, %ebp
       subl $8, %esp
       movl       8(%ebp), %eax
       movl       %eax, -8(%ebp)
       movl       12(%ebp), %eax
       movl       %eax, -4(%ebp)
       movl       -8(%ebp), %eax
       cmpl       %eax, -4(%ebp)
       jge   L2
       movl       -8(%ebp), %eax
       movl       %eax, -4(%ebp)
L2:
       movl       -4(%ebp), %eax
       leave
       ret
       .def  ___main; .scl  2;    .type       32;   .endef
       .section .rdata,"dr"
LC0:
       .ascii "the result is %d\12\0"
       .text
.globl _main
       .def  _main;     .scl  2;    .type       32;   .endef
_main:
       pushl       %ebp
       movl       %esp, %ebp
       subl $24, %esp
       andl $-16, %esp
       movl       $0, %eax
       addl $15, %eax
       addl $15, %eax
       shrl  $4, %eax
       sall   $4, %eax
       movl       %eax, -4(%ebp)
       movl       -4(%ebp), %eax
       call  __alloca
       call  ___main
       movl       $50, 4(%esp)
       movl       $100, (%esp)
       call  _max
       movl       %eax, 4(%esp)
       movl       $LC0, (%esp)
       call  _printf
       movl       $0, %eax
       leave
       ret
       .def  _printf;    .scl  3;    .type       32;   .endef
由于没有使用优化选项,在这段汇编代码中,我们看到了函数_max和在main函数中调用了函数_maxcall      _max),下面我们看看使用了优化选项的汇编代码(使用gcc –O3 –S inline_test.c-o inline_test.s命令):
       .file  "inline_test.c"
       .text
       .p2align 4,,15
.globl _max
       .def  _max;     .scl  2;    .type       32;   .endef
_max:
       pushl       %ebp
       movl       %esp, %ebp
       movl       8(%ebp), %edx
       movl       12(%ebp), %eax
       cmpl       %edx, %eax
       jge   L2
       movl       %edx, %eax
L2:
       popl %ebp
       ret
       .def  ___main; .scl  2;    .type       32;   .endef
       .section .rdata,"dr"
LC0:
       .ascii "the result is %d\12\0"
       .text
       .p2align 4,,15
.globl _main
       .def  _main;     .scl  2;    .type       32;   .endef
_main:
       pushl       %ebp
       movl       $16, %eax
       movl       %esp, %ebp
       subl $8, %esp
       andl $-16, %esp
       call  __alloca
       call  ___main
       movl       $LC0, (%esp)
       movl       $100, %eax
       movl       %eax, 4(%esp)
       call  _printf
       leave
       xorl  %eax, %eax
       ret
       .def  _printf;    .scl  3;    .type       32;   .endef
 
此时我们虽然看到_max仍然定义了,但是在main函数中却没有调用,而是直接使用了立即数100,进而打印之:
movl       $LC0, (%esp)
       movl       $100, %eax
       movl       %eax, 4(%esp)
       call  _printf
从这里可以看出,内联函数是否内联,关键要看编译器的优化选项是否打开,千万不要以为你在函数前面添加一个inline修饰符函数这个函数就是内联函数了。而宏却不一样,不管三七二十一编译器都会在预处理阶段进行替换的。下面我们仍旧用编译器生成的汇编代码来说明这个问题:
原始C代码如下,保存为macro_test.c
#include
 
#define max(x, y) \
       (x) > (y) ? (x) : (y)
 
int main(int args, char** argv)
{
       printf("the result is %d\n", max(100, 50));
       return 0;
}
 
使用gcc –S macro_test.c-o macro_test.s命令,将C代码生成汇编代码:
       .file  "macro_test.c"
       .def  ___main; .scl  2;    .type       32;   .endef
       .section .rdata,"dr"
LC0:
       .ascii "the result is %d\12\0"
       .text
.globl _main
       .def  _main;     .scl  2;    .type       32;   .endef
_main:
       pushl       %ebp
       movl       %esp, %ebp
       subl $24, %esp
       andl $-16, %esp
       movl       $0, %eax
       addl $15, %eax
       addl $15, %eax
       shrl  $4, %eax
       sall   $4, %eax
       movl       %eax, -4(%ebp)
       movl       -4(%ebp), %eax
       call  __alloca
       call  ___main
       movl       $100, 4(%esp)
       movl       $LC0, (%esp)
       call  _printf
       movl       $0, %eax
       leave
       ret
       .def  _printf;    .scl  3;    .type       32;   .endef
可以看到这部分代码的main函数跟inline_test.c优化后生成的汇编代码的main函数完全一样,当使用优化选项生成的汇编代码跟不使用优化选项生成的汇编代码完全一样,这就说明了宏是在预处理的时候替换的。
阅读(876) | 评论(0) | 转发(0) |
0

上一篇:全局变量和局部变量

下一篇:C++类

给主人留下些什么吧!~~