前几天在优快云论坛上看到一篇帖子:
在下面这段简单的代码里,实际的运算结果与笔算的不致;
#include <stdio.h>
#define function(x) ((x)*(x))
int main(void)
{
int i = 3;
printf("[01]the result is : %d\n", function(i++));
printf("[02]the result is : %d\n", function(++i));
}
按照C语言的语法结果应该是: 12, 25
但实际结果是在LINUX的GCC平台编译完后,执行为: 9 , 25
为什么会出现这样的情况了?实际是C标准只对C的自操作做了规定,而对于自运算的组合操作却没有明确规定。那么各个编译器在具体实现时,就有差別了。
为了更清淅的分析清楚这段代码在㡳层是如何运行的,我们看一下这代码汇编,当然,不能看二进制代码了,如果你是神,就另当別论了。
在LINUX下可以用gcc -S sourcode.c來编译出汇编代码。
我们來看最关键的一段
.LCFI2:
movl $3, -4(%rbp)
movl -4(%rbp), %eax
movl %eax, %esi
imull -4(%rbp), %esi
addl $1, -4(%rbp)
addl $1, -4(%rbp)
movl $.LC0, %edi
movl $0, %eax
call printf
addl $1, -4(%rbp)
addl $1, -4(%rbp)
movl -4(%rbp), %eax
movl %eax, %esi
imull -4(%rbp), %esi
movl $.LC1, %edi
movl $0, %eax
call printf
leave
ret
如果这一段还没有明白的话,那么我们换个角度来看这段汇编代码:
function(i++) function(++i)
movl $3, -4(%rbp) addl $1, -4(%rbp)
movl -4(%rbp), %eax addl $1, -4(%rbp)
movl %eax, %esi movl -4(%rbp), %eax
imull -4(%rbp), %esi movl %eax, %esi
addl $1, -4(%rbp) imull -4(%rbp), %esi
addl $1, -4(%rbp) movl $.LC1, %edi
movl $.LC0, %edi movl $0, %eax
movl $0, %eax call printf
call printf
在这里对于汇编不太了解的同学,要做一个超简单的说明,在以上一段代码中,部分汇编的操作符号,含义如下:
imull 是乘法操作
addl 是加法操作
好了,到这里大家有木有发现function(i++), function(++i)之间的別?
对了,在GCC中,前者是先做完乘法做完后, 再加;而对于后者却是先做完加法,再做乘法。这也是我们看到结果和预计的结果有出入。
这个问题,才开始的时候用标准皂C语這,语法规去分析,得出结果与实际运行的结果不一致,致使我花了好长时间,来找原因;但一致没有头绪,前几天在优快云上看到一大牛提出对于C语言,其中一些不明白的问题,早快捷的办法,就是只接编译成为汇编,去分析汇编语言,因为汇编语言中的语句,是原子操作,对于每一步操作,都可以很清楚的列出来,分析问题来一目了然。当然了,前提是你对汇编有一定的了解。
可能,你觉得,学习C语言,难道还要把汇编语言搞懂,因而就感到害怕了。其实,完全大可不必,如果你不是做㡳层硬件驱动,仅仅是为了分析C语言学习过程中一些不明白惖问题,一些基本的汇编知识就够了。如上面內文中,就两到三个操作符理解意思了,对不代码操行过程,心里就猜个大概。而这个过程对于深入理解C语言很有帮助。
用好奇的心去思考问题,享受其过程中的快乐。