通过实例理解:C语言 volatile 关键字

先来看一个简单的程序,试着回答一下面这段程序的输出是什么?

#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;

  1. 第一个(++j):计算完成之后并没有存储其值7,(优化了movl -20(%ebp), %ebx),只是使用了其在栈中的偏移地址。
  2. 第二个(++j):根据第一个(++j)在栈中的偏移地址:-20(%ebp) ,继续运算第二个(++j),第二次运算之后的值是8,运算完成之后,存储了栈中的值到 %eax中,这个时候才存储了j的值(movl -20(%ebp), %eax )
  3. 再对前面两个数进行求和运算的时候,变成了对两个%eax的运算,即为8+8=16
  4. 最后一个(++j) :对于最后一个(++j)运算的过程中,没有编译器优化

    addl $1, -20(%ebp) #++j 即9
    movl -20(%ebp), %eax #将9的值存在了 %eax中.

  5. 最后把前两个数只和(存在%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关键字

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值