我們都知道這樣`volatile`是否允許與工會打字?
union U {float a; int b;};
U u;
std::memset(u, 0, sizeof u);
u.a = 1.0f;
std::cout << u.b;
這種類型的雙關在C未定義行爲++。
它是未定義的,因爲在u.a = 1.0f;
賦值之後.a
變爲活動字段並且.b
變爲非活動字段,並且它是未定義行爲以從非活動字段中讀取。我們都知道這一點。
現在,請考慮下面的代碼
union U {float a; int b;};
U u;
std::memset(u, 0, sizeof u);
u.a = 1.0f;
char *ptr = new char[std::max(sizeof (int),sizeof (float))];
std::memcpy(ptr, &u.a, sizeof (float));
std::memcpy(&u.b, ptr, sizeof (int));
std::cout << u.b;
而現在它成爲明確的,因爲這種類型的雙關語是允許的。 另外,如您所見,調用後內存保持不變,如u
。
現在我們來添加線索和
volatile
關鍵字。
union U {float a; int b;};
volatile U u;
std::memset(u, 0, sizeof u);
u.a = 1.0f;
std::thread th([&]
{
char *ptr = new char[sizeof u];
std::memcpy(ptr, &u.a, sizeof u);
std::memcpy(&u.b, ptr, sizeof u);
});
th.join();
std::cout << u.b;
邏輯保持不變,但我們只有第二個線程。由於volatile
關鍵字代碼保持良好定義。
在真正的代碼中,第二個線程可以通過任何糟糕的線程庫實現,編譯器可以不知道第二個線程。但由於volatile
關鍵字,它仍然是明確的。
但是如果沒有其他線程呢?
union U {float a; int b;};
volatile U u;
std::memset(u, 0, sizeof u);
u.a = 1.0f;
std::cout << u.b;
沒有其他線程。 但編譯器不知道沒有其他線程!
從編譯器的角度來看,沒有什麼改變!如果第三個例子是明確的,那麼最後一個例子也必須是明確的!
而且我們不需要第二個線程,因爲它不會改變u
內存。
如果使用
volatile
,則編譯器假定
u
可以在任何時候以靜默方式進行修改。在這種修改下,任何領域都可以變得活躍
因此,編譯器無法跟蹤易失性聯合的哪個字段處於活動狀態。 即使沒有真正修改該聯合,它也不能假定某個字段在分配給它(並且其他字段保持不活動狀態)後仍保持活動狀態。
所以,在最後兩個例子中編譯器會給我精確的位表示1.0f
轉換爲int
。
問題是: 我的推理是否正確?第三和第四個例子真的很好嗎?標準對此有何評論?
'volatile'與線程無關。請參閱[爲什麼存在volatile?](http://stackoverflow.com/questions/72552/why-does-volatile-exist?rq=1) –
首先,在第四個示例中,您讀取一個沒有同步的變量,因此編譯器可以假設沒有其他線程寫入它。 'volatile'只意味着對變量的每一個操作都是可觀察的副作用;這與多線程執行無關。所以這個例子肯定是錯誤的。第二:你爲什麼首先做這樣的事情? –
@BaummitAugen 1.我做這樣的事情,因爲它看起來像在C++中簡單類型雙擊的黑客攻擊。 2.你的意思是,當'compiler'看到'th.join()'時,它認爲voltaile變量有可能被修改了嗎?但是如果我使用任何非標準的線程庫,例如SDL線程呢?編譯器不知道'SDL_WaitThread()'加入了一個線程。 3.「volatile只意味着對變量的每一個操作都是可觀察的副作用」你能解釋一下嗎?我不明白你的意思。 – HolyBlackCat