2013-02-06 52 views
10

如果我有一個像這樣的對象:訪問「填充」字節是否是UB?

struct { 
    uint32_t n; 
    uint8_t c; 
} blob {}; 

然後將有3「填充」字節。

難道UB訪問的填充字節?例如:

uint8_t * data = reinterpret_cast<uint8_t*>(&blob); 
std::cout << data[4] << data[5] << data[6] << data[7]; 

我首先假定這很可能是UB,但如果這是真的,那麼一個的memcpy將UB還有:

memcpy(buf, &blob, sizeof(blob)); 

我的具體問題是:

  • 是它UB訪問填充字節?
  • 如果沒有,那麼是否意味着該值定義呢?
+1

由於這是一個非常規則性的問題,我不確定最終答案是什麼。我懷疑它會被定義(只要uint8_t *是unsigned char *),但初始值將保持未指定狀態,就像任何其他未初始化的成員變量。現在,如果你想要規範引用,我把它留給有更多時間的人來查看它:) –

+0

我假設'uint32_t'與'sizeof(uint32_t)'對齊。 – StackedCrooked

+3

@PeterG:標準要求sizeof(blob)至少爲5.既然'uint8_t'存在,那麼'CHAR_BIT == 8'因此'sizeof(uint32_t)== 4'。然後是「sizeof(blob)」是5還是8的一致性問題。允許任意奇怪的實現使其成爲6或7. –

回答

6

不,在整個對象被零初始化時(標準在§8.5/ 5中指出當對象零初始化時將填充初始化爲0位)時,訪問填充不是UB,初始化,它不是一個具有用戶定義的構造函數的類。

+0

因此,如果對象不是零初始化的,但是成員被分配了,那麼訪問填充的字節*將會是UB? (以及memcpy?) – StackedCrooked

+0

@StackedCrooked如果它沒有初始化,但成員分配給,那麼你可以複製成員,但不是整個對象。 –

+1

@StackedCrooked:如果它沒有被初始化,那麼對於任何其他未初始化的內存將發生同樣的事情:如果你的程序依賴於你有UB的未初始化內存的值,如果那個內存的值是不相關的你的程序中沒有UB(即只要你的程序不依賴於填充字段的值,'memcpy'就完美了)。未初始化並不意味着未定義的行爲,在int main(){int a;中沒有未定義的行爲。 int b = a;返回0; }' –

0

我認爲,在正常情況下,你可以用UB結束了這一點。我在想的是你有ECC或Parity校驗的內存,ecc /奇偶校驗位是通過寫入內存來設置的。如果在[從未寫入AT ALL]之前沒有使用過一塊內存,並且您在填充字段中讀取了未初始化的字節,那麼當未寫入內存時可能會導致ecc/parity錯誤正在閱讀。

當然,在這樣的系統中,你會避免痛苦的整個堆通過簡單地做一個「填補所有的記憶」在引導過程中在某些時候,因爲它是illagel做:

struct Blob 
{ 
    uint32_t n; 
    uint8_t c; 
}; 

Blob *b = malloc(sizeof(Blob)*10); 

for(int i = 0; i < 10; i++) 
{ 
    b[i].n = i; 
    b[i].c = i; 
} 


... 

Blob a[3]; 

memcpy(a, &b[1], sizeof(a)); // Copies 3 * Blob objects, including padding. 

現在,由於並非b [x]的所有位都已設置,因此可能無法複製memcpy中的數據,原因是奇偶校驗/ ecc錯誤。這將是非常糟糕的。但是與此同時,編譯器不能強制設置所有的填充區域。

我的結論是,這是UB,但它不太可能,除非有特殊情況造成的問題。當然,您會在很多代碼中看到上述類型的memcpy代碼。

+0

讀取任何未初始化的內存都是UB,而不僅僅是在奇怪的系統上。 –

+0

是的,如果你使用'malloc'來填充分配的內存,那麼使用'memcpy'來複制你不能寫入每一個字節的結構是不好的。但是,我們經常看到這種代碼,你不同意嗎?它可以工作,因爲幾乎總是在操作系統中填充內存,無論是在引導過程中,還是在稍後的過程中,例如嚮應用程序提供內存的操作系統階段。但我相信這是UB來覆蓋內存未初始化的情況。 –

0

在C它不是不確定的行爲。唯一的一次你訪問未初始化的東西(如對象填充)未定義的行爲,是當對象具有自動存儲時間,從來沒有考慮它的地址:

6.3.2.1.2: 如果 左值指定自動存儲持續時間,可能已與寄存器存儲類聲明 的對象(從未有過採取它的地址),和該對象 是未初始化的(未用的初始化聲明並沒有分配給它一直 之前執行使用),行爲是未定義的。

但在這種情況下,你正在服用的地址(與&),這樣的行爲是明確界定(不會發生錯誤),但你可能會得到任何隨機值。

在C++中,所有投注都是關閉的,通常情況下是這樣。

+0

正如Steve Jessop所述,4.1/1說如果一個程序需要一個未初始化對象的左值到右值轉換,那麼它就是UB。 –

+0

啊,錯過了C++標籤 - 在C語言中已經很好定義了。 C++規範在這裏被打破。 –

0

如果它不是未定義的行爲,它肯定是實現定義的。雖然C++標準並不能保證你的程序做什麼,但是你的系統的ABI規範 - 如果你使用的是Linux的話 - SysV。我懷疑,如果你在填充位置上亂放,你可能更感興趣的是你的程序如何在你的系統上運行,而不是它在任何任意C++兼容系統上的表現。

0

POD結構將存在於至少sizeof(結構)字節(其中包括任何填充字節)的連續內存塊中。訪問填充字節(如果存在的話)只有在未初始化時纔會是UB。

memset(&s, 0, sizeof(s)); 

這將初始化所有字節,包括填充。之後從填充讀取不會是UB。

當然,memset()是一個C-ISM,我們絕不會在C++中這樣做,對吧?