views:

248

answers:

3

Consider the following code.

#include <stdio.h>
#include <vector>
#include <iostream>

struct XYZ { int X,Y,Z; };
std::vector<XYZ> A;

int rec(int idx)
{

   int i = A.size();
   A.push_back(XYZ());
   if (idx >= 5)
     return i;

   A[i].X = rec(idx+1);

   return i;
}

int main(){
  A.clear();
  rec(0);
  puts("FINISH!");

}

I couldn't figure out the reason why the code gives a segmentation fault on Linux (IDE used: Code::Blocks) whereas on Windows (IDE used: Visual C++) it doesn't.

When I used Valgrind just to check what actually the problem was, I got this output.

I got Invalid write of size 4 at four different places. Then why didn't the code crash when I used Visual C++?

Am I missing something?

+16  A: 

The recursive call to rec() might modify the vector while you're assigning a value to it.

What happens if you replace

A[i].X = rec(idx+1);

with

int tmp = rec(idx+1);
A[i].X = tmp;

?

Edit

Just to summarize the useful comments: the operand evaluation order of a = operation is unspecified and since the vector wasn't preallocated, several resizes can occur during a recursive call to rec(), thus invalidating the values in the vector.

ereOn
To be a bit more precise, inside the recursive call, the call to `push_back` can cause the previous pointers to be invalidated.
R Samuel Klatchko
Couldn't it basically be the `i = ++i;` issue (except the modification of A on the right side is hidden in the recursive call)? The compiler is free to evaluate `A[i]` before or after `rec` and whether it "works", depends on which order it chooses to do so.
UncleBens
@ereOn: Yes! I think your answer is correct. The order of evaluation of operands associated with "=" operator is _unspecified_ .
Prasoon Saurav
@Prasoon Saurav: Glad I could help ;)
ereOn
A: 

You're using int i = A.size()

And then you're indexing your struct as an array, but using the size value. You need to reduce it by 1 e.g. A[i-1].X = rec(idx+1);

Ah my mistake - I didn't take account of the vector push_back.

ChrisBD
But he does the indexing based on the `size()` result *after* adding an element to the `vector`. At that point the index is OK.
Michael Burr
+1  A: 

I get "* error for object 0x300180: incorrect checksum for freed object - object was probably modified after being freed. *" when I run that code.

As I recall, A[i].X = rec(idx+1) has three sequence points. When operator[] is called on A, when rec is called, and at the end. But the order of the first two is unspecified. So if g++ calculates A[i] first, and then calls rec(idx+1), then when rec returns the reference returned by A[i] could have been invalidated by a reallocation of the vector's internal memory. Under VC++, it might be evaluating rec(idx+1) first, so all the push_back calls are done up front, which means the A[i] calls refer to the correct block of memory. Alternatively, it might do things the same way, and you just happen to not segfault... that's one of the problems of undefined behavior.

Changing std::vector<XYZ> A; to std::vector<XYZ> A(10); will reserve enough space for 10 elements. This prevents your specific implementation of rec from ever needing to reallocate, and that fixes the error on my end.

Dennis Zickefoose
+1 for giving a precise explanation :)
Prasoon Saurav
-1. This solution would surely work right up to the moment where you increase the max recursive value to 11.
deworde
@deworde: Obviously, hence the qualification that it prevents his specific implementation from needing to reallocate.
Dennis Zickefoose
True, but I felt it would have been good practice to put the full caveat into the answer, for clarity. If you edit it, I'll change my vote.
deworde