3 回答

TA貢獻1824條經驗 獲得超5個贊
n3751支持以下未指定的操作:對象生存期,低級編程和memcpy,其中包括:
C ++標準目前對使用memcpy復制對象表示字節在概念上是賦值還是對象構造都保持沉默。對于基于語義的程序分析和轉換工具以及跟蹤對象生存期的優化程序,差異確實很重要。本文建議
允許使用memcpy復制兩個不同的平凡可復制表(但是大小相同)的兩個不同對象的字節
這樣的使用被識別為初始化,或更普遍地被識別為(概念上)對象構造。
識別為對象構造將支持二進制IO,同時仍允許基于生命周期的分析和優化器。
我找不到本文討論過的任何會議紀要,因此似乎仍然是一個未解決的問題。
C ++ 14草案標準目前在1.8 [intro.object]中說:
[...]在需要時,通過定義(3.1),新表達式(5.3.4)或實現(12.2)創建對象。[...]
這是我們所沒有的malloc,而復制瑣碎可復制類型的標準中所涉及的情況似乎僅引用3.9 [basic.types]部分中已存在的對象:
對于任何普通可復制類型T的對象(基類子對象除外),無論該對象是否持有類型T的有效值,組成該對象的基礎字節(1.7)都可以復制到char或unsigned char.42如果將char或unsigned char數組的內容復制回該對象,則該對象隨后應保留其原始值[...]
和:
對于任何普通可復制類型T,如果兩個指向T的指針指向不同的T對象obj1和obj2,則將組成obj1的基礎字節(1.7)復制到obj2,43 obj2,其中obj1和obj2都不是基類子對象。隨后應保持與obj1相同的值。[...]
提案基本上是這樣說的,因此這不足為奇。
dyp從ub郵件列表中指出了關于該主題的有趣討論:[ub]鍵入punning以避免復制。
投標p0593:隱式創建對象以進行低級對象操作
提案p0593試圖解決此問題,但AFAIK尚未得到審查。
本文建議在新分配的存儲中根據需要按需創建足夠瑣碎類型的對象,以賦予程序定義的行為。
它有一些激勵性的示例,它們在本質上相似,包括當前的std :: vector實現,該實現當前具有未定義的行為。
它提出了以下幾種隱式創建對象的方法:
我們建議至少將以下操作指定為隱式創建對象:
char,unsigned char或std :: byte數組的創建在該數組中隱式創建對象。
對malloc,calloc,realloc或任何名為operator new或operator new []的函數的調用會在其返回的存儲中隱式創建對象。
std :: allocator ::: allocate同樣在返回的存儲中隱式創建對象;分配器要求應該要求其他分配器實現也要這樣做。
調用memmove的行為就像
將源存儲復制到臨時區域
在目標存儲中隱式創建對象,然后
將臨時存儲復制到目標存儲。
這允許記憶保留普通復制對象的類型,或用于將一個對象的字節表示重新解釋為另一對象的字節表示。
調用memcpy的行為與調用memmove的行為相同,不同之處在于它在源和目標之間引入了重疊限制。
提名聯合成員的類成員訪問將觸發由聯合成員占用的存儲區中的隱式對象創建。請注意,這不是一個全新的規則:[P0137R1]中已經存在此權限,其中成員訪問位于分配的左側,但現在已被概括為該新框架的一部分。如下所述,這不允許通過聯合進行類型修剪。相反,它僅允許通過類成員訪問表達式來更改活動的聯合成員。
應將新的屏障操作(不同于不會創建對象的std :: launder)引入標準庫,其語義等效于具有相同源存儲和目標存儲的內存。作為一名稻草人,我們建議:
// Requires: [start, (char*)start + length) denotes a region of allocated
// storage that is a subset of the region of storage reachable through start.
// Effects: implicitly creates objects within the denoted region.
void std::bless(void *start, size_t length);
除上述內容外,還應將實現標準定義的非標準內存分配和映射功能集(例如POSIX系統上的mmap和Windows系統上的VirtualAlloc)指定為隱式創建對象。
注意,指針reinterpret_cast不足以觸發隱式對象創建。

TA貢獻1884條經驗 獲得超4個贊
此代碼正確嗎?
好吧,它通常會“起作用”,但僅適用于瑣碎的類型。
我知道您沒有要求它,但是讓我們使用一個非平凡類型的示例:
#include <cstdlib>
#include <cstring>
#include <string>
struct T // trivially copyable type
{
std::string x, y;
};
int main()
{
void *buf = std::malloc( sizeof(T) );
if ( !buf ) return 0;
T a{};
a.x = "test";
std::memcpy(buf, &a, sizeof a);
T *b = static_cast<T *>(buf);
b->x = b->y;
free(buf);
}
構造后a,a.x將分配一個值。假設std::string未針對使用較小字符串值的本地緩沖區進行優化,而只是將數據指針指向外部存儲塊。將原樣memcpy()的內部數據復制a到中buf?,F在a.x,b->x為string數據引用相同的內存地址。當b->x分配一個新值時,該存儲塊將被釋放,但a.x仍會引用它。當a隨后在年底進入的范圍了main(),它會嘗試再次釋放相同的內存塊。發生未定義的行為。
如果要“正確”,將對象構造到現有內存塊中的正確方法是改用placement-new運算符,例如:
#include <cstdlib>
#include <cstring>
struct T // does not have to be trivially copyable
{
// any members
};
int main()
{
void *buf = std::malloc( sizeof(T) );
if ( !buf ) return 0;
T *b = new(buf) T; // <- placement-new
// calls the T() constructor, which in turn calls
// all member constructors...
// b is a valid self-contained object,
// use as needed...
b->~T(); // <-- no placement-delete, must call the destructor explicitly
free(buf);
}
- 3 回答
- 0 關注
- 676 瀏覽
添加回答
舉報