当询问C中常见的未定义行为时,灵魂比我提到的严格别名规则更加开明.
他们在说什么?
来自http://en.cppreference.com/w/cpp/string/byte/memcpy:
如果对象不是TriviallyCopyable(例如标量,数组,C兼容结构),则行为未定义.
在我的工作中,我们使用std::memcpy了很长时间来按比例交换不是TriviallyCopyable的对象:
void swapMemory(Entity* ePtr1, Entity* ePtr2)
{
static const int size = sizeof(Entity);
char swapBuffer[size];
memcpy(swapBuffer, ePtr1, size);
memcpy(ePtr1, ePtr2, size);
memcpy(ePtr2, swapBuffer, size);
}
Run Code Online (Sandbox Code Playgroud)
从来没有任何问题.
我理解滥用std::memcpy非TriviallyCopyable对象并导致下游的未定义行为是微不足道的.但是,我的问题是:
std::memcpy当与非TriviallyCopyable对象一起使用时,为什么它本身的行为是未定义的?为什么标准认为有必要指定?
UPDATE
http://en.cppreference.com/w/cpp/string/byte/memcpy的内容已经过修改,以回应这篇文章和帖子的答案.目前的描述说:
如果对象不是TriviallyCopyable(例如标量,数组,C兼容结构),则行为是未定义的,除非程序不依赖于目标对象(不运行
memcpy)的析构函数的效果和生命周期目标对象(已结束,但未开始memcpy)由其他一些方法启动,例如placement-new.
PS
@Cubbi的评论:
@RSahu如果有东西保证UB下游,它会使整个程序不确定.但我同意在这种情况下似乎可以绕过UB并相应地修改cppreference.
应该使用哪个std::uninitialized_copy和什么时候有什么区别std::copy?
[class.dtor]/15读取,强调我的:
一旦为对象调用析构函数,该对象就不再存在 ; 如果为生命周期结束的对象调用析构函数,则行为未定义(3.8).
但是,据我所知,这是标准中对"现有"对象的唯一引用.这似乎与[basic.life]形成鲜明对比,后者更具体:
类型对象的生命周期在以下情况
T结束:
如果
T是具有非平凡析构函数(12.4)的类类型,则析构函数调用将启动,或者对象占用的存储器被重用或释放.
我们在这里有两个不同的措辞:"对象的生命周期结束"和"对象不再存在",前者只发生在一个非平凡的析构函数中,后者发生在任何析构函数中.差异的意义是什么?一个物体不再存在的含义是什么?
假设您有一个类型对象T和一个适当对齐的内存缓冲区alignas(T) unsigned char[sizeof(T)].如果您使用std::memcpy从类型对象复制T到unsigned char数组,是否考虑复制构造或复制分配?
如果一个类型可以轻易复制而不是标准布局,那么可以想象一个类如下:
struct Meow
{
int x;
protected: // different access-specifier means not standard-layout
int y;
};
Run Code Online (Sandbox Code Playgroud)
可以像这样实现,因为编译器不会被强制使用标准布局:
struct Meow_internal
{
private:
ptrdiff_t x_offset;
ptrdiff_t y_offset;
unsigned char buffer[sizeof(int) * 2 + ANY_CONSTANT];
};
Run Code Online (Sandbox Code Playgroud)
编译器可以存储x和y喵的缓冲器内的任何部分buffer,甚至可能在随机内的偏移buffer,只要它们被适当地对准和不重叠.的偏移x和y可即使编译愿与各施工随机变化.(如果编译器希望,x可以继续y使用,因为标准只要求相同访问说明符的成员按顺序排列,x并且y具有不同的访问说明符.)
这将符合可轻易复制的要求; a memcpy将复制隐藏的偏移字段,因此新副本将起作用.但有些事情是行不通的.例如,持有指向x跨越a 的指针memcpy会破坏:
Meow a;
a.x …Run Code Online (Sandbox Code Playgroud) 我正在讨论试图弄清楚C++中是否允许未对齐访问reinterpret_cast.我想不是,但我找不到确认或反驳的标准的正确部分.我一直在看C++ 11,但是如果它更清楚的话,我可以使用另一个版本.
C11中未定义未对齐的访问权限.C11标准的相关部分(第6.3.2.3段,第7段):
指向对象类型的指针可以转换为指向不同对象类型的指针.如果生成的指针未针对引用的类型正确对齐,则行为未定义.
由于未定义访问的行为未定义,因此某些编译器(至少是GCC)认为可以生成需要对齐数据的指令.大多数情况下,代码仍适用于未对齐的数据,因为现在大多数x86和ARM指令都使用未对齐的数据,但有些则没有.特别是,某些向量指令不会,这意味着随着编译器在生成优化指令方面变得更好,使用旧版本编译器的代码可能无法与较新版本一起使用.当然,某些体系结构(如MIPS)与未对齐数据的效果不同.
当然,C++ 11更复杂.§5.2.10,第7段说:
可以将对象指针显式转换为不同类型的对象指针.当prvalue
v类型的"指针T1"被转换为类型"指向cvT2",结果是static_cast<cv T2*>(static_cast<cv void*>(v))如果两个T1和T2是标准布局类型(3.9)和的对准要求T2并不比那些更严格的T1,或者如果任一类型是void.将"指向T1"的类型的prvalue转换为"指向"的类型T2(其中T1和T2是对象类型,并且对齐要求T2不比那些更严格T1)并返回其原始类型,产生原始指针值.未指定任何其他此类指针转换的结果.
请注意,最后一个单词是"未指定",而不是"未定义".§1.3.25将"未指明的行为"定义为:
行为,对于格式良好的程序构造和正确的数据,取决于实现
[ 注意:不需要实现来记录发生的行为.本国际标准通常描述了可能的行为范围.- 结束说明 ]
除非我遗漏了某些内容,否则标准实际上并没有描述这种情况下可能的行为范围,这似乎向我表明,一个非常合理的行为是为C实现的行为(至少由GCC实现):不支持他们.这意味着编译器可以自由地假设未发生未对齐访问并发出可能无法使用未对齐内存的指令,就像它对C一样.
然而,我与之讨论的人有不同的解释.他们引用第1.9段第5段:
执行格式良好的程序的一致实现应该产生与具有相同程序和相同输入的抽象机的相应实例的可能执行之一相同的可观察行为.但是,如果任何此类执行包含未定义的操作,则此国际标准不要求使用该输入执行该程序的实现(甚至不考虑第一个未定义操作之前的操作).
由于没有未定义的行为,他们认为C++编译器无权假设未发生未对齐访问.
那么,reinterpret_cast在C++中通过安全进行未对齐的访问吗?它在说明书(任何版本)中的位置如何?
编辑:通过"访问",我的意思是实际加载和存储.就像是
void unaligned_cp(void* a, void* b) {
*reinterpret_cast<volatile uint32_t*>(a) =
*reinterpret_cast<volatile uint32_t*>(b);
}
Run Code Online (Sandbox Code Playgroud)
如何分配内存实际上超出了我的范围(它适用于可以从任何地方调用数据的库),但是malloc …
我正在用C++为我的VM编写一个内存管理器.好吧,更确切地说,VM指令将被编译成带有嵌入式内存管理器的C++.我在处理C方面感觉更舒服,但现在我确实需要异常处理的原生支持,这几乎是我使用C++的唯一原因.
C和C++都有严格的别名规则,两个不兼容类型的对象不应重叠,在C中对于联合有一个小的例外.但是,以限定的存储器分配功能,如行为malloc,calloc,alloca等等,C标准具有以下段落.
6.5-6访问其存储值的对象的有效类型是对象的声明类型(如果有).已分配的对象没有声明的类型.如果通过具有非字符类型的左值的值将值存储到没有声明类型的对象中,则左值的类型将成为该访问的对象的有效类型以及不修改该值的后续访问的有效类型储值.如果使用
memcpy或将值复制到没有声明类型的对象中memmove,或者将其复制为字符类型数组,则该访问的修改对象的有效类型以及不修改该值的后续访问的有效类型是有效类型复制值的对象,如果有的话.对于没有声明类型的对象的所有其他访问,对象的有效类型只是用于访问的左值的类型.
这有效地使用原始分配的内存为任何类型在C中定义良好的行为.我试图在C++标准文档中找到类似的段落,但找不到一个.我认为C++在这方面有不同的方法.什么是C语言中"没有声明类型的已分配对象"的C++等价物,C++标准如何定义它?
请考虑以下示例代码:
class C
{
public:
int* x;
};
void f()
{
C* c = static_cast<C*>(malloc(sizeof(C)));
c->x = nullptr; // <-- here
}
Run Code Online (Sandbox Code Playgroud)
如果由于任何原因我不得不忍受未初始化的内存(当然,如果可能的话,我会打电话new C()),我仍然可以调用放置构造函数.但是,如果我省略这一点,如上所述,并手动初始化每个成员变量,是否会导致未定义的行为?即绕过构造函数本身未定义的行为,或者用类外的一些等效代码替换调用它是否合法?
(通过另一个完全不同的问题遇到这个问题;要求好奇......)
我有一个表示二进制消息的结构.我想编写一个函数来从缓冲区(无论是文件还是套接字,无关紧要)获取下一个这样的记录:
template <typename Record>
Record getNext();
Run Code Online (Sandbox Code Playgroud)
现在,我可以这样写:
template <typename Record>
Record getNext() {
Record r;
populateNext(reinterpret_cast<char*>(&r), // maybe ::read()
sizeof(r)); // or equivalent
return r;
}
Run Code Online (Sandbox Code Playgroud)
这很好,给了我RVO的好处.但是,它将调用默认构造函数Record,该构造函数可以由具有非trival默认构造函数的类型组成,这些构造函数可以工作,我希望避免这些 - 这些不一定是POD类型,但它们是标准布局.
有没有办法写getNext()这样我们避免任何构造函数(默认或复制/移动)Record?理想情况下,当用户调用时:
auto record = getNext<Record>();
Run Code Online (Sandbox Code Playgroud)
缓冲区直接读入内存record.这可能吗?