VS2005过度优化的陷阱

      最近在调试一块S3C6410的核心板。为了验证核心板扩展槽上的所有接口,LD特地设计了一个扩展底板,上面遍布红红绿绿的LED。软件上用总线读写和GPIO来控制这些灯。观察LED的亮灭,该亮的亮,该灭的灭,说明接口没有问题,否则就需要拿给硬件检查了。

      考虑到既有总线读写,又有大量的GPIO控制,写驱动程序会相当麻烦,所以使用了《WinCE6.0中应用程序如何直接访问物理空间》中介绍的方法,直接在测试程序中实现总线访问和GPIO控制。测试程序写的还算顺利,一会儿就搞定了。不过,有些IO的控制有点奇怪,总是莫名其妙的被修改。《ARM-WinCE5.0-寄存器读写工具》中曾提到过GPIO被修改的原因和解决办法。不过,在这里似乎都解释不通。因为现在只有测试应用程序能够修改GPIO,不存在被别的程序修改的可能。难道是代码写的有问题?代码已经简单到不能再简单了,如下。

#include  " stdafx.h "
#include <windows.h>
#include  " s3c6410.h "

typedef  struct {
    LPVOID    pvDestMem;
    DWORD    dwPhysAddr;
    DWORD    dwSize;
} VIRTUAL_COPY_EX_DATA,*PVIRTUAL_COPY_EX_DATA;

#define IOCTL_VIRTUAL_COPY_EX CTL_CODE (FILE_DEVICE_UNKNOWN,3333,METHOD_BUFFERED,FILE_ANY_ACCESS)

LPVOID GetVirtual(DWORD dwPhyBaseAddress, DWORD dwSize)
{
    LPVOID pVirtual;
    VIRTUAL_COPY_EX_DATA vced;

     if(dwPhyBaseAddress &  0xFFF)
    {
         return NULL;
    }

    vced.dwPhysAddr = dwPhyBaseAddress>> 8;
    pVirtual = VirtualAlloc( 0, dwSize, MEM_RESERVE, PAGE_NOACCESS);
    vced.pvDestMem = pVirtual;
    vced.dwSize = dwSize;
    KernelIoControl(IOCTL_VIRTUAL_COPY_EX, &vced,  sizeof(vced), NULL, NULL, NULL);

     return pVirtual;
}

int _tmain( int argc, _TCHAR* argv[])
{
     volatile  PS3C6410_GPIO_REG pGPIO1;

    pGPIO1 = (PS3C6410_GPIO_REG)GetVirtual(S3C6410_BASE_REG_PA_GPIO,  sizeof(S3C6410_GPIO_REG));

    pGPIO1->GPBCON =  0x22222222;
    printf( " \r\npGPIO1->GPBCON(%08x) ", pGPIO1->GPBCON);    
    pGPIO1->GPBCON &= (~( 0xFFFF <<  16));
    printf( " -->(%08x) ", pGPIO1->GPBCON);
    
     return  0;
}

      预想的执行结果应该是pGPIO1->GPBCON(02222222)-->(00002222)而实际结果却是pGPIO1->GPBCON(02222222)-->(00000000),不仅仅是高16位被清零了,所有位都被清零!

      跟LD一起调试时,愣是没找到原因。回来的路上,忽然想到可能是编译器优化的问题。不过,马上又把它给否了——已经加了volatile啊。难道volatile不起效?不太可能吧?

      到家以后,用IDA工具看了下测试程序的汇编代码,果不其然,跟预想的不一样,代码如下。

. text: 00011000 wmain                                    ;  CODE XREF: mainCRTStartupHelper+8Cp
. text: 00011000                                          ;  DATA XREF: .pdata:00014000o
. text: 00011000
. text: 00011000 var_24          = -0x24
. text: 00011000 var_20          = -0x20
. text: 00011000 var_1C          = -0x1C
. text: 00011000 var_18          = -0x18
. text: 00011000 var_14          = -0x14
. text: 00011000 var_10          = -0x10
. text: 00011000
. text: 00011000                 STMFD   SP!, {R4,R5,LR}
. text: 00011004                  SUB     SP, SP, #0x18
. text: 00011008                 MOVL    R3, 0x7F0080
. text: 00011010                  STR     R3, [SP,#0x24+var_14]
. text: 00011014                  MOV     R3, # 1
. text: 00011018                  MOV     R2, #0x2000
. text: 0001101C                  MOV     R1, #0x940
. text: 00011020                  MOV     R0, # 0
. text: 00011024                 BL      VirtualAlloc
. text: 00011028                  MOV     R4, R0
. text: 0001102C                 LDR     R0, =0x223414
. text: 00011030                  MOV     LR, #0x940
. text: 00011034                  MOV     R5, # 0
. text: 00011038                  MOV     R3, # 0
. text: 0001103C                  MOV     R2, #0xC
. text: 00011040                  ADD     R1, SP, #0x24+var_18
. text: 00011044                  STR     R4, [SP,#0x24+var_18]
. text: 00011048                  STR     LR, [SP,#0x24+var_10]
. text: 0001104C                  STR     R5, [SP,#0x24+var_20]
. text: 00011050                  STR     R5, [SP,#0x24+var_24]
. text: 00011054                 BL      KernelIoControl
. text: 00011058                  STR     R4, [SP,#0x24+var_1C]
. text: 0001105C                 LDR     R1, =0x22222222
. text: 00011060                 LDR     R3, [SP,#0x24+var_1C]
. text: 00011064                 LDR     R2, [SP,#0x24+var_1C]
. text: 00011068                 LDR     R0, =aPgpio1Gpbcon08
. text: 0001106C                  STR     R1, [R3,#0x20]
. text: 00011070                 LDR     R1, [R2,#0x20]
. text: 00011074                  BL      printf
. text: 00011078                 LDR     R3, [SP,#0x24+var_1C]
. text: 0001107C                 LDR     R2, [SP,#0x24+var_1C]
. text: 00011080                 LDR     R0, =a08x
. text: 00011084                  STRH    R5, [R3,#0x22]
. text: 00011088                 LDR     R1, [R2,#0x20]
. text: 0001108C                  BL      printf
. text: 00011090                  MOV     R0, # 0
. text: 00011094                  ADD     SP, SP, #0x18
. text: 00011098                 LDMFD   SP!, {R4,R5,PC}
. text: 00011098  ;  End of function wmain

      pGPIO1->GPBCON &= (~(0xFFFF << 16));被优化得无影无踪,仅剩STRH    R5, [R3,#0x22],而R5此时此刻为0x00000000。原因似乎是找到了,确实是编译优化的问题,但却更让人不解,这里的volatile为什么不起作用?在TCC8901上,一直都这么用,从没出现过这问题。差别在哪里呢?

      后来对比了TCC8901的BSP,有了进一步的发现,它定义GPIO结构体的代码如下。

typedef  struct _GPIO{
     volatile unsigned  int    GPADAT;                 //    0x000  R/W  0x00000000  GPA Data Register 
     volatile unsigned  int    GPAEN;                  //    0x004  R/W  0x00000000  GPA Output Enable Register 
     volatile unsigned  int    GPASET;                 //    0x008  W  -  OR function on GPA Output Data 
     volatile unsigned  int    GPACLR;                 //    0x00C  W  -  BIC function on GPA Output Data 
     volatile unsigned  int    GPAXOR;                 //    0x010  W  -  XOR function on GPA Output Data 
     volatile unsigned  int    GPACD0;                 //    0x014  W  0x55555555  Driver strength Control 0 on GPA Output Data 
     volatile unsigned  int    GPACD1;                 //    0x018  W  0x00000000  Driver strength Control 1 on GPA Output Data 
     volatile unsigned  int    GPAPD0;                 //    0x01C  W  0x55555555  Pull-Up/Down function on GPA Output Data 
     volatile unsigned  int    GPAPD1;                 //    0x020  W  0x00000000  Pull-Up/Down function on GPA Output Data 
     volatile unsigned  int    GPAFN0;                 //    0x024  W  0x00000000  Port Configuration on GPA Output Data 
     volatile unsigned  int    GPAFN1;                 //    0x028  W  0x00000000  Port Configuration on GPA Output Data 
     volatile unsigned  int    GPAFN2;                 //    0x02C  W  0x00000000  Port Configuration on GPA Output Data 
     volatile unsigned  int    GPAFN3;                 //    0x030  W  0x00000000  Port Configuration on GPA Output Data 
     volatile unsigned  int    NOTDEFINE0[ 3];          //      0x034-0x03C     Reserved 
     volatile unsigned  int    GPBDAT;                 //    0x040  R/W  0x00000000  GPB Data Register 
     volatile unsigned  int    GPBEN;                  //    0x044  R/W  0x00000000  GPB Output Enable Register 
     volatile unsigned  int    GPBSET;                 //    0x048  W  -  OR function on GPB Output Data 
     volatile unsigned  int    GPBCLR;                 //    0x04C  W  -  BIC function on GPB Output Data 
     volatile unsigned  int    GPBXOR;                 //    0x050  W  -  XOR function on GPB Output Data 
     volatile unsigned  int    GPBCD0;                 //    0x054  W  0x55555555  Driver strength Control 0 on GPB Output Data 
     volatile unsigned  int    GPBCD1;                 //    0x058  W  0x00000000  Driver strength Control 1 on GPB Output Data 
     volatile unsigned  int    GPBPD0;                 //    0x05C  W  0x55555555  Pull-Up/Down function on GPB Output Data 
     volatile unsigned  int    GPBPD1;                 //    0x060  W  0x00000000  Pull-Up/Down function on GPB Output Data 
     volatile unsigned  int    GPBFN0;                 //    0x064  W  0x00000000  Port Configuration on GPB Output Data 
     volatile unsigned  int    GPBFN1;                 //    0x068  W  0x00000000  Port Configuration on GPB Output Data 
     volatile unsigned  int    GPBFN2;                 //    0x06C  W  0x00000000  Port Configuration on GPB Output Data 
     volatile unsigned  int    GPBFN3;                 //    0x070  W  0x00000000  Port Configuration on GPB Output Data 
     volatile unsigned  int    NOTDEFINE1[ 3];          //       0x074-0x07C     Reserved

      而S3C6410定义GPIO的结构体的代码如下。

typedef  struct
{
    UINT32 GPACON;         //  000
    UINT32 GPADAT;         //  004
    UINT32 GPAPUD;         //  008
    UINT32 GPACONSLP;     //  00c

    UINT32 GPAPUDSLP;     //  010
    UINT32 PAD1[ 3];         //  014~01f

    UINT32 GPBCON;         //  020
    UINT32 GPBDAT;         //  024
    UINT32 GPBPUD;         //  028
    UINT32 GPBCONSLP;     //  02c

    UINT32 GPBPUDSLP;     //  030
    UINT32 PAD2[ 3];         //  034~03f

      可以看到,在TCC8901的结构体定义中都有volatile,而S3C6410中是没有的。也许这就是原因,结构体内部必须有volatile才行?定义结构体指针变量时的volatile作用域有限,并没有起到预想的作用——阻止编译器的优化。后来测试发现,如果结构体内部有volatile,定义结构体指针变量时省去volatile都行。示例代码如下,执行结果也是正确的(前提:在结构体定义中UINT32 GPBCON前增加了volatile!!!)。

int _tmain( int argc, _TCHAR* argv[])
{
     PS3C6410_GPIO_REG pGPIO1;

    pGPIO1 = (PS3C6410_GPIO_REG)GetVirtual(S3C6410_BASE_REG_PA_GPIO,  sizeof(S3C6410_GPIO_REG));

    pGPIO1->GPBCON =  0x22222222;
    printf( " \r\npGPIO1->GPBCON(%08x) ", pGPIO1->GPBCON);    
    pGPIO1->GPBCON &= (~( 0xFFFF <<  16));
    printf( " -->(%08x) ", pGPIO1->GPBCON);
    
     return  0;
}

      问题的原因似乎更明朗了。但好似还是有点问题。因为在结构体中添加volatile前,下面这段代码的执行也是正确的。

int _tmain( int argc, _TCHAR* argv[])
{
     volatile  S3C6410_GPIO_REG* pGPIO1;

    pGPIO1 = (PS3C6410_GPIO_REG)GetVirtual(S3C6410_BASE_REG_PA_GPIO,  sizeof(S3C6410_GPIO_REG));

    pGPIO1->GPBCON =  0x22222222;
    printf( " \r\npGPIO1->GPBCON(%08x) ", pGPIO1->GPBCON);    
    pGPIO1->GPBCON &= (~( 0xFFFF <<  16));
    printf( " -->(%08x) ", pGPIO1->GPBCON);
    
     return  0;
}

      可以看到,与问题代码唯一的不同就是"volatile PS3C6410_GPIO_REG pGPIO1;”变成"volatile S3C6410_GPIO_REG* pGPIO1; ” ,"PS3C6410_GPIO_REG”和"S3C6410_GPIO_REG*”不是等价的吗?通常更习惯用“PS3C6410_GPIO_REG”,这也有问题么。

      问题还没有结束,在结构体中添加volatile前,下面这段代码也能正确运行。

int _tmain( int argc, _TCHAR* argv[])
{
     volatile PS3C6410_GPIO_REG pGPIO1;

    pGPIO1 = (PS3C6410_GPIO_REG)GetVirtual(S3C6410_BASE_REG_PA_GPIO,  sizeof(S3C6410_GPIO_REG));

    pGPIO1->GPBCON =  0x22222222;
    printf( " \r\npGPIO1->GPBCON(%08x) ", pGPIO1->GPBCON);    
     pGPIO1->GPBCON = pGPIO1->GPBCON & (~( 0xFFFF  <<  16 ));
    printf( " -->(%08x) ", pGPIO1->GPBCON);
    
     return  0;
}

      这段代码与问题代码的唯一区别pGPIO1->GPBCON &= (~(0xFFFF << 16));变成pGPIO1->GPBCON = pGPIO1->GPBCON & (~(0xFFFF << 16));

      难道这也有区别?那为什么会有这个区别呢?

      纳闷…纠结…晕…

      看来现在还不能给这个问题做一个全面的总结,只有以下几点教训:

      用volatile时得多留些心眼,有时用了volatile也不一定管用;

      对于MCU专用寄存器结构体的定义,最好内部加上volatile;

      在定义寄存器结体指针变量时,尽量使用形如S3C6410_GPIO_REG*的方式,慎用PS3C6410_GPIO_REG;

      在对MCU寄存器进行位运算时,尽量使用 a = a & b;的方式,慎用a &=b;

      WinCE6.0中应用程序直接访问物理空间有隐患,谨慎使用;

      VS2005的编译器优化很强大,必须小心优化过度,但不也能因噎废食;

      只有弄清楚优化规则的来龙去脉,才能避免写出被编译器优化得面目全非的代码;

      PS:疑惑还没有彻底解决,有知道的TX请明示,感兴趣的TX也请留言讨论,看晕了的TX请多包涵。祝各位节日快乐!

你可能感兴趣的:(VS2005过度优化的陷阱)