Bra*_*cer 7 c++ strict-aliasing memcpy language-lawyer c++14
二十多年前,我会(也没有)想到对POD结构执行二进制I / O:
struct S { std::uint32_t x; std::uint16_t y; };
S s;
read(fd, &s, sizeof(s)); // assume this succeeds and reads sizeof(s) bytes
std::cout << s.x + s.y;
Run Code Online (Sandbox Code Playgroud)
(我忽略了填充和字节顺序问题,因为它们不属于我要问的问题。)
“很明显”,我们可以读入s和编译器需要假设的内容s.x和s.y是别名通过read()。因此,s.x在read()不是undefined行为之后(因为s未初始化)。
同样的情况
S s = { 1, 2 };
read(fd, &s, sizeof(s)); // assume this succeeds and reads sizeof(s) bytes
std::cout << s.x + s.y;
Run Code Online (Sandbox Code Playgroud)
编译器无法假定s.x仍在。1之后read()。
快进到现代世界,我们实际上必须遵循别名规则并避免未定义的行为,依此类推,而我无法向自己证明这是允许的。
例如,在C ++ 14中,[basic.types]¶2表示:
对于任何普通可复制类型T的对象(基类子对象除外),无论该对象是否持有类型T的有效值,组成该对象的基础字节(1.7)都可以复制到char或无符号字符。
42如果将char或unsigned char数组的内容复制回该对象,则该对象随后应保留其原始值。
¶4说:
类型T的对象的对象表示形式是类型T的对象占用的N个无符号字符对象的序列,其中N等于sizeof(T)。
[basic.lval]¶10说:
如果程序尝试通过以下类型之一以外的glvalue来访问对象的存储值,则该行为未定义:54
...-
一个char或未签名的char类型。
54该列表的目的是指定对象可能会别名或可能不会别名的那些情况。
综上所述,我认为这是标准的说法,即“您可以形成任何普通复制(因而是POD)类型的unsigned charor或char指针,并读取或写入其字节”。实际上,在介绍给我们现代措辞的N2342中,简介表中说:
程序可以安全地应用编码优化,尤其是std :: memcpy。
及以后:
但是,该类中唯一的数据成员是char数组,因此程序员直观地期望该类是可存储的并且具有二进制I / O能力。
通过提出的解决方案,可以通过使默认构造函数变得微不足道(对于N2210,语法为endian()= default)将类制成POD,从而解决所有问题。
听起来真是N2342是想说“我们需要更新的措辞,使其所以你可以做I / O喜欢read()和write()这些类型”,它真的好像更新的措辞提出的标准。
另外,我经常听到对“ std::memcpy()空洞”或类似地方的引用,在这里您可以std::memcpy()用来基本上“允许混叠”。但是该标准似乎并未std::memcpy()明确指出(实际上,在一个脚注中提到了该标准std::memmove(),并称其为实现此方法的“示例”)。
另外,事实是I / O功能read()通常是POSIX中特定于OS的,因此在标准中未进行讨论。
因此,考虑到所有这些,我的问题是:
是什么实际上保证了我们可以对POD结构进行真实的I / O操作(如上所示)?
我们实际上是否需要std::memcpy()将内容放入unsigned char缓冲区或从缓冲区移出(肯定不是),还是可以直接读取POD类型?
OS I / O功能是否“承诺”它们“好像通过读取或写入unsigned char值”还是“好像按std::memcpy()”来操纵基础内存?
当我和原始I / O功能之间存在层(例如Asio)时,我应该担心什么?
严格的别名是关于通过指针/引用访问对象而不是对象的实际类型的对象。但是,严格别名规则允许通过指向byte数组的指针访问任何类型的任何对象。至少从C ++ 14开始,此规则就存在了。
现在,这没有多大意义,因为必须定义这样的访问的含义。为此(就编写而言),我们实际上只有两个规则:[basic.types] / 2和/ 3,它们涵盖了复制Trivially Copyable类型的字节。这个问题最终归结为:
您是否正在从文件中读取“组成[一个]对象的基础字节”?
如果您s实际上正在从的实时实例的字节中复制您要读取的数据S,那么您就可以100%罚款。从标准中可以明显看出,执行fwrite将给定的字节写入文件,然后执行fread从文件中读取那些字节。因此,如果将现有S实例的字节写入文件,然后将这些写入的字节读取到existing S,则相当于复制了这些字节。
遇到技术问题的地方就是开始理解解释的杂草。将标准解释为定义此类程序的行为是合理的,即使在同一程序的不同调用中进行写入和读取也是如此。
在以下两种情况之一中会引起关注:
1:当编写数据的程序实际上是与读取数据的程序不同的程序时。
2:当写数据的程序实际上没有写类型的对象S,而是写了恰好可以合法解释为的字节时S。
该标准不控制两个程序之间的互操作性。但是,C ++ 20确实提供了一种有效地说明“如果此内存中的字节包含的合法对象表示形式的工具T,那么我将返回该对象的外观的副本”。它叫std::bit_cast ; 你可以给它传递一个字节数组sizeof(T),它将返回一个字节的副本T。
如果您是骗子,就会得到不确定的行为。而且bit_cast甚至不进行编译,如果T是不平凡的可复制。
但是,S从技术上不是S完全而是完全可以直接从源中直接将字节复制到活动中S是另一回事。标准中没有措辞可以使该工作正常进行。
我们的朋友P0593提出了一种明确声明这种假设的机制,但是并没有完全融入C ++ 20。
| 归档时间: |
|
| 查看次数: |
225 次 |
| 最近记录: |