Interaction of structs with object attributes and std::vector in c++ - c++

I have encountered this problem and am wondering what its cause is.
My code is as follows:
struct node{
bool leaf;
std::string label;
};
//in main
std::vector<node> graph;
graph.reserve(5);
Now, if i try to assign
graph[3].leaf = true;,
everything works out.
However, if i try to do the same with an object type, like
graphA[i].label = "01";,
I get a segmentation fault.
If i change the code to
struct node{
bool leaf;
std::string * label;
};
And allocate the memory for the string in every instance of node in the vector, I can now assign a value to graphA[i]->label without a problem.
Why is this? Any responses will be appreciated.

Now, if i try to assign graph[3].leaf = true;
You're invoking undefined behavior.
graph has no element at index 3. In fact, it has no elements at all. You only reserved memory for the vector, but didn't add any elements to it.
You can add 5 default-constructed elements using resize:
graph.resize(5);

Now, if i try to assign graph[3].leaf = true;, everything works out.
It's important to stress that this is a mere coincidence. Nothing works out - you have undefined behaviour because you access graph[3] when graph is empty. It would be much better if the program crashed right away, so that you notice something is wrong.
graph is empty because you confused std::vector's reserve and resize member functions. You don't set the vector's element count to 5, you just ask it to prepare its internally held memory for at least 5 elements in the future. std::vector is even allowed to ignore this request.
When you do graphA[i].label = "01";, you also have undefined behaviour, of course.
So why do the first version and the pointer "fix" (which also invokes undefined behaviour) seem to work fine while the other one crashes? C++ as a programming language does not distinguish between different kinds of undefined behaviour. Anything can happen; you may as well experience situations in which the first crashes and the second one "works out". Such is the nature of undefined behaviour.
What probably happens here in practice is that in the bool and std::string* case, you are coincidentally writing to a memory location your program is allowed to write to, because you are just dealing with a single bool or with a single pointer. In the std::string version, with all of std::string's automatic dynamic memory management happening behind the scenes, more places in memory are involved and so it happens that you hit a forbidden one.
But that's just a very speculative theory. If you really want to know for sure, debug the code and step into each individual operation. But remember that undefined behaviour is always undefined behaviour.

Related

Casting structs with non-aggregate members

I am receiving an segmentation fault (SIGSEGV) when I try to reinterpret_cast a struct that contains an vector. The following code does not make sense on its own, but shows an minimal working (failing) example.
// compiler: g++ -std=c++17
struct Table
{
std::vector<int> ids;
};
std::vector<std::byte> storage;
// put that table into the storage
Table table = {.ids = {3, 5}};
auto convert = [](Table x){ return reinterpret_cast<std::byte*>(&x); };
std::byte* bytes = convert(table);
storage.insert(storage.end(), bytes, bytes + sizeof(Table));
// ...
// get that table back from the storage
Table& tableau = *reinterpret_cast<Table*>(&storage.front());
assert(tableau.ids[0] == 3);
assert(tableau.ids[1] == 5);
The code works fine if I inline the convert function, so my guess is that some underlying memory is deleted. The convert function makes a local copy of the table and after leaving the function, the destructor for the local copy's ids vector is called. Recasting just
returns the vector, but the ids are already deleted.
So here are my questions:
Why does the segmentation fault happen? (Is my guess correct?)
How could I resolve this issue?
Thanks in advance :D
I see at least three reasons for undefined behavior in the shown code, that fatally undermines what the shown code is attempting to do. One or some combination of the following reasons is responsible for your observed crash.
struct Table
{
std::vector<int> ids;
};
Reason number 1 is that this is not a trivially copyable object, so any attempt to copy it byte by byte, as the shown code attempts to do, results in undefined behavior.
storage.insert(storage.end(), bytes, bytes + sizeof(Table));
Reason number 2 is that sizeof() is a compile time constant. You might be unaware that the sizeof of this Table object is always the same, whether or not its vector is empty or contains the first billion digits of π. The attempt here to copy the whole object into the byte buffer, this way, therefore fails for this fundamental reason.
auto convert = [](Table x){ return reinterpret_cast<std::byte*>(&x); };
Reason number 3 is that this lambda, for all practical purposes, is the same as any other function with respect to its parameters: its x parameter goes out of scope and gets destroyed as soon as this function returns.
When a function receives a parameter, that parameter is just like a local object in the function, and is a copy of whatever the caller passed to it, and like all other local objects in the function it gets destroyed when the function returns. This function ends up returning a pointer to a destroyed object, and subsequent usage of this pointer also becomes undefined behavior.
In summary, what the shown code is attempting to do is, unfortunately, going against multiple core fundamentals of C++, and manifests in a crash for one or some combination of these reasons; C++ simply does not work this way.
The code works fine if I inline the convert function
If, by trial and error, you come up with some combination of compiler options, or cosmetic tweaks, that avoids a crash, for some miraculous reason, it doesn't fix any of the underlying problems and, at some point later down the road you'll get a crash anyway, or the code will fail to work correctly. Guaranteed.
How could I resolve this issue?
The only way for you to resolve this issue is, well, not do any of this. You also indicated that what you're trying to do is just "store multiple vectors of different types in the same container". This happens to be what std::variant can easily handle, safely, so you'll want to look into that.

Should I reset primitive member variable in destructor?

Please see following code,
class MyClass
{
public:
int i;
MyClass()
{
i = 10;
}
};
MyClass* pObj = nullptr;
int main()
{
{
MyClass obj;
pObj = &obj;
}
while (1)
{
cout << pObj->i; //pObj is dangling pointer, still no crash.
Sleep(1000);
}
return 0;
}
obj will die once it comes out of scope. But I tested in VS 2017, I see no crash even after I use it.
Is it good practice to reset int member varialbe i?
Accessing a member after an object got destroyed is undefined behavior. It may seem like a good to set members in a destructor to a predictable and most likely unexpected value, e.g., a rather large value or a value with specific bit pattern making it easy to recognize the value in a debugger.
However, this idea is flawed and dwarved by the system:
All classes would need to play along and instead of concentrating on creating correct code developers would spent time (both development time as well as run-time) making pointless change.
Compilers happen get rather smart and can detect that changes in destructor are not needed. Since a correct program cannot detect whether the change was made they may not make the change at all. This effect is an actual issue for security applications where, e.g., a password should be erased from memory so it cannot be read (using some non-portable mean).
Even if the value gets set to a specific value, memory gets reused and the values get overwritten. Especially with objects on the stack it is most likely that the memory is used for something else before you see the bad value in a debugger.
Even when resetting values you would necessarily see a "crash": a crash is caused by something being setup to protect against something invalid. In your example you are accessing an int on the stack: the stack will remain accessible from a CPU point of view and at best you'd get an unexpected value. Use of unusual pointer values typically leads to a crash because the memory management system tries to access a location which isn't mapped but even that isn't guaranteed: on a busy 32 bit system pretty much all memory may be in use. That is, trying to rely on undefined behavior being detect is also futile.
Correspondingly, it is much better to use good coding practices which avoid dangling references right away and concentrate on using these. For example, I'm always initializing members in the member initializer list, even in the rare cases they end up getting changed in the body of the constructor (i.e., you'd write your constructor as MyClass(): i() {}).
As a debugging tool it may be reasonable to replace the allocation functions (ideally the allocator object but potentially the global operator new()/operator delete() and family with a version which doesn't quickly hand out released memory and instead fills the released memory with a predictable pattern. Since these actions slow down the program you'd only use this code in a debug build but it is relatively simple to implement once and easy to enable/disable centrally it may be worth the effort. In practice I don't think even such a system pays off as use of managed pointers and proper design of ownership and lifetime avoid most errors due to dangling references.
The behaviour of code you gave is undefined. Partial case of undefined behaviour is working as expected, so here is nothing strange that the code works. Code can work now and it can broke anyway at any time depending of compiler version, compiler options, stack content and a moon phase.
So first and most important is to avoid dangling pointers (and all other kinds of undefined behaviour) everywhere.
What about clearing variables in destructor, I found a best practice:
Follow coding rules saving me from mistakes of access to unallocated or destroyed objects. I cannot describe it in a few words but rules are pretty common (see here and anywhere).
Analyze code by humans (code review) or by statical analyzers (like cppcheck or PVS-Studio or another) to avoid cases similar to one you described above.
Do not call delete manually, better use scoped_ptr or similar object lifetime managers. When delete is reasonable, I usually (usually) set pointer to nullptr after deletion to keep myself from mistakes.
Use pointers as rare as it possible. References are preferred.
When objects of my class used outside and I suspect that somebody can access it after deletion I can put signature field inside, set it to something like 0xDEAD in destructor and check at enter or every public method. Here be careful to not slow down your code to unacceptable speed.
After all of this setting i from your example to 0 or -1 is redundant. As for me it's not a thing you should focus your attention.

Rather Strange clang issue

I tried a few Google searches before making this post, but to be honest I don't know what to search for. I have a C++ project and have been happily going about using the GNU compilers (g++). Today I tried to compile with clang++ and got a segfault.
Fine, ok, I can deal with this. After perusing my code and printing some stuff I was able to fix the problem. However the solution deeply troubles and confuses me.
Here's the situation: I'm using a tree-like data structure that stores a class called Ligament, but I'm storing it in a std::vector. I do this by storing a vector of "children" which are really just integer offsets between parent and child within the vector. In this way I can access children by using the this pointer, i.e
child = this[offset];
However, none of that's important. Here's this issue: I have an Ligament::addChild(int) function that takes an integer and pushes it to the back of a vector that is a member of Ligament:
void Ligament::addChild(uint32_t offset){
children.push_back(offset);
}
Very simple stuff. In general I pass to addChild an argument that gets returned from a recursive function called fill:
//starting at root
uint32_t fill(vector<Ligament>& lVec, TiXmlElement * el){
//store current size here, as size changes during recursion
uint32_t curIdx = lVec.size();
lVec.push_back(createLigament());
//Add all of this Ligament's children
TiXmlElement * i = el->FirstChildElement("drawable");
for (; i; i=i->NextSiblingElement("drawable")){
uint32_t tmp = fill(lVec, i) - curIdx;
lVec[curIdx].addChild(tmp);
//Does not work in clang++, but does in g++
//lVec[curIdx].addChild(fill(lVec,i)-curIdx);
}
//return the ligament's index
return curIdx;
}
The fill function gets called on an XML element and goes through its children, depth first.
Sorry if all that was unclear, but the core of the problem seems to be what's in that for loop. For some reason I have to store the return value of the fill call in a variable before I send it to the addChild function.
If I don't store it in a temporary variable, it seems as though the addChild function does not change the size of children, but I can't imagine why.
To check all this I printed out the size of the children vector before and after these calls, and it never went above 1. Only when I called addChild with a value that wasn't directly returned from a function did it seems to work.
I also printed out the values of offset inside the addChild function as well as inside the for loop before it was called. In all cases the values were the same, both in clang++ and in g++.
Since the issue is resolved I was able to move forward, but this is something I'd expect to work. Is there something I'm doing wrong?
Feel free to yell at me if I could do more to make this question clearer.
ALSO: I realize now that passing lVec around by reference through these recursions may be bad, as a push_back call may cause the address to change. Is this a legitimate concern?
EDIT:
So as people have pointed out, my final concern turned out to be related to the issue. The fill call has the potential to resize the vector, while the lVec[curIdx] = modifier will change an element in the vector. The order in which these things occurs can have drastic consequences.
As a follow up, is using the tmp variable acceptable? There's still the issue of a reallocation occuring...I think I will use SHR's suggestion of a map, then convert it to a vector when all is said and done.
// Does not work in clang++, but does in g++:
lVec[curIdx].addChild(fill(lVec,i)-curIdx);
The bug you are seeing is due to dependence on order of evaluation. Since fill(lVec, i) may cause lVec to reallocate its elements, the program will have undefined behavior if lVec[curIdx] is evaluated before fill(lVec,i). The order of evaluation of function arguments - and the postfix expression that determines which function to call - is unspecified.
I think it is undefined behavior.
you push into vector, and change it in the same command.
one compiler may do the fill first and the other may get lVec[curIdx] first.
if it is the case it will work for both compilers when you use map<uint32_t,uint32_t> instead of the vector. since map doesn't require the memory to be sequential.

0xfeeefeee segmentation fault, but simple allocation change?

I'm implementing a Barnes-Hut simulation program, and have this short snippet of code:
BhTree *BhTree::make() {
return new BhTree();
}
The rest of the code (substantial) then works great. The created nodes are never deleted during the program lifetime. Using this fact, I wanted to optimize the allocation by using this:
vector<BhTree> mSpace;
BhTree *BhTree::make() {
mSpace.push_back(BhTree());
return &mSpace[mSpace.size()-1];
}
This leads to a dreaded segmentation fault in an unrelated section of the code. Interestingly, in a recursive function, where this has suddenly been transformed into 0xfeeefeee, the Microsoft code for heap-freed memory.
Can anyone see off-hand what the problem might be? The vector mSpace is never accessed elsewhere.
As the std::vector grows it will reallocate memory for its contents and invalidate all the previous pointers you handed out.
You are returning a pointer to an object managed by the vector:
&mSpace[mSpace.size()-1];
The address will become invalid as soon as the vector expands it's internal buffer, and copies the objects to a new location.
You have 3 options:
Change all your code to pass your objects by value/reference instead of pointers
Make the vector store std::shared_ptr<Bhtree> instead (if your compiler supports C++ 11).
Make the vector store Bhtree* and make sure to delete each element
The second variant would look like this:
std::vector<std::shared_ptr<Bhtree>> mSpace;
BhTree *BhTree::make() {
mSpace.push_back(std::make_shared<BhTree>());
return mSpace.back().get();
}
mSpace.push_back() can resize the vector internal array when it determines that it is out of space. When this happens the internal array of the vector becomes invalid and the old pointers to the vector elements are also invalid.

C++ Dynamic Allocation Mismatch: Is this problematic?

I have been assigned to work on some legacy C++ code in MFC. One of the things I am finding all over the place are allocations like the following:
struct Point
{
float x,y,z;
};
...
void someFunc( void )
{
int numPoints = ...;
Point* pArray = (Point*)new BYTE[ numPoints * sizeof(Point) ];
...
//do some stuff with points
...
delete [] pArray;
}
I realize that this code is atrociously wrong on so many levels (C-style cast, using new like malloc, confusing, etc). I also realize that if Point had defined a constructor it would not be called and weird things would happen at delete [] if a destructor had been defined.
Question: I am in the process of fixing these occurrences wherever they appear as a matter of course. However, I have never seen anything like this before and it has got me wondering. Does this code have the potential to cause memory leaks/corruption as it stands currently (no constructor/destructor, but with pointer type mismatch) or is it safe as long as the array just contains structs/primitive types?
Formally the code causes undefined behavior because of the pointer type mismatch in new[]/delete[]. In practice it should work fine.
The pointer type mismatch issue can easily be fixed by adding a cast to the delete-expression
delete [] (BYTE *) pArray;
If Point type is defined as shown in the question (i.e. with trivial constructor and destructor), then this correction solves all formal issues there are in this code. From the language point of view, the lifetime of an object with trivial constructor (destructor) begins (ends) simultaneously with its storage duration. I.e. there's no requirement to perform the actual invocation of constructor (destructor).
As long as the constructors and destructors do nothing, then you're safe.
As long as you assure that it really does invoke a matching delete[] for every new[], it shouldn't leak -- but if an exception might be thrown by any of the code that's been commented out, that's going to be difficult to assure (basically, you need to catch any possible exceptions, delete the memory, then re-throw the exception).
I would first try to figure out why the code was written that way in the first place. It might be simply because the programmer didn't know any better, or because they were trying to work around some funky defect int he compiler. But there might be a real reason that you are unaware of. If there is, then unless you understand that reason and its side effects, you may introduce a defect by changing this code.
That out of the way, and assuming there is no particular reason why the code needs to be this way now, you should be safe in changing the code to use more modern and correct constructs.
But why? I understand the motivation to make the code more correct. But what do you really gain by this? If the code works the way it is now (a big assumption), then by changing the code you possibly gain the benefit of making the code more understandable to future programmers, but every line of code you change introduces the possibility for a new bug to be written.
And if finally you do decide to go ahead with the change, why stop halfway? Consider getting rid of all the news and deletes altogether, and replace them with vectors etc.