C语言之volatile用法(二十一),2021最新Android面试笔试题目分享
int main (void)
{
int i = 10;
int a = i; //優化
int b = i;
printf (“i = %d\n”, b);
return 0;
}
//編譯優化、查看匯編
gcc -O2 -S test.c
cat test.s
.file?? ?“test.c”
.section?? ?.rodata.str1.1,“aMS”,@progbits,1
.LC0:
.string?? ?“i = %d\n”
.section?? ?.text.startup,“ax”,@progbits
.p2align 4,15
.globl?? ?main
.type?? ?main, @function
main:
.LFB22:
.cfi_startproc
pushl?? ?%ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl?? ?%esp, %ebp
.cfi_def_cfa_register 5
andl?? ?$-16, %esp
subl?? ?$16, %esp
movl?? ?$10, 8(%esp)
movl?? ?$.LC0, 4(%esp)
movl?? ?$1, (%esp)
call?? ?__printf_chk
xorl?? ?%eax, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE22:
.size?? ?main, .-main
.ident?? ?“GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3”
.section?? ?.note.GNU-stack,"",@progbits
//示例二
#include <stdio.h>
int main (void)
{
volatile int i = 10;
int a = i; //未優化
int b = i;
printf (“i = %d\n”, b);
return 0;
}
//編譯優化、查看匯編
gcc -O2 -S test.c
cat test.s
.file?? ?“test.c”
.section?? ?.rodata.str1.1,“aMS”,@progbits,1
.LC0:
.string?? ?“i = %d\n”
.section?? ?.text.startup,“ax”,@progbits
.p2align 4,15
.globl?? ?main
.type?? ?main, @function
main:
.LFB22:
.cfi_startproc
pushl
《Android學習筆記總結+最新移動架構視頻+大廠安卓面試真題+項目實戰源碼講義》
【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整內容開源分享
%ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl?? ?%esp, %ebp
.cfi_def_cfa_register 5
andl?? ?$-16, %esp
subl?? ?$32, %esp
movl?? ?$10, 28(%esp)
movl?? ?28(%esp), %eax
movl?? ?28(%esp), %eax
movl?? ?$.LC0, 4(%esp)
movl?? ?$1, (%esp)
movl?? ?%eax, 8(%esp)
call?? ?__printf_chk
xorl?? ?%eax, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE22:
.size?? ?main, .-main
.ident?? ?“GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3”
.section?? ?.note.GNU-stack,"",@progbits
比較:
可以清楚的看到:使用 volatile 的代碼編譯未優化。
volatile 指出 i 是隨時可能發生變化的,每次使用它的時候必須從 i的地址中讀取,因而編譯器生成的匯編代碼會重新從i的地址讀取數據放在 b 中。而優化做法是,由于編譯器發現兩次從 i讀數據的代碼之間的代碼沒有對 i 進行過操作,它會自動把上次讀的數據放在 b 中。而不是重新從 i 里面讀。這樣以來,如果 i是一個寄存器變量或者表示一個端口數據就容易出錯,所以說 volatile 可以保證對特殊地址的穩定訪問。
如果上述例子,還是不夠明顯:
#include <stdio.h>
#include <sys/timeb.h>
long long getSystemTime() {
struct timeb t;
ftime(&t);
return 1000 * t.time + t.millitm;
}
#define TIME 1000000000
int main(void)
{
volatile int a, b = TIME; /* volatile修飾變量 */
int x, y = TIME; ? ? ? ? ?/* 一般變量 ? */
long long start = 0, end = 0;
start=getSystemTime();
for (a = 0; a < b; a++);
end=getSystemTime();
printf(“vloatile修飾變量用時: %lld ms\n”, end - start);
start=getSystemTime();
for (x = 0; x < y; x++);
end=getSystemTime();
printf(“一般變量用時: %lld ms\n”, end - start);
return 0;
}
編譯:gcc test.c
輸出結果:
vloatile修飾變量用時: 3738 ms
一般變量用時: 3742 ms
優化編譯:gcc -O2 test.c
輸出結果:
vloatile修飾變量用時: 3550 ms
一般變量用時: 0 ms
可明顯看出:
for(int i=0; i<100000; i++);
這個語句用來測試空循環的速度的,但是編譯器肯定要把它優化掉,根本就不執行。
如果你寫成,
for(volatile int i=0; i<100000; i++);
它就會執行了。
我們用上面的例子基本已經搞明白,volatile 不會被編譯器優化了,現在講點理論知識。
參看:C語言中volatile關鍵字的作用
1、編譯器優化介紹:
由于內存訪問速度遠不及CPU處理速度,為提高機器整體性能,在硬件上引入硬件高速緩存Cache,加速對內存的訪問。另外在現代CPU中指令的執行并不一定嚴格按照順序執行,沒有相關性的指令可以亂序執行,以充分利用CPU的指令流水線,提高執行速度。以上是硬件級別的優化。再看軟件一級的優化:一種是在編寫代碼時由程序員優化,另一種是由編譯器進行優化。編譯器優化常用的方法有:將內存變量緩存到寄存器;調整指令順序充分利用CPU指令流水線,常見的是重新排序讀寫指令。對常規內存進行優化的時候,這些優化是透明的,而且效率很好。由編譯器優化或者硬件重新排序引起的問題的解決辦法是在從硬件(或者其他處理器)的角度看必須以特定順序執行的操作之間設置內存屏障(memory barrier),Linux 提供了一個宏解決編譯器的執行順序問題。
void Barrier(void)
這個函數通知編譯器插入一個內存屏障,但對硬件無效,編譯后的代碼會把當前CPU寄存器中的所有修改過的數值存入內存,需要這些數據的時候再重新從內存中讀出。
2、volatile總是與優化有關,編譯器有一種技術叫做數據流分析,分析程序中的變量在哪里賦值、在哪里使用、在哪里失效,分析結果可以用于常量合并,常量傳播等優化,進一步可以消除一些代碼。但有時這些優化不是程序所需要的,這時可以用volatile關鍵字禁止做這些優化。
volatile的本意是“易變的” 因為訪問寄存器要比訪問內存單元快的多,所以編譯器一般都會作減少存取內存的優化,但有可能會讀臟數據。當要求使用volatile聲明變量值的時候,系統總是重新從它所在的內存讀取數據,即使它前面的指令剛剛從該處讀取過數據。精確地說就是,遇到這個關鍵字聲明的變量,編譯器對訪問該變量的代碼就不再進行優化,從而可以提供對特殊地址的穩定訪問;如果不使用valatile,則編譯器將對所聲明的語句進行優化。(簡潔的說就是:volatile關鍵詞影響編譯器編譯的結果,用volatile聲明的變量表示該變量隨時可能發生變化,與該變量有關的運算,不要進行編譯優化,以免出錯)
三、volatile 使用
1、并行設備的硬件寄存器(如:狀態寄存器)
存儲器映射的硬件寄存器通常也要加 voliate,因為每次對它的讀寫都可能有不同意義。
例如:
假設要對一個設備進行初始化,此設備的某一個寄存器為0xff800000。
int ?*output = (unsigned ?int *)0xff800000;//定義一個IO端口;
int ? init(void)
{
int i;
for(i=0;i< 10;i++){
*output = i;
}
}
經過編譯器優化后,編譯器認為前面循環半天都是廢話,對最后的結果毫無影響,因為最終只是將output這個指針賦值為 9,所以編譯器最后給你編譯編譯的代碼結果相當于:
int ?init(void)
{
*output = 9;
}
如果你對此外部設備進行初始化的過程是必須是像上面代碼一樣順序的對其賦值,顯然優化過程并不能達到目的。反之如果你不是對此端口反復寫操作,而是反復讀操作,其結果是一樣的,編譯器在優化后,也許你的代碼對此地址的讀操作只做了一次。然而從代碼角度看是沒有任何問題的。這時候就該使用volatile通知編譯器這個變量是一個不穩定的,在遇到此變量時候不要優化。
再例如上面提到的?volatile 用于相關寄存器定義
//編譯led.c文件
#define GPC1CON *((volatile unsigned int*)0xE0200080)
#define GPC1DAT *((volatile unsigned int*)0xE0200084)
#define GPC1PUD *((volatile unsigned int*)0xE0200088)
//隱式聲明
void delay (unsigned int);
void led_test (void) {
//配置相應管腳為輸出功能 GPC1_3
GPC1CON &= ~(0x0f << 12);
GPC1CON |= (1 << 12);
//GPC1_4為輸出功能
GPC1CON |= (1 << 16);
//禁止內部上拉下拉功能
GPC1PUD &= ~(0x03 << 6);
GPC1PUD &= ~(0x03 << 8);
while (1) {
//燈亮
GPC1DAT |= (1 << 3);
GPC1DAT |= (1 << 4);
delay (0x100000);
//燈滅
GPC1DAT &= ~(1 << 3);
GPC1DAT &= ~(1 << 4);
delay (0x100000);
}
}
void delay (unsigned int n) {
unsigned int i = 0;
for (i = n; i != 0; i–);
}
編譯:
arm-linux-gcc -c led.c -o led.o –nostdlib
不使用標準庫,生成led.o文件
#define GPC1CON *((volatile unsigned int*)0xE0200080) ??怎么理解?
這里其實就是定義了一個指針變量。
GPC1CON 為寄存器名稱、0xE0200080 為寄存器地址、(volatile unsigned int*) 為強制類型轉換。
我們知道 volatile 和 const 一樣為類型修飾符,不改變變量類型。
寄存器地址為什么要加 volatile 修飾呢?
是因為,這些寄存器里面的值是隨時變化的。如果我們沒有將這個地址強制類型轉換成 volatile,那么我們在使用GPC1CON 這個寄存器的時候,?會直接從 CPU 的寄存器中取值。因為之前GPC1CON ?被訪問過,也就是之前就從內存中取出?GPC1CON 的值保存到某個寄存器中。之所以直接從寄存器中取值,而不去內存中取值,是因為編譯器優化代碼的結果(訪問 CPU寄存器比訪問 RAM 快的多)。用 volatile 關鍵字對?0xE0200080 ?進行強制轉換,使得每一次訪問?GPC1CON 時,執行部件都會從?0xE0200080 ?這個內存單元中取出值來賦值給?GPC1CON ?。
2、一個中斷服務子程序中會訪問到的非自動變量(Non-automatic variables)
由于訪問寄存器的速度要快過RAM,所以編譯器一般都會作減少存取外部RAM的優化,例如:
static int i=0; //i 為非自動變量
int main(void)
{
…
while (1){
if (i) dosomething();
}
}
/* Interrupt service routine. */
void ISR_2(void)
{
i=1;
}
程序的本意是希望 ISR_2 中斷產生時,在main函數中調用 dosomething 函數,但是,由于編譯器判斷在 main 函數里面沒有修改過 i,因此可能只執行一次對從i到某寄存器的讀操作,然后每次if判斷都只使用這個寄存器里面的“i副本”,導致 dosomething 永遠也不會被調用。如果將變量加上 volatile 修飾,則編譯器保證對此變量的讀寫操作都不會被優化(肯定執行)。此例中i也應該如此說明。
3、多線程應用中被幾個任務共享的變量
當兩個線程都要用到某一個變量且該變量的值會被改變時,應該用 volatile 聲明,該關鍵字的作用是防止優化編譯器把變量從內存裝入CPU寄存器中。如果變量被裝入寄存器,那么兩個線程有可能一個使用內存中的變量,一個使用寄存器中的變量,這會造成程序的錯誤執行。volatile的意思是讓編譯器每次操作該變量時一定要從內存中真正取出,而不是使用已經存在寄存器中的值,如下:
volatile ?BOOL ?bStop ?= ?FALSE; ?//bStop ?為共享全局變量
(1) 在一個線程中:
while( ?!bStop ?) ?{ ?… ?}
bStop ?= ?FALSE;
return;
(2) 在另外一個線程中,要終止上面的線程循環:
總結
以上是生活随笔為你收集整理的C语言之volatile用法(二十一),2021最新Android面试笔试题目分享的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: deepin终端编译c程序_Deepin
- 下一篇: 硅麦驱动开发及调试(pdm>>I2S>>