M.M*_*M.M 11 c++ lifetime language-lawyer
在C++中,这段代码是否正确?
#include <cstdlib>
#include <cstring>
struct T // trivially copyable type
{
int x, y;
};
int main()
{
void *buf = std::malloc( sizeof(T) );
if ( !buf ) return 0;
T a{};
std::memcpy(buf, &a, sizeof a);
T *b = static_cast<T *>(buf);
b->x = b->y;
free(buf);
}
Run Code Online (Sandbox Code Playgroud)
换句话说,是*b一个生命已经开始的对象?(如果是的话,它什么时候开始呢?)
Sha*_*our 13
这是未指定的,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和obj2都不是基类子对象,如果构成obj1的基础字节(1.7)被复制到obj2,43 obj2中随后应与obj1保持相同的价值.[...]
这基本上就是提案所说的,所以这不应该是令人惊讶的.
dyp从ub邮件列表中指出了关于这个主题的一个引人入胜的讨论:[ub]键入punning以避免复制.
提案p0593试图解决这个问题,但AFAIK尚未得到审查.
本文提出,在新分配的存储中,根据需要按需创建足够琐碎类型的对象,以便为程序定义行为.
它有一些激励性的例子,它们本质上是相似的,包括当前具有未定义行为的当前std :: vector实现.
它提出了隐式创建对象的以下方法:
我们建议至少将以下操作指定为隐式创建对象:
创建char,unsigned char或std :: byte数组会隐式创建该数组中的对象.
对malloc,calloc,realloc或任何名为operator new或operator new []的函数的调用会在其返回的存储中隐式创建对象.
std :: allocator :: allocate同样隐式地在其返回的存储中创建对象; 分配器要求应该要求其他分配器实现也这样做.
对memmove的调用就好像它一样
将源存储复制到临时区域
隐式地在目标存储中创建对象,然后
将临时存储复制到目标存储.
这允许memmove保留简单可复制对象的类型,或者用于将一个对象的字节表示重新解释为另一个对象的字节表示.
对memcpy的调用与调用memmove的行为相同,只是它在源和目标之间引入了重叠限制.
提名联盟成员的类成员访问会触发由union成员占用的存储中的隐式对象创建.请注意,这不是一个全新的规则:对于成员访问位于赋值左侧的情况,此权限已存在于[P0137R1]中,但现在已作为此新框架的一部分进行推广.如下所述,这不允许通过工会进行打字; 相反,它只允许通过类成员访问表达式更改活动联合成员.
应该将新的屏障操作(不同于std :: launder,不创建对象)引入标准库,其语义等同于具有相同源和目标存储的memmove.作为一名稻草人,我们建议:
Run Code Online (Sandbox Code Playgroud)// 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);除上述内容外,还应将一组实现定义的非stasndard内存分配和映射函数(如POSIX系统上的mmap和Windows系统上的VirtualAlloc)指定为隐式创建对象.
请注意,指针reinterpret_cast不足以触发隐式对象创建.
@Shafik Yaghmour 的回答是彻底的,并且与作为一个开放问题的代码有效性相关 - 回答时就是这种情况。Shafik 的回答正确地参考了 p0593,在回答时它是一个提案。但从那以后,该提案被接受了,事情也得到了明确。
malloc在 C++20 之前的 C++ 规范中没有提到使用创建对象的可能性,例如参见 C++17 规范[intro.object]:
C++ 程序中的构造创建、销毁、引用、访问和操作对象。对象由定义 (6.1)、new 表达式 (8.5.2.4)、隐式更改联合的活动成员 (12.3) 或创建临时对象 (7.4, 15.2) 创建。
以上措辞并不是malloc创建对象的选项,因此使其成为事实上的未定义行为。
然后它被视为一个问题,这个问题后来由https://wg21.link/P0593R6解决,并被接受为自 C++98 以来所有 C++ 版本的 DR,然后添加到 C++20 规范中,新的措辞:
- C++ 程序中的构造创建、销毁、引用、访问和操作对象。一个对象是由一个定义、一个新表达式、一个隐式创建对象的操作创建的(见下文) ......
...
- 此外,在指定的存储区域内隐式创建对象之后,一些操作被描述为生成指向合适的创建对象的指针。这些操作选择一个隐式创建的对象,其地址是存储区域的起始地址,并产生一个指向该对象的指针值,如果该值将导致程序具有定义的行为。如果没有这样的指针值会给程序定义的行为,则程序的行为是未定义的。如果多个这样的指针值会给程序定义的行为,则未指定产生哪个这样的指针值。
C++20 规范中给出的示例是:
#include <cstdlib>
struct X { int a, b; };
X *make_x() {
// The call to std?::?malloc implicitly creates an object of type X
// and its subobjects a and b, and returns a pointer to that X object
// (or an object that is pointer-interconvertible ([basic.compound]) with it),
// in order to give the subsequent class member access operations
// defined behavior.
X *p = (X*)std::malloc(sizeof(struct X));
p->a = 1;
p->b = 2;
return p;
}
Run Code Online (Sandbox Code Playgroud)
至于使用memcpy- @Shafik Yaghmour 已经解决了这一点,这部分对于可简单复制的类型有效(措辞从C++98 和 C++03 中的POD更改为C++11及之后的普通可复制类型 )。
底线:代码有效。
至于生命周期的问题,让我们深入研究一下有问题的代码:
struct T // trivially copyable type
{
int x, y;
};
int main()
{
void *buf = std::malloc( sizeof(T) ); // <= just an allocation
if ( !buf ) return 0;
T a{}; // <= here an object is born of course
std::memcpy(buf, &a, sizeof a); // <= just a copy of bytes
T *b = static_cast<T *>(buf); // <= here an object is "born"
// without constructor
b->x = b->y;
free(buf);
}
Run Code Online (Sandbox Code Playgroud)
请注意,*b为了完整起见,可以在释放 之前添加对 的析构函数的调用buf:
b->~T();
free(buf);
Run Code Online (Sandbox Code Playgroud)
虽然这不是规范所要求的。
或者,删除 b也是一种选择:
delete b;
// instead of:
// free(buf);
Run Code Online (Sandbox Code Playgroud)
但如前所述,代码按原样有效。
| 归档时间: |
|
| 查看次数: |
1377 次 |
| 最近记录: |