volatile 修饰的变量"副作用/序列点"问题

本文通过对比带与不带volatile修饰符的变量在C语言中运算的差异,详细解析了volatile的作用及其对编译器优化的影响。通过具体的代码示例和对应的汇编代码,展示了volatile如何确保变量值的正确性和一致性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


这个问题是因为这个帖子而起, 也是这段代码有点不明白. 和自己预期的值不一样, 所以就编译反汇编看了下.

编译工具:  Code:blocks(GCC), CPU(Intel Core i3 32位), OS(windows 7 / 32bit).

参考书籍:  深入理解计算机系统(Bryant & Hallaron)

更多了解关于 volatile 可以参考: http://blog.youkuaiyun.com/kissmonx/article/details/7724757

#include <stdio.h>

int main(int argc, char ** argv)
{
    volatile int u = 0;  // 我对这个也是纳闷, 难道和编译器的优化有关??? 反汇编看了下. 大致有点了解了.

    u = u++ + ++u;       // 如果写作三个 '+' 连一块(u+++ +u 除外), 就会报出 "自增操作数必须是左值" 的错误.
    printf("%d\n", u);   // 结果是 1

    return  0;
}

汇编格式为 AT&T, 如果要产生 intel 格式的可以加编译选项如:-masm=intel

004013B7	push   %ebp                 
004013B8	mov    %esp,%ebp
004013BA	and    $0xfffffff0,%esp
004013BD	sub    $0x20,%esp       // 分配栈空间           
004013C0	call   0x401920 <__main>    
004013C5	movl   $0x0,0x1c(%esp)
004013CD	mov    0x1c(%esp),%edx  
004013D1	mov    0x1c(%esp),%eax
004013D5	inc    %eax             // u 自增一次
004013D6	mov    %eax,0x1c(%esp)  // volatile 变量每次取值都要去内存处, 防变量被意外修改而不知(这里进行的是存储器(基址 + 偏移量)寻址).
004013DA	add    %edx,%eax        // 1 + 0
004013DC	mov    %eax,0x1c(%esp)  // 然后就是在寄存器和 u 的内存地址处来回转悠, u 的值在这过程没有改变. 和其他情况确实不一样.
004013E0	lea    0x1(%edx),%eax   // %eax 值为 0x1 + 0
004013E3	mov    %eax,0x1c(%esp)  // 写入内存
004013E7	mov    0x1c(%esp),%eax  // 重新加载
004013EB	mov    %eax,0x4(%esp)   // 将 %eax 送入栈顶之下的 4 字节处, 准备输出, 栈顶要载入新值.
004013EF	movl   $0x408064,(%esp) // 将返回地址保存在栈顶
004013F6	call   0x40138c <printf>
004013FB	mov    $0x0,%eax        // 返回 0
00401400	leave
00401401	ret                     // 初学者, 注解如有误, 请指出, Thx.


// 作为对比, 这个是不加 volatile 修饰的变量运算的结果.
#include <stdio.h>

int main(int argc, char ** argv)
{
    int u = 0;

    u = u++ + ++u;      
    printf("%d\n", u);   // 结果是 3

    return  0;
}


反汇编的结果作为对比可以看出差别:

004013B7	push   %ebp
004013B8	mov    %esp,%ebp
004013BA	and    $0xfffffff0,%esp
004013BD	sub    $0x20,%esp
004013C0	call   0x401918 <__main>
004013C5	movl   $0x0,0x1c(%esp)
004013CD	incl   0x1c(%esp)          // 第一次出现自增指令 1
004013D1	mov    0x1c(%esp),%eax     // 看到没, 少用了一个寄存器, ^_^
004013D5	add    %eax,%eax           // 使用 add 指令 1 + 1. 
004013D7	mov    %eax,0x1c(%esp)     // 写入 2
004013DB	incl   0x1c(%esp)          // 第一次出现自增指令 2 + 1
004013DF	mov    0x1c(%esp),%eax     // 加载
004013E3	mov    %eax,0x4(%esp)      // 准备输出
004013E7	movl   $0x408064,(%esp)
004013EE	call   0x40138c <printf>
004013F3	mov    $0x0,%eax
004013F8	leave
004013F9	ret



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值