【问题标题】:Is it safe to memset bool to 0?将 memset bool 设置为 0 是否安全?
【发布时间】:2016-01-27 14:49:52
【问题描述】:

假设我有一些 legacy 代码,除非发现 bug,否则无法更改,并且它包含以下代码:

bool data[32];
memset(data, 0, sizeof(data));

这是将数组中的所有bool 设置为false 值的安全方法吗?

更一般地说,将memsetbool 转换为0 以使其价值false 是安全的吗?

是否保证适用于所有编译器?还是我要求修复?

【问题讨论】:

  • @user657267 我将“安全”定义为“这样做会导致false
  • 不能保证在所有编译器上都可以工作,但可以。依赖它的遗留代码太多,任何人都不敢破坏它。
  • @LightnessRacesinOrbit 确实,这是有原因的。另一个原因是人们修理没有损坏的东西。我曾经看到一个主要产品发布灾难性地失败,因为有人从错误消息中删除了错误的标点符号。 (完全无害,对吧?是的,但它移动了一个字节的内存,足以暴露以前无害的数据溢出。)如果你能找到避免这两个问题的方法,请告诉我,我们会写一本书并致富.
  • @CareyGregory:但它坏了。忽略纯粹偶然起作用的代码并不是“将工作代码留在原处”。它是“将损坏的代码留在原处”。我同意由于实际情况而保留此代码是可以接受的,但如果将其部署在昂贵的宇宙飞船上,即使是代码审查的第一阶段,我也能接受的最低将是编译时断言。
  • @LightnessRacesinOrbit 如果这是新的(ish)代码,我会同意你的看法。但事实并非如此。它是遗留代码,只能通过“错误”的正当理由以及可能需要的所有开销来修复。即使只是一个编译时断言,也需要新的构建、新的打包和部署,以及这些步骤所带来的所有破坏机会。

标签: c++ language-lawyer memset


【解决方案1】:

法律有保障吗?没有。

C++ 对bool 值的表示只字未提。

现实有保障吗?是的。

我的意思是,如果您希望找到一个不将布尔值 false 表示为零序列的 C++ 实现,我祝您好运。鉴于false必须隐式转换为0true必须隐式转换为10必须隐式转换为false,非0必须隐式转换为true ……好吧,如果以其他方式实现它,那你就太傻了。

这是否意味着它“安全”由您决定。

我通常不会这么说,但如果我遇到你这种情况,我会很乐意让这件事发生。如果您真的很担心,可以在您的可分发包中添加一个测试可执行文件,以在安装实际项目之前验证每个目标平台上的先决条件。

【讨论】:

  • 嗯……你是怎么做到的?上次我试图发布一个简短的答案时它不会让我这样做。 (而且我敢肯定,简短解释了反对票。你真的可以解释一下,嗯?)
  • @CareyGregory:162 个字符还不够吗?我猜你一定不喜欢 Twitter。
  • 赞成,虽然我希望有一些对 C++ 规范的参考。
  • 不,我不喜欢推特,但我评论时正好是 4 个字符。
  • @MatthieuM.:在某些平台上,测试值的特定位是否已设置比测试值是否非零更快。例如,许多嵌入式控制器都有“如果内存位被设置则跳转”指令,但没有“如果内存为非零则跳转”指令。
【解决方案2】:

不。它不安全(或者更具体地说,便携)。但是,它可能工作,因为您的典型实现将:

  1. 使用 0 表示布尔值(实际上,C++ 规范需要它)
  2. 生成memset() 可以处理的元素数组。

但是,最佳实践将规定使用 bool data[32] = {false} - 此外,这可能会释放编译器以在内部以不同方式表示结构 - 因为使用 memset() 可能会导致它生成一个 32 字节的值数组,而不是说, 一个 4 字节,非常适合您的平均 CPU 寄存器。

【讨论】:

  • 小心; bool data[32] = {false} 可能会起作用(阅读:它会;总是)但它也有点误导。它不等同于bool data[32] = {false, false, false, ...},而是等同于bool data[32] = {false, 0, 0, 0, 0}。这里真正的可取之处在于0 肯定会隐式转换为false,但这确实意味着命名false 有点牵强,which may give someone a big surprise one day。因此,bool data[32] = {} 将是我的首选。
  • 在进一步阅读后,我已经证实 C++ 规范要求 false 评估为零 - 因此我的答案的相关部分是内部表示可以更有效的事实。因此,虽然您是正确的,但仍然保证规范将所有元素初始化为 false。
  • 好的代码很大程度上是主观的。我认为{false} 初始化 bool 数组比 for(;;) 无限循环有更多问题 - 它完全符合标准。
  • 我非常清楚地解释了问题所在,并提供了指向更多信息的链接。如果你在整个职业生涯中编写不清楚的代码只是因为它“可验证地符合标准”,我希望我不必维护它!
  • {false} 完全清楚,只要您知道 C++ 将语句扩展为什么 - 如果您假设您可以将其更改为 {true} 以切换到将所有内容初始化为该值,那就是您的问题.事实上,也许更重要的是,在这种情况下,实际 解决方案提供了一个很好的//comment,如果您担心经验不足的开发人员会来,说明您正在做什么穿过它。
【解决方案3】:

更新

P1236R1: Alternative Wording for P0907R4 Signed Integers are Two's Complement 说如下:

根据圣地亚哥的 EWG 决定,与 P0907R3 不同,bool 被指定为具有某种整数类型作为其基础类型,但“bool”的填充位的存在将保持未指定,true 和 false 的映射也是如此到底层类型的值。

原答案

我相信这是未指定的,尽管false 的底层表示似乎全为零。 Boost.Container relies on this as well强调我的):

Boost.Container 使用带有零值的 std::memset 来初始化一些 与大多数平台一样,这种初始化会产生所需的类型 具有改进性能的值初始化。

按照 C11 标准,Boost.Container 假定对于任何 整数类型,所有位都为零的对象表示 应是该类型中值零的表示。 自从 _Bool/wchar_t/char16_t/char32_t 在 C 中也是整数类型,它认为所有 C++ 整数类型都可以通过 std::memset 初始化。

他们指出的这个 C11 引用实际上来自 C99 缺陷:defect 263: all-zero bits representations 添加了以下内容:

对于任何整数类型,所有位所在的对象表示 zero 应该是该类型中值零的表示。

那么这里的问题是假设正确,C 和 C++ 之间整数的底层对象表示是否兼容? 提案Resolving the difference between C and C++ with regards to object representation of integers 试图在某种程度上回答这个问题,据我所知没有解决。我在标准草案中找不到这方面的确凿证据。我们有几个案例,它在类型方面明确链接到 C 标准。 3.9.1[basic.fundamental] 部分说:

[...] 有符号和无符号整数类型应满足 C 标准第 5.2.4.2.1 节中给出的约束。

3.9 [basic.types] 说:

类型 T 的对象的对象表示是 N 的序列 类型 T 的对象占用的 unsigned char 对象,其中 N 等于 大小(T)。对象的值表示是一组位 保存类型 T 的值。对于普通可复制类型,值 表示是对象表示中的一组位 确定一个值,它是一个离散元素 实现定义的一组值。44

脚注 44(不规范)说:

意图是 C++ 的内存模型与 C++ 的内存模型兼容 ISO/IEC 9899 编程语言 C。

标准草案在指定 bool 的基础表示方面最远的是在3.9.1 部分:

类型 bool、char、char16_t、char32_t、wchar_t 以及带符号和 无符号整数类型统称为整数类型。 50 A 整数类型的同义词是整数类型。的表示 整数类型应使用纯二进制计数来定义值 system.51 [ 示例:本国际标准允许 2 的 补码、1 的补码和有符号幅度表示 整数类型。 ——结束示例]

该部分还说:

bool 类型的值为真或假。

但我们所知道的truefalse 是:

布尔文字是关键字 false 和 true。这样的文字 是纯右值,类型为 bool。

我们知道它们可以转换为 01

bool 类型的纯右值可以转换为 int 类型的纯右值,用 假变零,真变一。

但这并没有让我们更接近底层表示。

据我所知,除了填充位之外,标准引用实际底层位值的唯一地方是通过defect report 1796: Is all-bits-zero for null characters a meaningful requirement? 删除的:

尚不清楚可移植程序是否可以检查表示的位;相反,它似乎仅限于检查与值表示相对应的数字位(3.9.1 [basic.fundamental] 第 1 段)。要求空字符值比较等于 0 或 '\0' 而不是指定表示的位模式可能更合适。

还有更多defect reports 处理标准中关于什么是位以及值和对象表示之间的差异的差距。

实际上,我希望这会起作用,但我认为它不安全,因为我们无法在标准中明确这一点。你是否需要改变它,不清楚,你显然有一个不平凡的权衡。所以假设它现在可以工作,问题是我们是否认为它可能会与各种编译器的未来版本中断,这是未知的。

【讨论】:

  • 标准是否说POD数据可以::memset()为0?
  • @Slava well this 说我们可以 memset 一个 POD,但细节并没有真正说明,标准中似乎也没有说明。据我所知,这在 [basic.types]. 中有介绍
  • 那么 bool 可以成为 POD 的一部分,隐含地要求它的二进制表示恕我直言
  • @Slava 问题是我们有Values of type bool are either true or false. 所以如果我们假设,在底层表示中0 是假的,1 是真的(我们无法证明) 其他值呢?它们是真的还是假的?我们不能说,听起来未定义,所以它看起来像是一个缺陷或只是未指定。
  • ::memset( &intvar, 0, sizeof( int ) ) 是否保证结果与intvar = int{}; 相同? boolvar = bool{}; 应该是一样的吗?
【解决方案4】:

从 3.9.1/7 开始:

类型 bool 、 char 、 char16_t 、 char32_t 、 wchar_t 以及带符号和 无符号整数类型统称为整数类型。一种 整数类型的同义词是整数类型。的表示 整数类型应使用纯二进制计数来定义值 系统。

鉴于此,我看不到任何可能的 bool 实现不会将 false 表示为全 0 位。

【讨论】:

  • 你可以在 memory 中实现 false 为 1 和 true 为 0。只要编译器巧妙地管理代码中所有必需的转换。类似于空指针在内存中不一定为0。
  • 不,标准无意像那样限制bool 的表示。 bool 值保证转换为 01,但不保证与 01 有任何关联。
  • @M.M: 嗯,你删掉的文字根本没有说false 必须表示为整数0。基本上,您必须为您的索赔提供支持文本。您的文本允许 false 在内部表示为 66true 在内部表示为 42,只要这两种表示形式都遵循强制性的“纯二进制计数系统”。
  • @AnT 对于其他整数类型,“纯二进制记数系统”意味着 int 1 必须表示为 000...00142 表示为 000...01010010 等等。正如您所建议的那样,它不仅仅意味着“任何系列的位”;脚注详细说明。
  • @M.M:我无法回应“尽管有你的反驳,我仍然是对的”。
猜你喜欢
  • 1970-01-01
  • 2014-10-19
  • 1970-01-01
  • 2019-12-06
  • 1970-01-01
  • 2019-09-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多