Wierd runtime behaviour

Started by
9 comments, last by Hodgman 11 years, 7 months ago
Hello,

I have some strange issue with my factory class and the objects it creates.

I have minimal code which looks similiar and have sort-of similiar error behaviour. See below, I run this on visual studio 2010 with boost 1_49_0


#include "boost/function.hpp"

typedef boost::function1<void, void*> Task;
typedef int SomeArg;
class Allocator
{
public:
template<class T>
T* create() { return new T(); }
template<class T, typename arg0>
T* create(arg0 a0) { return new T(a0); }
template<class T, typename arg0, typename arg1>
T* create(arg0 a0, arg1 a1) { return new T(a0, a1); }
template<class T, typename arg0, typename arg1, typename arg2>
T* create(arg0 a0, arg1 a1, arg2 a2) { return new T(a0, a1, a2); }
template<class T, typename arg0, typename arg1, typename arg2, typename arg3>
T* create(arg0 a0, arg1 a1, arg2 a2, arg3 a3) { return new T(a0, a1, a2, a3); }
};


class TestClass
{
public:
TestClass(SomeArg& arg0, SomeArg& arg1) : mArg0 (arg0), mArg1(arg1) { }
TestClass(Task task, void* funcArg, SomeArg& arg0, SomeArg& arg1) : mArg0 (arg0), mArg1(arg1) { mTask = task; mFuncArg = funcArg; }

private:
SomeArg& mArg0;
SomeArg& mArg1;
Task mTask;
void* mFuncArg;
};


class myFactory
{
public:
myFactory(SomeArg& arg0, SomeArg& arg1) : mArg0(arg0), mArg1(arg1) { }
TestClass* createTest() { return alloc.create<TestClass, SomeArg, SomeArg>(mArg0, mArg1); }
TestClass* createTest(Task task, void* funcArg) { return alloc.create<TestClass, Task, void*, SomeArg, SomeArg>(task, funcArg, mArg0, mArg1); }

private:
SomeArg& mArg0;
SomeArg& mArg1;
Allocator alloc;
};


void MyTestFunction(void* asdf)
{
//..
}


int _tmain(int argc, _TCHAR* argv[])
{
SomeArg a0 = 1;
SomeArg a1 = 2;
myFactory fact(a0, a1);
TestClass* t1 = fact.createTest();
TestClass* t2 = fact.createTest(&MyTestFunction, NULL);
TestClass* t3 = fact.createTest(&MyTestFunction, NULL);
TestClass* t4 = fact.createTest(&MyTestFunction, NULL);
TestClass* t5 = fact.createTest(&MyTestFunction, NULL);
TestClass* t7 = fact.createTest();


// now t2, t3, t4, t5 all have their member variables invalidated???

return 0;
}


Basically after creation of t7, all t2-t5 have their member variables invalidated; their mArg0 and mArg1 for example are just 0xcccccccc. However using the debugger I see that they are clearly valid up untill t7.
This is not the same output as in my main code (where the mTask = task in t2 throws an exception) but I think the cause of the error is the same

Why is this? There must be some bug with my factory/allocation scheme.
Advertisement
Please post actual code, it makes it much easier to find this sort of thing.

My guess is that you are using a container like std::vector and you have used enough space to cause it to resize. Whenever you add an item to a vector container it can invalidate all the pointers within it. There are other containers that don't have that property.

Please post actual code, it makes it much easier to find this sort of thing.

My guess is that you are using a container like std::vector and you have used enough space to cause it to resize. Whenever you add an item to a vector container it can invalidate all the pointers within it. There are other containers that don't have that property.


I posted a minimal code sample which reproduces this, did it not display in the post?
When I ran your code, t2-t5 aren't corrupt after t7 is created... they're corrupt as soon as they're created
[edit] because:class TestClass
{
public:
TestClass(Task task, void* funcArg, int& arg0, int& arg1) : mArg0 (arg0), mArg1(arg1)
private:
SomeArg& mArg0;
SomeArg& mArg1;<-- this reference points to...
...
TestClass* create(Task a0, void* a1, int a2, int a3)<-- this temporary stack value
{ return new T(a0, a1, a2, a3); }
Here's what I see in the debugger (copied from Locals):

Breakpoint at t4 (note t1 looks buggered):

+ t4 0xcccccccc {mArg0=??? mArg1=??? mTask={...} ...} TestClass *
+ t1 0x006120e0 {mArg0=0xcccccccc mArg1=0xcccccccc mTask={...} ...} TestClass *
+ t5 0xcccccccc {mArg0=??? mArg1=??? mTask={...} ...} TestClass *
+ t3 0x006121c0 {mArg0=0x0017fbe8 mArg1=0x011817a4 mTask={...} ...} TestClass *
+ t2 0x00612150 {mArg0=0x0017fbe8 mArg1=0x011817a4 mTask={...} ...} TestClass *
+ t7 0xcccccccc {mArg0=??? mArg1=??? mTask={...} ...} TestClass *

Breakpoint after t7:

+ t4 0x00722230 {mArg0=0xcccccccc mArg1=0xcccccccc mTask={...} ...} TestClass *
+ t1 0x007220e0 {mArg0=0x00000001 mArg1=0x00000002 mTask={...} ...} TestClass *
+ t5 0x007222a0 {mArg0=0xcccccccc mArg1=0xcccccccc mTask={...} ...} TestClass *
+ t3 0x007221c0 {mArg0=0xcccccccc mArg1=0xcccccccc mTask={...} ...} TestClass *
+ t2 0x00722150 {mArg0=0xcccccccc mArg1=0xcccccccc mTask={...} ...} TestClass *
+ t7 0x00722310 {mArg0=0x00000001 mArg1=0x00000002 mTask={...} ...} TestClass *

I have no idea what is happening.
The templated allocation functions appear to be taking their arguments by value. Adding a & modifier to the two argument constructor and to the final two parameters of the 4 argument constructor does not cause the behaviour you are seeing. In fact I see the behaviour you describe when the second object has been completed, as per this example:

#include "boost/function.hpp"

typedef boost::function1<void, void*> Task;
typedef int SomeArg;
class Allocator
{
public:
template<class T, typename arg0, typename arg1>
T* create(arg0 a0, arg1 a1) { return new T(a0, a1); }

template<class T, typename arg0, typename arg1, typename arg2, typename arg3>
T* create(arg0 a0, arg1 a1, arg2 a2, arg3 a3) { return new T(a0, a1, a2, a3); }
};


class TestClass
{
public:
TestClass(SomeArg& arg0, SomeArg& arg1) : mArg0 (arg0), mArg1(arg1) {
std::cout << "contruct2(" << &arg0 << ", " << &arg1 << ")\n";
}


TestClass(Task task, void* funcArg, SomeArg& arg0, SomeArg& arg1) : mArg0 (arg0), mArg1(arg1) {
std::cout << "contruct4(" << &arg0 << ", " << &arg1 << ")\n";
mTask = task;
mFuncArg = funcArg;
}

void print(int i) {
std::cout << i << ": " << mArg0 << ", " << mArg1 << ", " << mTask << "(" << mFuncArg << ")\n";
}

private:
SomeArg& mArg0;
SomeArg& mArg1;
Task mTask;
void* mFuncArg;
};


class myFactory
{
public:
myFactory(SomeArg& arg0, SomeArg& arg1) : mArg0(arg0), mArg1(arg1) { }
TestClass* createTest() { return alloc.create<TestClass, SomeArg, SomeArg>(mArg0, mArg1); }
TestClass* createTest(Task task, void* funcArg) { return alloc.create<TestClass, Task, void*, SomeArg, SomeArg>(task, funcArg, mArg0, mArg1); }

private:
SomeArg& mArg0;
SomeArg& mArg1;
Allocator alloc;
};


void MyTestFunction(void* asdf)
{
//..
}

int main(int argc, char* argv[])
{
SomeArg a0 = 1;
SomeArg a1 = 2;
std::cout << "arguments(" << &a0 << ", " << &a1 << ")\n";

myFactory fact(a0, a1);
TestClass* t1 = fact.createTest();

t1->print(1);

TestClass* t2 = fact.createTest(&MyTestFunction, NULL);

t1->print(1);
t2->print(2);

TestClass* t3 = fact.createTest();

t1->print(1);
t2->print(2);
t3->print(3);

return 0;
}


Output:

$ g++ test.cpp
$ ./a.out
arguments(0x7fff264bce68, 0x7fff264bce6c)
contruct2(0x7fff264bcdb4, 0x7fff264bcdb0)
1: 1, 2, 0(0)
contruct4(0x7fff264bcd44, 0x7fff264bcd40)
1: 1, 642502160, 0(0)
2: 32767, 642502032, 1(0)
contruct2(0x7fff264bcdb4, 0x7fff264bcdb0)
1: 1, 2, 0(0)
2: 32767, 642502032, 1(0)
3: 3, 2, 0(0)
[/quote]
You can see the addresses of the constructor parameters is not that of the values on the stack, and the corruption comes as soon as the construction returns.

Modifying the allocator like so works:

class Allocator
{
public:
template<class T, typename arg0, typename arg1>
T* create(arg0 &a0, arg1 &a1) { return new T(a0, a1); }

template<class T, typename arg0, typename arg1, typename arg2, typename arg3>
T* create(arg0 a0, arg1 a1, arg2 &a2, arg3 &a3) { return new T(a0, a1, a2, a3); }
};
I see.
Is there any good general solution to this? For example it's just circumstance 3rd and 4th argument are passed as references, since I intend to forward any argument, whether by value or reference, to the the created classes constructors.
I believe the simplest solution, if you cannot use perfect forwarding in C++11 (I'm not sure of how supported it is), is to overload each combination of const and non-const references.

So:

class Allocator
{
public:
// ...

template<class T, typename arg0, typename arg1>
T* create(const arg0 &a0, const arg1 &a1) { return new T(a0, a1); }

template<class T, typename arg0, typename arg1>
T* create(const &arg0 a0, arg1 &a1) { return new T(a0, a1); }

template<class T, typename arg0, typename arg1>
T* create(arg0 &a0, const arg1 &a1) { return new T(a0, a1); }

template<class T, typename arg0, typename arg1>
T* create(arg0 &a0, arg1 &a1) { return new T(a0, a1); }

// ...
};

Obviously doing so by hand is tedious and error prone, but writing a script to generate the source from 0 to some N should be fairly easy.
Can I still pass arguments by value with those declarations?

Are there any other/better object construction schemes btw?

Can I still pass arguments by value with those declarations?
[/quote]
Pass by value will go through the const reference overloads, so they should work perfectly. As the code is simple and inlined in the template body, there should be no overhead of indirect access. Const references can also bind to temporaries and literals.


Are there any other/better object construction schemes btw?
[/quote]
Apart from C++11 perfect forwarding, not that I am aware of.

This topic is closed to new replies.

Advertisement