在大多數情況下,把變量緩存在寄存器中是一個非常有價值的優化方法,如果不用的話很可惜。C和C++給你提供了顯式禁用這種緩存優化的機會。如果你聲明變量是使用了volatile修飾符,編譯器就不會把這個變量緩存在寄存器裏——每次訪問都將去存取變量在內存中的實際位置。
1、中斷服務程序中修改的供其它程序檢測的變量需要加volatile;
2、多任務環境下各任務間共享的標誌應該加volatile;
3、寄存器對應的變量值一般最好加上volatile,例如:
volatile UINT32* Register; // Register is the address of the register
又如:
*(volatile UINT32 *) (UINTN) (ApicBase + APIC_REGISTER_ICR_HIGH_OFFSET) = ICRHigh;
*(volatile UINT32 *) (UINTN) (ApicBase + APIC_REGISTER_ICR_LOW_OFFSET) = ICRLow;
4、當多條指令往同一個寄存器內按照時序寫值時,如果用O2優化,程序只寫入最後一條指令的寫!顯然是錯誤的!
===============
自己的實踐!
用cl test.c /FAcs /Zi /O2編譯下面程序。
#include "stdio.h"
unsigned int q;
int main()
{
// volatile unsigned int *i;
unsigned int *i;
unsigned int p;
p = 0x55aa;
i = (unsigned int *) 0x310;
*i = p;
p = *i; // 如果沒有volatile,編譯器優化O2認爲此步多餘,在下一步會直接將0x55aa賦給q;
// 但是如果i是一個硬件寄存器的地址,需要寫入一個值再讀出狀態值,那麼q的值就不是正確的狀態值,而是0x55aa!這顯然是錯誤的。
q = p;
return 0;
}
test_non_volatile.cod:
; Listing generated by Microsoft (R) Optimizing Compiler Version 13.10.3077
TITLE test.c
.386P
include listing.inc
if @Version gt 510
.model FLAT
else
_TEXT SEGMENT PARA USE32 PUBLIC 'CODE'
_TEXT ENDS
_DATA SEGMENT DWORD USE32 PUBLIC 'DATA'
_DATA ENDS
CONST SEGMENT DWORD USE32 PUBLIC 'CONST'
CONST ENDS
_BSS SEGMENT DWORD USE32 PUBLIC 'BSS'
_BSS ENDS
$$SYMBOLS SEGMENT BYTE USE32 'DEBSYM'
$$SYMBOLS ENDS
$$TYPES SEGMENT BYTE USE32 'DEBTYP'
$$TYPES ENDS
_TLS SEGMENT DWORD USE32 PUBLIC 'TLS'
_TLS ENDS
; COMDAT _main
_TEXT SEGMENT PARA USE32 PUBLIC 'CODE'
_TEXT ENDS
sxdata SEGMENT DWORD USE32 'SXDATA'
sxdata ENDS
FLAT GROUP _DATA, CONST, _BSS
ASSUME CS: FLAT, DS: FLAT, SS: FLAT
endif
INCLUDELIB LIBC
INCLUDELIB OLDNAMES
_DATA SEGMENT
COMM _q:DWORD
_DATA ENDS
PUBLIC _main
; Function compile flags: /Ogty
; File e:/temp/test.c
; COMDAT _main
_TEXT SEGMENT
_main PROC NEAR ; COMDAT
; 7 : // volatile unsigned int *i;
; 8 : unsigned int *i;
; 9 : unsigned int p;
; 10 :
; 11 : p = 0x55aa;
; 12 :
; 13 : i = (unsigned int *) 0x310;
00000 b8 10 03 00 00 mov eax, 784 ; 00000310H
; 14 : *i = p;
00005 c7 00 aa 55 00
00 mov DWORD PTR [eax], 21930 ; 000055aaH
; 15 : p = *i; // 忽略此句!
; 16 :
; 17 : q = p;
0000b c7 05 00 00 00
00 aa 55 00 00 mov DWORD PTR _q, 21930 ; 000055aaH //直接賦0x55aa
; 18 :
; 19 : return 0;
00015 33 c0 xor eax, eax
; 20 : }
00017 c3 ret 0
_main ENDP
_TEXT ENDS
END
test_volatile.cod:
; Listing generated by Microsoft (R) Optimizing Compiler Version 13.10.3077
TITLE test.c
.386P
include listing.inc
if @Version gt 510
.model FLAT
else
_TEXT SEGMENT PARA USE32 PUBLIC 'CODE'
_TEXT ENDS
_DATA SEGMENT DWORD USE32 PUBLIC 'DATA'
_DATA ENDS
CONST SEGMENT DWORD USE32 PUBLIC 'CONST'
CONST ENDS
_BSS SEGMENT DWORD USE32 PUBLIC 'BSS'
_BSS ENDS
$$SYMBOLS SEGMENT BYTE USE32 'DEBSYM'
$$SYMBOLS ENDS
$$TYPES SEGMENT BYTE USE32 'DEBTYP'
$$TYPES ENDS
_TLS SEGMENT DWORD USE32 PUBLIC 'TLS'
_TLS ENDS
; COMDAT _main
_TEXT SEGMENT PARA USE32 PUBLIC 'CODE'
_TEXT ENDS
sxdata SEGMENT DWORD USE32 'SXDATA'
sxdata ENDS
FLAT GROUP _DATA, CONST, _BSS
ASSUME CS: FLAT, DS: FLAT, SS: FLAT
endif
INCLUDELIB LIBC
INCLUDELIB OLDNAMES
_DATA SEGMENT
COMM _q:DWORD
_DATA ENDS
PUBLIC _main
; Function compile flags: /Ogty
; File e:/temp/test.c
; COMDAT _main
_TEXT SEGMENT
_main PROC NEAR ; COMDAT
; 7 : volatile unsigned int *i;
; 8 : // unsigned int *i;
; 9 : unsigned int p;
; 10 :
; 11 : p = 0x55aa;
; 12 :
; 13 : i = (unsigned int *) 0x310;
00000 b8 10 03 00 00 mov eax, 784 ; 00000310H
; 14 : *i = p;
00005 c7 00 aa 55 00
00 mov DWORD PTR [eax], 21930 ; 000055aaH
; 15 : p = *i;
; 16 :
; 17 : q = p;
0000b 8b 00 mov eax, DWORD PTR [eax] // 彙編顯式將內存i中的值賦給p
0000d a3 00 00 00 00 mov DWORD PTR _q, eax //q也能正確得到真實的內存中的值
; 18 :
; 19 : return 0;
00012 33 c0 xor eax, eax
; 20 : }
00014 c3 ret 0
_main ENDP
_TEXT ENDS
END
如果我們不加優化選項O2, cod文件如下,可見彙編代碼也是正確的。
; Listing generated by Microsoft (R) Optimizing Compiler Version 13.10.3077
TITLE test.c
.386P
include listing.inc
if @Version gt 510
.model FLAT
else
_TEXT SEGMENT PARA USE32 PUBLIC 'CODE'
_TEXT ENDS
_DATA SEGMENT DWORD USE32 PUBLIC 'DATA'
_DATA ENDS
CONST SEGMENT DWORD USE32 PUBLIC 'CONST'
CONST ENDS
_BSS SEGMENT DWORD USE32 PUBLIC 'BSS'
_BSS ENDS
$$SYMBOLS SEGMENT BYTE USE32 'DEBSYM'
$$SYMBOLS ENDS
$$TYPES SEGMENT BYTE USE32 'DEBTYP'
$$TYPES ENDS
_TLS SEGMENT DWORD USE32 PUBLIC 'TLS'
_TLS ENDS
sxdata SEGMENT DWORD USE32 'SXDATA'
sxdata ENDS
FLAT GROUP _DATA, CONST, _BSS
ASSUME CS: FLAT, DS: FLAT, SS: FLAT
endif
INCLUDELIB LIBC
INCLUDELIB OLDNAMES
_DATA SEGMENT
COMM _q:DWORD
_DATA ENDS
PUBLIC _main
; Function compile flags: /Odt
; File e:/temp/test.c
_TEXT SEGMENT
_i$ = -8 ; size = 4
_p$ = -4 ; size = 4
_main PROC NEAR
; 6 : {
00000 55 push ebp
00001 8b ec mov ebp, esp
00003 83 ec 08 sub esp, 8
; 7 : // volatile unsigned int *i;
; 8 : unsigned int *i;
; 9 : unsigned int p;
; 10 :
; 11 : p = 0x55aa;
00006 c7 45 fc aa 55
00 00 mov DWORD PTR _p$[ebp], 21930 ; 000055aaH
; 12 :
; 13 : i = (unsigned int *) 0x310;
0000d c7 45 f8 10 03
00 00 mov DWORD PTR _i$[ebp], 784 ; 00000310H
; 14 : *i = p;
00014 8b 45 f8 mov eax, DWORD PTR _i$[ebp]
00017 8b 4d fc mov ecx, DWORD PTR _p$[ebp]
0001a 89 08 mov DWORD PTR [eax], ecx
; 15 : p = *i;
0001c 8b 55 f8 mov edx, DWORD PTR _i$[ebp]
0001f 8b 02 mov eax, DWORD PTR [edx]
00021 89 45 fc mov DWORD PTR _p$[ebp], eax
; 16 :
; 17 : q = p;
00024 8b 4d fc mov ecx, DWORD PTR _p$[ebp]
00027 89 0d 00 00 00
00 mov DWORD PTR _q, ecx
; 18 :
; 19 : return 0;
0002d 33 c0 xor eax, eax
; 20 : }
0002f 8b e5 mov esp, ebp
00031 5d pop ebp
00032 c3 ret 0
_main ENDP
_TEXT ENDS
END