簡體   English   中英

將結構別名為其第一個成員是否是嚴格的別名違規?

[英]Is it a strict aliasing violation to alias a struct as its first member?

示例代碼:

struct S { int x; };

int func()
{
     S s{2};
     return (int &)s;    // Equivalent to *reinterpret_cast<int *>(&s)
}

我相信這是常見的並且被認為是可以接受的。 該標准確實保證結構中沒有初始填充。 然而,這種情況並未列在嚴格的別名規則 (C++17 [basic.lval]/11) 中:

如果程序嘗試通過以下類型之一以外的泛左值訪問對象的存儲值,則行為未定義:

  • (11.1) 對象的動態類型,
  • (11.2) 對象的動態類型的 cv 限定版本,
  • (11.3) 類似於(如 7.5 中定義的)對象的動態類型的類型,
  • (11.4) 與對象的動態類型對應的有符號或無符號類型的類型,
  • (11.5) 一種類型,它是與對象的動態類型的 cv 限定版本相對應的有符號或無符號類型,
  • (11.6) 在其元素或非靜態數據成員中包含上述類型之一的聚合或聯合類型(遞歸地包括子聚合或包含聯合的元素或非靜態數據成員),
  • (11.7) 是對象動態類型的(可能是 cv 限定的)基類類型的類型,
  • (11.8) char、unsigned char 或 std::byte 類型。

很明顯,對象s正在訪問其存儲的值。

要點中列出的類型是執行訪問的泛左值的類型,而不是正在訪問的對象的類型。 在這段代碼中,泛左值類型是int ,它不是聚合或聯合類型,排除了 11.6。

我的問題是:這段代碼是否正確,如果正確,在上述哪一個要點下是允許的?

演員的行為歸結為 [expr.static.cast]/13;

“指向cv1 void指針”類型的純右值可以轉換為“指向cv2 T指針”類型的純右值,其中T是對象類型,而cv2cv1具有相同的 cv 限定,或比cv1更大的 cv 限定。 如果原始指針值表示內存中一個字節的地址A並且A不滿足T的對齊要求,則結果指針值是未指定的。 否則,如果原始指針值指向對象a ,並且存在T類型的對象b (忽略 cv 限定)與a指針可互轉換,則結果是指向b的指針。 否則,指針值不會因轉換而改變。

指針互變的定義是:

兩個對象 a 和 b 是指針可互轉換的,如果:

  • 它們是同一個對象,或者
  • 一個是聯合對象,另一個是該對象的非靜態數據成員,或
  • 一個是標准布局類對象,另一個是該對象的第一個非靜態數據成員,或者,如果該對象沒有非靜態數據成員,則是該對象的第一個基類子對象,或
  • 存在一個對象 c 使得 a 和 c 是指針可相互轉換的,而 c 和 b 是指針可相互轉換的。

因此,在原始代碼中, ssx指針可相互轉換的,因此(int &)s實際上指定了sx

所以,在嚴格的別名規則中,其存儲值被訪問的對象是sx而不是s ,所以沒有問題,代碼是正確的。

我認為它在expr.reinterpret.cast#11

如果可以使用 reinterpret_cast 將“指向 T1 的指針”類型的表達式顯式轉換為“指向 T2 的指針”類型,則指定對象x T1 類型的泛左值表達式可以轉換為“對 T2 的引用”類型。 結果是*reinterpret_cast<T2 *>(p) ,其中p是指向“指向 T1 的指針”類型的x指針 沒有臨時創建,沒有復制,也沒有調用構造函數或轉換函數[1]

[1] 當結果引用與源泛左值相同的對象時,這有時被稱為類型雙關語

支持@MM關於指針不可恢復的回答:

來自cppreference

假設對齊要求得到滿足,一個reinterpret_cast改變有限的情況下處理指針相互轉換的對象的指針以外的價值:

struct S { int a; } s;


int* p = reinterpret_cast<int*>(&s); // value of p is "pointer to s.a" because s.a
                                     // and s are pointer-interconvertible
*p = 2; // s.a is also 2

相對

struct S { int a; };

S s{2};
int i = (int &)s;    // Equivalent to *reinterpret_cast<int *>(&s)
                     // i doesn't change S.a;

所引用的規則源自 C89 中的類似規則,除非在編寫 C89 時擴展“by”一詞的含義,或者識別出“未定義行為”的含義,否則該規則在書面上是荒謬的。 給定類似struct S {unsigned dat[10];}s; , 語句s.dat[1]++; 顯然會修改s的存儲值,但該表達式中唯一的struct S類型左值僅用於生成unsigned*類型的值。 用於修改任何對象的唯一左值是int類型。

在我看來,有兩種相關的方法可以解決這個問題:(1) 認識到標准的作者希望允許一種類型的左值明顯來自另一種類型的左值,但不想糾結於必須考慮哪些形式的可見推導的細節,特別是因為編譯器需要識別的情況范圍會根據它們執行的優化風格和使用它們的任務而有很大差異; (2) 認識到標准的作者沒有理由認為標准是否真的要求對特定結構進行有效處理是重要的,如果每個人都清楚有理由不這樣做的話。

我認為委員會成員之間沒有就編譯器是否給出以下內容達成共識:

struct foo {int ct; int *dat;} it;
void test(void)
{
  for (int i=0; i < it.ct; i++)
    it.dat[i] = 0;
}

應要求確保例如在it.ct = 1234; it.dat = &it.ct; it.ct = 1234; it.dat = &it.ct; , 調用test(); 會將it.ct並且沒有其他影響。 部分基本原理表明,至少一些委員會成員會期望如此,但省略任何允許使用成員類型的任意左值訪問結構類型的對象的規則表明情況並非如此。 C 標准從來沒有真正解決過這個問題,C++ 標准稍微清理了一些東西,但也沒有真正解決它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM