volatile的单纯语义

 

以前我一直认为C的volatile具有一些“平台相关”的语义,比如,与并发访问相关的栅栏。看来只有C#的volatile才有那样的语义。MS实现的C编译器连volatile操作的位置也不再保证,而是保有为了优化可以调动其位置的权利。

像下面这样的代码(VS2010 VC10编译器):

#include <Windows.h>
#include <stdio.h>


static volatile int a;
static volatile int b;

__declspec(noinline) static void f( int *p )
{
    b = 7;
    a = *p;
}


int main()
{
    a = 0;
    b = 0;

    __try
    {
        f((int *)a);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        printf("execpt\n");
    }

    printf("b=%d\n", b);
    getchar();
    return 0;
}

从C语言层面讲是在a = *p;处发生异常。但测试结果显示b=0,即没有执行b = 7;就产生了异常。
问题在于,VC10编译器将b = 7;的生成的代码调动到a = *p;产生的代码之间。机器生成的代码类似下面:

00401000  mov         ecx,dword ptr [eax]  
00401005  mov         dword ptr [__fmode+4 (40336Ch)],7 
0040100F  mov         dword ptr [__fmode+8 (403370h)],eax 


其中地址00401005为b = 7;生成的代码,而其被a = *p;生成的代码包围住。异常发生在00401000,即取地址0的值。

要想让最后b=7,就要改写f:

__declspec(noinline) static void f( int *p )
{
    b = 7;
    _ReadWriteBarrier();
    a = *p;
}

在两句话之间加入一个读写栅栏。
其实,这是一个伪栅栏(真栅栏是体系结构的机器指令),它并不生成任何代码,只是告诉编译器:在它上下两边的代码要分割开,不能跨越它。
这是个奇怪的函数,显然编译器认识他,看见他,就做了这样一个特殊的工作,这是用户实现的函数所无法做到的。
在MS的实现里,volatile的语义十分单纯,仅仅指“取消寄存器缓冲优化”(或者说,必须实际读/写内存),而不取消“重排列代码”的优化。
(想测试本篇的例子,别忘记要编译Release版本,并用O2优化。)

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值