为什么这种未定义的行为
Why is this undefined behaviour?
下面是示例代码:
X * makeX(int index) { return new X(index); }
struct Tmp {
mutable int count;
Tmp() : count(0) {}
const X ** getX() const {
static const X* x[] = { makeX(count++), makeX(count++) };
return x;
}
};
这将报告静态数组构造中 CLang build 500 上的未定义行为。为了简化这篇文章,计数不是静态的,但它不会改变任何东西。我收到的错误如下:
test.cpp:8:44: 警告:对"计数"进行了多次未排序的修改 [-Wunsequenced]
在 C++11 中,这很好;初始化器列表的每个子句都先于下一个子句排序,因此评估是明确定义的。
从历史上看,这些子句可能是未排序的,因此count
的两个未排序修改会产生未定义的行为。
(尽管,正如注释中所指出的,即使在那时,它也可能已经明确定义 - 您可能会将标准解释为暗示每个子句都是一个完整表达式,并且每个完整表达式的末尾都有一个顺序点。我将留给历史学家来辩论过时语言的细节。
更新 2
因此,经过一些研究,我意识到这实际上是很好的定义,尽管评估顺序没有指定。把这些碎片放在一起是一个非常有趣的,虽然有一个更普遍的问题涵盖了 C++11 案例,但没有一个涵盖 C++11 之前案例的一般问题,所以我最终创建了一个自我回答问题,初始化项中同一变量的多个突变是否列出了 C++11 之前的未定义行为,涵盖了所有细节。
基本上,看到makeX(count++), makeX(count++)
时的本能是将整个事物视为一个完整的表达式,但事实并非如此,因此每个初始者都有一个序列点。
更新
正如James指出的那样,它可能不是未定义的pre-C++11,这似乎依赖于将每个元素的初始化解释为一个完整的表达式,但不清楚你绝对可以提出这种说法。
源语言
在 C++11 之前,在一个序列点内多次修改变量是未定义的行为,我们可以看到,通过查看旧标准草案中的相关部分,将是第 5
节 表达式第 4 段说(强调我的):
[...]在上一个和下一个序列点之间,标量对象的存储值应通过表达式的计算最多修改一次。此外,访问先前的值只能用于确定要存储的值。对于完整表达式的子表达式的每个允许顺序,应满足本款的要求;否则,行为是未定义的。
在C++11标准草案中,这一变化以及第1.9
节计划执行第15段的以下措辞说(强调我的):
除非另有说明,否则对单个运算符的操作数和单个表达式的子表达式的计算是无序的。[...]如果标量对象的副作用相对于同一标量对象上的另一个副作用或使用同一标量对象的值进行的值计算是未排序的,则行为是未定义的。
我们可以看到,对于第 8.5.4
节的初始值设定项列表 列表初始化 第 4 段说:
在大括号初始化列表的初始值设定项列表中,初始值设定项子句(包括由包扩展 (14.5.3) 产生的任何子句)将按其出现的顺序进行评估。也就是说,与给定初始值设定项子句关联的每个值计算和副作用在初始值设定项列表的逗号分隔列表中与它后面的任何初始值设定项子句关联的每个值计算和副作用之前进行排序。
因为在这种情况下,,
不是序列点,而是更像数组元素初始化中的分隔符。
换句话说,您在没有序列点的语句中修改同一变量两次(在修改之间)。
编辑:感谢@MikeSeymour:这是C++03
以前的问题。似乎在C++11
中,评估的顺序是为这种情况定义的。
- 为什么我会收到警告,指出函数已使用但未定义,以及已定义但未使用?
- 为什么从 char 转换为 std::byte 可能是未定义的行为?
- 为什么我会收到链接器错误:未定义对 ..?
- 为什么 setjmp/longjmp 的这种用法是未定义的行为?
- 为什么我在 Windows API 中得到对 TextOut() 函数的未定义引用?
- 为什么我的C++代码无法编译,出现未定义的引用错误
- 为什么更改包含 psapi.h 的顺序会产生编译错误?(标识符 BOOL 未定义)
- 为什么当函数参数未定义为常量引用时存在无限递归?
- 为什么销毁被放置 new 覆盖的对象不是未定义的行为?
- 未定义的对象(〔basic.life〕/8):为什么允许引用重新绑定(和常量修改)
- 为什么这种类型的双关语不是未定义的行为?
- 使用Cygwin C++时出现未定义的引用错误,为什么我的文件没有链接?
- 为什么T是未定义的?我正在尝试实现一个用于双链表的节点类,它不喜欢我使用友元运算符后的T
- 为什么 consteval 函数允许未定义的行为?
- 为什么我总是收到错误:"未定义对'robots::robots()'的引用
- 为什么虚拟方法生成对_sbrk的未定义引用?
- 为什么 mfc 中静态文本工具中的变量未定义
- 为什么 std::memcpy(作为类型双关语的替代方案)不会导致未定义的行为?
- 为什么 i = ++i + 2 是未定义的行为?
- 为什么我的 2D 数组未定义?