I've got a tree stored in an array, and i'm trying to find a particular node:
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;
}
}
Nothing special, in other words. However, MSVC 2012 fails with an attempt to access nodes[sentinel] which is out of range. It turns out that it first calculates &nodes[index], then tests index. (Debug mode, no optimizations).
To me, this looks like a code generation bug, but I haven't seen such bugs in at least a decade. It's plain unoptimized code. Sure, even with the rearrangement, node isn't actually used before index is tested, and it's not terribly unsafe on x86 to have such an out-of-bounds pointer, but MSVC's vector<> rightfully asserts on that illegal index.
Did a clean build and checked the assembly again; it's repeatable. The tree isn't empty either, there's always a root node.
Am I overlooking something or is this really a serious compiler bug?
If you have a for-loop like this:
for (init; cond; step) { body; }
Then this is the order in which the expressions/statements are executed:
In other words, it's a synonym for this:
{
init;
while (cond) {
body;
step;
}
}
In your case, it can happen that body sets index to sentinel. You then expect cond to execute and break the loop, but notice that after every body execution, step executes before cond. This means that node = &nodes[index] will indeed be executed, with the new value of index, which is sentinel. So VS is producing what it should.
Your loop seems quite different from a traditional for loop; I think it would make more sense to turn it into an explicit while loop. If I were doing a code review of your code, I would definitely request that.
Your code rewritten to a while loop is like
Node* node = &nodes[index]; // root node
while(index != sentinel)
{
{
if (foo(*node)) {
index = node->left;
} else {
index = node->right;
}
}
node = &nodes[index];
}
The last line might be an access to nodes[-1].
I'ld rewrite your loop to
unsigned short index = 0;
do
{
Node* node = &nodes[index];
if (foo(*node)) {
index = node->left;
} else {
index = node->right;
}
} while(index != sentinel);
"select" Isn't Broken. ;-)
See how the loop is executed.
Initialise Node* node = &nodes[index]
Check index index != sentinel. Exit?
Loop body. This changes index!
node = &nodes[index]
Back to 2.
When after step 3 index == -1, you get your out of range access on step 4.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With