先来看一个简单的程序,试着回答一下面这段程序的输出是什么?
#include <stdio.h>
int main()
{
int i = 5;
int j = 6;
int p = 0;
int q = 0;
p = (i++)+(i++)+(i++);
q = (++j)+(++j)+(++j);
printf("i=%d,j=%d,p=%d,q=%d \n",i,j,p,q);
return 0;
}
根据C 语言的规则:
i++是先赋值再计算
++i是先计算再赋值
p = (i++)+(i++)+(i++); i++是先赋值再计算,原来的式子
等价于 p = 5+6+7 即18;i++ 重复了三次 i的最终值为 8;
q = (++j)+(++j)+(++j); ++j是先赋值再计算,原来的式子
等价于 p = 7+8+9 即24;++j 重复了三次 j的最终值为 9;
于是最终的结果是:
“ i=8,j=9,p=18,q=24 ”
但是在环境上实际测试的值是:
“ i=8,j=9,p=18, q=25 ”
为什么 q 是 25?
我们深入程序的内部看一看,linux 下执行,gcc -S volatitle.c -o vo.s ,注意”-S”大写。重点看注释的部分。
.file "volatitle.c"
.section .rodata
.LC0:
.string "i=%d,j=%d,p=%d,q=%d \n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
leal 4(%esp), %ecx
.cfi_def_cfa 1, 0
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
.cfi_escape 0x10,0x5,0x2,0x75,0
movl %esp, %ebp
pushl %ecx
.cfi_escape 0xf,0x3,0x75,0x7c,0x6
subl $20, %esp
movl $5, -24(%ebp) # i = 5
movl $6, -20(%ebp) # j = 6
movl $0, -16(%ebp)
movl $0, -12(%ebp)
movl -24(%ebp), %edx
leal 1(%edx), %eax
movl %eax, -24(%ebp)
movl -24(%ebp), %eax
leal 1(%eax), %ecx
movl %ecx, -24(%ebp)
leal (%edx,%eax), %ecx
movl -24(%ebp), %eax
leal 1(%eax), %edx
movl %edx, -24(%ebp)
addl %ecx, %eax
movl %eax, -16(%ebp)
addl $1, -20(%ebp) # ++j 即7
addl $1, -20(%ebp) # ++j 即8
movl -20(%ebp), %eax # 将j的值 存在 %eax
leal (%eax,%eax), %edx #把eax+eax的值装入edx中。此时就是把8+8的值16 存在了 %edx
addl $1, -20(%ebp) #++j 即9
movl -20(%ebp), %eax #将9的值存在了 %eax中
addl %edx, %eax #即 16+9=25
movl %eax, -12(%ebp)
subl $12, %esp
pushl -12(%ebp)
pushl -16(%ebp)
pushl -20(%ebp)
pushl -24(%ebp)
pushl $.LC0
call printf
addl $32, %esp
movl $0, %eax
movl -4(%ebp), %ecx
.cfi_def_cfa 1, 0
leave
.cfi_restore 5
leal -4(%ecx), %esp
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609"
.section .note.GNU-stack,"",@progbits
上面的汇编不必全部看懂,但是有些运算部分已经注释,根据注释的内容,可以看出,在计算q = (++j)1+(++j)2+(++j)3;
- 第一个(++j):计算完成之后并没有存储其值7,(优化了movl -20(%ebp), %ebx),只是使用了其在栈中的偏移地址。
- 第二个(++j):根据第一个(++j)在栈中的偏移地址:-20(%ebp) ,继续运算第二个(++j),第二次运算之后的值是8,运算完成之后,存储了栈中的值到 %eax中,这个时候才存储了j的值(movl -20(%ebp), %eax )
- 再对前面两个数进行求和运算的时候,变成了对两个%eax的运算,即为8+8=16
最后一个(++j) :对于最后一个(++j)运算的过程中,没有编译器优化
addl $1, -20(%ebp) #++j 即9
movl -20(%ebp), %eax #将9的值存在了 %eax中.最后把前两个数只和(存在%edx)和最后一个 数(存在%eax) 相加。addl %edx, %eax # 16+9=25;
在计算前两个 (++j)1+(++j)2)的时候,编译器做了一个常量合并的优化,如何才能不优化呢?
定义变量的时候,加上volatile 关键字。
#include <stdio.h>
int main()
{
int i = 5;
volatile int j = 6;
int p = 0;
int q = 0;
p = (i++)+(i++)+(i++);
q = (++j)+(++j)+(++j);
printf("i=%d,j=%d,p=%d,q=%d \n",i,j,p,q);
return 0;
}
加上一个 volatitle关键字后,执行的结果是
“ i=8,j=9,p=18,q=24 ”
linux 下执行,gcc -S volatitle.c -o voo.s ,注意”-S”大写,重点看注释的部分。
.file "volatitle.c"
.section .rodata
.LC0:
.string "i=%d,j=%d,p=%d,q=%d \n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
leal 4(%esp), %ecx
.cfi_def_cfa 1, 0
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
.cfi_escape 0x10,0x5,0x2,0x75,0
movl %esp, %ebp
pushl %ecx
.cfi_escape 0xf,0x3,0x75,0x7c,0x6
subl $20, %esp
movl $5, -20(%ebp) # i = 5
movl $6, -24(%ebp) # j = 5
movl $0, -16(%ebp)
movl $0, -12(%ebp)
movl -20(%ebp), %edx
leal 1(%edx), %eax
movl %eax, -20(%ebp)
movl -20(%ebp), %eax
leal 1(%eax), %ecx
movl %ecx, -20(%ebp)
leal (%edx,%eax), %ecx
movl -20(%ebp), %eax
leal 1(%eax), %edx
movl %edx, -20(%ebp)
addl %ecx, %eax
movl %eax, -16(%ebp)
movl -24(%ebp), %eax #存j
leal 1(%eax), %edx #++j,存入%edx
movl %edx, -24(%ebp) #取出到 栈中
movl -24(%ebp), %eax #
addl $1, %eax #++j存入%eax
movl %eax, -24(%ebp) #
addl %eax, %edx #计算前两个(++j)的结果,存入 %edx中。
movl -24(%ebp), %eax #存栈
addl $1, %eax #++j 存入%eax
movl %eax, -24(%ebp) #
addl %edx, %eax
movl %eax, -12(%ebp)
movl -24(%ebp), %eax
subl $12, %esp
pushl -12(%ebp)
pushl -16(%ebp)
pushl %eax
pushl -20(%ebp)
pushl $.LC0
call printf
addl $32, %esp
movl $0, %eax
movl -4(%ebp), %ecx
.cfi_def_cfa 1, 0
leave
.cfi_restore 5
leal -4(%ecx), %esp
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609"
.section .note.GNU-stack,"",@progbits
汇编中j的每一次运算都去做了存值的操作。
这是一篇实例,想看更多关于volatile的理论部分,请参考详解C中volatile关键字