在 for 循环中重新排序测试条件:编译器错误?

Reordering test condition in for-loop: compiler bug?

本文关键字:条件 测试 编译器 错误 排序 循环 for 新排序      更新时间:2023-10-16

我有一个存储在数组中的树,我正在尝试找到一个特定的节点:

std::vector<Node> nodes = ...
const unsigned short sentinel = -1;
unsigned short index = 0;
for (Node* node = &nodes[index]; // root node
index != sentinel;
node = &nodes[index])
{
if (foo(*node)) {
index = node->left;
} else {
index = node->right;
}
}

换句话说,没什么特别的。但是,MSVC 2012 失败,尝试访问超出范围的nodes[sentinel]。原来它先计算&nodes[index],然后测试index。(调试模式,无优化)。

对我来说,这看起来像是一个代码生成错误,但我至少十年没有见过这样的错误。这是未经优化的纯代码。当然,即使进行了重新排列,在测试index之前实际上并没有使用node,并且在x86上拥有这样的越界指针并不是非常不安全,但是MSVC的vector<>正确地断言了该非法索引。

做了一个干净的构建并再次检查了程序集;它是可重复的。树也不是空的,总有一个根节点。

我是否忽略了某些东西,或者这真的是一个严重的编译器错误?

如果你有一个这样的 for 循环:

for (init; cond; step) { body; }

那么这是表达式/语句的执行顺序:

  1. 初始化
  2. cond;在 false 上停止,否则
  3. 身体
  4. cond;在 false 上停止,否则
  5. 身体

换句话说,它是它的同义词:

{
init;
while (cond) {
body;
step;
}
}

在您的情况下,可能会发生身体设置indexsentinel的情况。然后,您希望cond执行并打破循环,但请注意,在每次主体执行之后,step都会在cond之前执行。 这意味着node = &nodes[index]确实会被执行,新值为index,即sentinel。所以VS正在生产它应该生产的东西。

您的循环似乎与传统的for循环完全不同;我认为将其变成一个明确的while循环会更有意义。如果我正在对您的代码进行代码审查,我肯定会要求这样做。

你的代码重写为 while 循环就像

Node* node = &nodes[index]; // root node
while(index != sentinel)
{
{
if (foo(*node)) {
index = node->left;
} else {
index = node->right;
}
}
node = &nodes[index];
}

最后一行可能是对节点的访问[-1]。

我把你的循环重写为

unsigned short index = 0;
do
{
Node* node = &nodes[index];
if (foo(*node)) {
index = node->left;
} else {
index = node->right;
}
} while(index != sentinel);

"select" 没有被破坏。

查看循环是如何执行的。

  1. 初始化Node* node = &nodes[index]

  2. 检查索引index != sentinel。退出?

  3. 环形体。这改变了index

  4. node = &nodes[index]

  5. 回到 2.

在步骤 3index == -1之后,您可以在步骤 4 中获得超出范围的访问权限。

for (init; check; step) { body }表达式中,顺序为:initcheck,然后它不断重复循环bodystepcheck,以便在检查之前发生step

然而,你的循环在这里很奇怪,因为你不需要node参与体外!

您可以轻松地将其重写为:

const unsigned short sentinel = -1;
std::vector<Node> nodes = ...
for (unsigned short index = 0; // root node
index != sentinel;
)
{
Node& node = nodes[index];
if (foo(node)) {
index = node.left;
} else {
index = node.right;
}
}

这不仅更短,而且涉及的变量范围更窄:)

哨兵可能存在错误(如果作者不希望这种行为):无符号短不能为 -1; 它应该很短...或签名的内容