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

来源:互联网 发布:java线程池下载多任务 编辑:程序博客网 时间:2024/05/18 01:03

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

#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, @functionmain:.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, @functionmain:.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关键字

原创粉丝点击