日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Volatile的陷阱

發布時間:2023/11/30 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Volatile的陷阱 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

最近寫的關于在嵌入式開發中常遇到的關于volatile關鍵字使用的短文,都是些通用的技術,貼上來share

?

對于volatile關鍵字,大部分的C語言教材都是一筆帶過,并沒有做太過深入的分析,所以這里簡單整理了一些關于volatile的使用注意事項。實際上從語法上來看volatileconst是一樣的,但是如果const用錯,幾乎不會有什么問題;而volatile用錯,后果可能很嚴重。所以在volatile的使用上,建議大家還是盡量求穩,少用一些沒有切實把握的技巧。

注意volatile修飾的是誰

首先來看下面兩個定義的區別:

uchar * volatile reg;

這行代碼里volatile修飾的是reg這個變量。所以這里實際上是定義了一個uchar類型的指針,并且這個指針變量本身是volatile 的。但是指針所指的內容并不是volatile的!在實際使用的時候,編譯器對代碼中指針變量reg本身的操作不會進行優化,但是對reg所指的內容 *reg卻會作為non-volatile內容處理,對*reg的操作還是會被優化。通常這種寫法一般用在對共享指針的聲明上,即這個指針變量有可能會被中斷等函數修改。將其定義為volatile以后,編譯器每次取指針變量的值的時候都會從內存中載入,這樣即使這個變量已經被別的程序修改了當前函數用的時候也能得到修改后的值(否則通常只在函數開始取一次放在寄存器里,以后就一直使用寄存器內的副本)。

volatile uchar *reg;

這行代碼里volatile修飾的是指針所指的內容。所以這里定義了一個uchar類型的指針,并且這個指針指向的是一個volatile的對象。但是指針變量本身并不是volatile的。如果對指針變量reg本身進行計算或者賦值等操作,是可能會被編譯器優化的。但是對reg所指向的內容 *reg的引用卻禁止編譯器優化。因為這個指針所指的是一個volatile的對象,所以編譯器必須保證對*reg的操作都不被優化。通常在驅動程序的開發中,對硬件寄存器指針的定義,都應該采用這種形式。

volatile uchar * volatile reg;

這樣定義出來的指針就本身是個volatile的變量,又指向了volatile的數據內容。

volatileconst的合用

從字面上看,volatileconst似乎是一個對象的兩個對立屬性,是互斥的。但是實際上,兩者是有可能一起修飾同一個對象的。看看下面這行聲明:

extern const volatile unsigned int rt_clock;

這是在RTOS系統內核中常見的一種聲明:rt_clock通常是指系統時鐘,它經常被時鐘中斷進行更新。所以它是volatile,易變的。因此在用的時候,要讓編譯器每次從內存里面取值。而rt_clock通常只有一個寫者(時鐘中斷),其他地方對其的使用通常都是只讀的。所以將其聲明為 const,表示這里不應該修改這個變量。所以volatileconst是兩個不矛盾的東西,并且一個對象同時具備這兩種屬性也是有實際意義的。

? 注意

在上面這個例子里面,要注意聲明和定義時對const的使用:

在需要讀寫rt_clock變量的中斷處理程序里面,應該如下定義(define)此變量:

volatile unsigned int rt_clock;

而在提供給外部用戶使用的頭文件里面,可以將此變量聲明(declare)為:

extern const volatile unsigned int rt_clock;

這樣是沒有問題的。但是切記一定不能反過來,即定義一個const的變量:

const unsigned int a;

但是卻聲明為非const變量:

extern unsigned int a;

這樣萬一在用戶函數里面對a進行了寫操作,結果是Undefined

再看另一個例子:

volatile struct devregs * const dvp = DEVADDR;

這里的volatileconst實際上是分別修飾了兩個不同的對象:volatile修飾的是指針dvp所指的類型為struct devregs的數據結構,這個結構對應者設備的硬件寄存器,所以是易變的,不能被優化的;而后面的const修飾的是指針變量dvp。因為硬件寄存器的地址是一個常量,所以將這個指針變量定義成const的,不能被修改。



危險的volatile用法

下面將列舉幾種對volatile的不當使用和可能導致的非預期的結果。

例:定義為volatile的結構體成員

考察下面對一個設備硬件寄存器結構類型的定義:

struct devregs{?

? ? unsigned short volatile csr;?

? ? unsigned short const volatile data;?

};

我們的原意是希望聲明一個設備的硬件寄存器組。其中有一個16bitCSR控制/狀態寄存器,這個寄存器可以由程序向設備寫入控制字,也可以由硬件設備設置反映其工作狀態。另外還有一個16bitDATA數據寄存器,這個寄存器只會由硬件來設置,由程序進行讀入。

看起來,這個結構的定義沒有什么問題,也相當符合實際情況。但是如果執行下面這樣的代碼時,會發生什么情況呢?

struct devregs * const dvp = DEVADDR;?

?

while ((dvp->csr & (READY | ERROR)) == 0)?

? ? ; /* NULL - wait till done */

通過一個non-volatile的結構體指針,去訪問被定義為volatile的結構體成員,編譯器將如何處理?答案是:UndefinedC99 標準沒有對編譯器在這種情況下的行為做規定。所以編譯器有可能正確地將dvp->csr作為volatile的變量來處理,使程序運行正常;也有可能就將dvp->csr作為普通的non-volatile變量來處理,在while當中優化為只有開始的時候取值一次,以后每次循環始終使用第一次取來的值而不再從硬件寄存器里讀取,這樣上面的代碼就有可能陷入死循環!!

如果你使用一個volatile的指針來指向一個非volatile的對象。比如將一個non-volatile的結構體地址賦給一個 volatile的指針,這樣對volatile指針所指結構體的使用都會被編譯器認為是volatile的,即使原本那個對象沒有被聲明為 volatile。然而反過來,如果將一個volatile對象的地址賦給一個non-volatile的普通指針,通過這個指針訪問volatile對象的結果是undefined,是危險的。

所以對于本例中的代碼,我們應該修改成這樣:

struct devregs {?

? ? unsigned short csr;?

? ? unsigned short data;?

};?

?

volatile struct devregs * const dvp = DEVADDR;

這樣我們才能保證通過dvp指針去訪問結構體成員的時候,都是作為volatile來處理的。




例:定義為volatile的結構體類型

考察如下代碼:

volatile struct devregs {?

? ? /* stuff */?

} dev1;?

......;?

struct devregs dev2;

作者的目的也許是希望定義一個volatile的結構體類型,然后順便定義一個這樣的volatile結構體變量dev1。后來又需要一個這種類型的變量,因此又定義了一個dev2。然而,第二次所定義的dev2變量實際上是non-volatile的!!因為實際上在定義結構體類型時的那個 volatile關鍵字,修飾的是dev1這個變量而不是struct devregs類型的結構體!!

所以這個代碼應該改寫成這樣:

typedef volatile struct devregs {?

? ? /* stuff */?

} devregs_t;?

?

devregs_t dev1;?

......;?

devregs_t dev2;

這樣我們才能得到兩個volatile的結構體變量。



例:多次的間接指針引用

考察如下代碼:

/* DMA buffer descriptor */?

struct bd {?

? ? unsigned int state;?

? ? unsigned char *data_buff;?

};?

?

struct devregs {?

? ? unsigned int csr;?

? ? struct bd *tx_bd;?

? ? struct bd *rx_bd;?

};?

?

volatile struct devregs * const dvp = DEVADDR;?

?

/* send buffer */?

dvp->tx_bd->state = READY;?

while ((dvp->tx_bd->state & (EMPTY | ERROR)) == 0)?

? ? ; /* NULL - wait till done */

這樣的代碼常用在對一些DMA設備的發送Buffer處理上。通常這些Buffer DescriptorBD)當中的狀態會由硬件進行設置以告訴軟件Buffer是否完成發送或接收。但是請注意,上面的代碼中對dvp->tx_bd->state的操作實際上是non-volatile的!這樣的操作有可能因為編譯器對其讀取的優化而導致后面陷入死循環。

因為雖然dvp已經被定義為volatile的指針了,但是也只有其指向的devregs結構才屬于volatile object的范圍。也就是說,將dvp聲明為指向volatile數據的指針可以保障其所指的volatile object之內的tx_bd這個結構體成員自身是volatile變量,但是并不能保障這個指針變量所指的數據也是volatile的(因為這個指針并沒有被聲明為指向volatile數據的指針)。

要讓上面的代碼正常工作,可以將數據結構的定義修改成這樣:

struct devregs {?

? ? unsigned int csr;?

? ? volatile struct bd *tx_bd;?

? ? volatile struct bd *rx_bd;?

};

這樣可以保證對state成員的處理也是volatile的。不過最為穩妥和清晰的辦法還是這樣:

volatile struct devregs * const dvp = DEVADDR;?

volatile struct bd *tx_bd = dvp->tx_bd;?

?

tx_bd->state = READY;?

while ((tx_bd->state & (EMPTY | ERROR)) == 0)?

? ? ; /* NULL - wait till done */

這樣在代碼里面能絕對保證數據結構的易變性,即使數據結構里面沒有定義好也不會有關系。而且對于日后的維護也有好處:因為這樣從代碼里一眼就能看出哪些數據結構的訪問是必須保證volatile的。

例:到底哪個volatile可能無效

就在你看過前面幾個例子,感覺自己可能已經都弄明白了的時候,請看最后這個例子:

struct hw_bd {?

? ? ......;?

? ? volatile unsigned char * volatile buffer;?

};?


struct hw_bd *bdp;?


......;?

bdp->buffer = ...; ?

bdp->buffer[i] = ...;

請問上面標記了①和②的兩行代碼,哪個是確實在訪問volatile對象,而哪個又是undefined的結果?

答案是:②是volatile的,①是undefined。來看本例的數據結構示意圖:

? ? ? ? (non-volatile)

?bdp -->+-------------+

? ? ? ? | ? ? ? ? ? ? |

? ? ? ? | ? ... ... ? |

? ? ? ? | ? ? ? ? ? ? |

? ? ? ? +-------------+? ? (volatile)? ?

? ? ? ? |? ? buffer ? |-->+------------+

? ? ? ? +-------------+ ? |? ? ? ? ? ? |

? ? ? ? ? ? ? ? ? ? ? ? ? |? ? ? ? ? ? |

? ? ? ? ? ? ? ? ? ? ? ? ? |? ? ? ? ? ? |

? ? ? ? ? ? ? ? ? ? ? ? ? +------------+

? ? ? ? ? ? ? ? ? ? ? ? ? |? buffer[i] |

? ? ? ? ? ? ? ? ? ? ? ? ? +------------+

? ? ? ? ? ? ? ? ? ? ? ? ? |? ? ? ? ? ? |

? ? ? ? ? ? ? ? ? ? ? ? ? |? ? ? ? ? ? |

? ? ? ? ? ? ? ? ? ? ? ? ? +------------+

buffer成員本身是通過一個non-volatile的指針bdp訪問的,按照C99標準的定義,這就屬于undefined的情況,因此對bdp->buffer的訪問編譯器不一定能保證是volatile的;

雖然buffer成員本身可能不是volatile的變量,但是buffer成員是一個指向volatile對象的指針。因此對buffer成員所指對象的訪問編譯器可以保證是volatile的,所以bdp->buffer[i]volatile的。

所以,看似簡單的volatile關鍵字,用起來還是有非常多的講究在里面的,大家一定要引起重視。

總結

以上是生活随笔為你收集整理的Volatile的陷阱的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 欧美一区2区三区4区公司 | 亚洲精品国产成人久久av盗摄 | 69精品无码成人久久久久久 | 色悠久久综合 | 6080一级片 | 青草草在线观看 | 人妻无码一区二区三区 | 黄色片亚洲 | 亚洲一区在线不卡 | 在线高清免费观看 | 五月婷婷,六月丁香 | 成人涩涩视频 | 99国产免费 | 国产一久久| 免费的黄色小视频 | 中文字幕乱码一区二区三区 | 夜夜草 | 无码人妻丰满熟妇区bbbbxxxx | 依人成人网 | 久艹av| 日韩精品在线视频观看 | 久久精品免费一区二区 | 在线观看中文字幕亚洲 | 朴麦妮原版视频高清资源 | 日本美女日批视频 | 国产人妖一区 | 恶虐女帝安卓汉化版最新版本 | 国产乱子伦精品 | 中文字幕网伦射乱中文 | 少妇性生活视频 | 国产精品精品久久久久久 | 国产高清在线免费观看 | 老汉色av | 性欧美精品中出 | 黄色片子视频 | 成人免费直播 | 国产九色在线播放九色 | 波多野结衣免费在线视频 | 精品福利影院 | 成人网免费| 欧美抠逼视频 | 99日韩 | 超碰成人在线观看 | 全球av在线 | 国内精品视频在线播放 | 日本sm调教—视频|vk | 国产夜色视频 | 日韩午夜av | 看免费黄色大片 | 97xxxx| 韩国主播青草55部完整 | 色呦呦在线观看视频 | 国产一区二区免费电影 | 538精品在线视频 | 一区二区三区精品国产 | 人妻精品久久久久中文字幕69 | 国产成人精品在线播放 | 色综合99| 久久99精品久久久久久水蜜桃 | 亚洲精品无码不卡在线播he | 亚洲天堂2016 | 久久色网 | 欧美bbbbb | 91大神久久| 午夜理伦三级做爰电影 | 四虎av网站| 久久露脸| 天天添天天射 | 欧美黄色性 | 国产精品日日摸夜夜爽 | 日韩无码精品一区二区三区 | 日韩精品一区二区三区视频 | 国产精品女主播 | 精品无码国产一区二区三区av | 国产人妻久久精品一区二区三区 | 久久久久99 | 男人晚上看的视频 | 欧美丰满一区二区免费视频 | 欧美天天性 | 老妇裸体性激交老太视频 | 日韩欧美aⅴ综合网站发布 国产成人一区二区三区小说 | 男人天堂1024 | 天堂va蜜桃一区二区三区 | 69综合| 亚洲一区在线观看视频 | 影音先锋波多野结衣 | 国产在线观看免费av | 精品久久精品久久 | 精品九九九九 | 国产男男gay体育生网站 | 亚洲理论片在线观看 | 欧美一级电影在线 | 免费成人高清 | 一本加勒比波多野结衣 | 爱爱的网站 | 伊人久久香 | 精品视频99 | 九九视频免费在线观看 | 国产色综合视频 |