簡體   English   中英

嚴格的別名規則

[英]Strict aliasing rule

我正在閱讀有關 reinterpret_cast 的注釋及其別名規則( http://en.cppreference.com/w/cpp/language/reinterpret_cast )。

我寫了這段代碼:

struct A
{
  int t;
};

char *buf = new char[sizeof(A)];

A *ptr = reinterpret_cast<A*>(buf);
ptr->t = 1;

A *ptr2 = reinterpret_cast<A*>(buf);
cout << ptr2->t;

我認為這些規則在這里不適用:

  • T2 是對象的(可能是 cv 限定的)動態類型
  • T2 和 T1 都是(可能是多級的,可能在每一級都有 cv 限定)指向相同類型 T3 的指針(C++11 起)
  • T2 是聚合類型或聯合類型,它將上述類型之一保存為元素或非靜態成員(遞歸地包括子聚合的元素和所包含聯合的非靜態數據成員):這使得轉換是安全的從結構的第一個成員和聯合的元素到包含它的結構/聯合。
  • T2 是對象的動態類型的(可能是 cv 限定的)有符號或無符號變體
  • T2 是對象的動態類型的(可能是 cv 限定的)基類
  • T2 是字符或無符號字符

在我看來,這段代碼是不正確的。 我對嗎? 代碼正確與否?

另一方面,connect函數(man 2 connect)和struct sockaddr呢?

   int connect(int sockfd, const struct sockaddr *addr,
               socklen_t addrlen);

例如。 我們有 struct sockaddr_in 並且我們必須將它轉換為 struct sockaddr。 以上規則也不適用,所以這個演員不正確嗎?

是啊,這是無效的,但不是因為你轉換char*A* :那是因為你沒有獲得A* ,實際上指向一個A* ,正如你已經確定,沒有任何的類型鋸齒選項合身。

你需要這樣的東西:

#include <new>
#include <iostream>

struct A
{
  int t;
};

char *buf = new char[sizeof(A)];

A* ptr = new (buf) A;
ptr->t = 1;

// Also valid, because points to an actual constructed A!
A *ptr2 = reinterpret_cast<A*>(buf);
std::cout << ptr2->t;

現在類型別名根本不存在(盡管繼續閱讀,因為還有更多事情要做!)。

實際上,這還不夠。 我們還必須考慮對齊 雖然上面的代碼可能看起來有效,但為了完全安全,您需要將new放置到正確對齊的存儲區域中,而不僅僅是一個隨意的char塊。

標准庫(自 C++11 起)為我們提供了std::aligned_storage來做到這一點:

using Storage = std::aligned_storage<sizeof(A), alignof(A)>::type;
auto* buf = new Storage;

或者,如果您不需要動態分配它,只需:

Storage data;

然后,做你的新安置:

new (buf) A();
// or: new(&data) A();

並使用它:

auto ptr = reinterpret_cast<A*>(buf);
// or: auto ptr = reinterpret_cast<A*>(&data);

它看起來像這樣:

#include <iostream>
#include <new>
#include <type_traits>

struct A
{
  int t;
};

int main()
{
    using Storage = std::aligned_storage<sizeof(A), alignof(A)>::type;

    auto* buf = new Storage;
    A* ptr = new(buf) A();

    ptr->t = 1;

    // Also valid, because points to an actual constructed A!
    A* ptr2 = reinterpret_cast<A*>(buf);
    std::cout << ptr2->t;
}

現場演示

即便如此,由於 C++17 這有點復雜; 有關更多信息,請參閱相關的 cppreference 頁面並注意std::launder

當然,這整個事情看起來很人為,因為您只需要一個A ,因此不需要數組形式; 實際上,您首先只需創建一個沼澤標准A 但是,假設buf實際上實際上更大,並且您正在創建一個分配器或類似的東西,這是有道理的。

派生 C++ 規則的 C 別名規則包括一個腳注,指定規則的目的是說明事物何時可以別名。 標准的作者認為沒有必要禁止實現以不必要的限制方式在事物沒有別名的情況下應用規則,因為他們認為編譯器作者會尊重諺語“不要阻止程序員做什么需要完成”,標准的作者將其視為 C 精神的一部分。

需要使用聚合成員類型的左值來實際為聚合類型的值設置別名的情況很少見,因此標准不要求編譯器識別這種別名是完全合理的。 但是,在不涉及別名的情況下限制性地應用規則會導致類似的情況:

union foo {int x; float y;} foo;
int *p = &foo.x;
*p = 1;

甚至,就此而言,

union foo {int x; float y;} foo;
foo.x = 1;

調用 UB,因為賦值用於使用int訪問union foofloat的存儲值,這不是允許的類型之一。 任何質量然而,編譯器應該能夠認識到其上可見新鮮源於一個左值進行操作union foo是一個訪問union foo ,和一個訪問union foo允許影響的存儲值它的成員(如本例中的float成員)。

該標准的作者可能拒絕使腳注規范化,因為這樣做需要正式定義何時通過新派生的左值訪問是對父級的訪問,以及什么樣的訪問模式構成別名。 雖然大多數情況都非常明確,但也有一些極端情況,用於低級編程的實現可能比用於高端數字運算的實現更悲觀,並且標准的作者認為任何能夠計算弄清楚如何處理較難的情況應該能夠處理簡單的情況。

暫無
暫無

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

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