I ran into some code that builds a linked list on the stack and via destructors cleans up correctly AFAICT
Code:
#include <iostream>#include <sstream>template <typename T>struct ConsList{ T value; ConsList<T> *tail;};struct NodeStackGuard{ ConsList<const char*> oldNodeStack; ConsList<const char*> &nodeStack; NodeStackGuard(ConsList<const char *> &nodeStack, const char *str) : oldNodeStack(nodeStack), nodeStack(nodeStack) { nodeStack = {str, &oldNodeStack}; } ~NodeStackGuard() { nodeStack = oldNodeStack; }};void printPath(const ConsList<const char *>* node){ if (node) { if (node->value) { std::cout << node->value; } printPath(node->tail); } else { std::cout << "\n"; }}void push(int depth, ConsList<const char *>& stack){ std::stringstream ss; ss << " depth: " << depth; std::string s(ss.str()); NodeStackGuard guard(stack, s.c_str()); if (depth > 0) { push(depth - 1, stack); } else { printPath(&stack); }}int main(){ ConsList<const char *> stack{nullptr, nullptr}; push(5, stack);}
Note: The actual code was not stacking strings, it was stacking pointers to parents while traversing an AST so that it could access the parents while deep in the AST hierarchy.
To explain what's happening. At the bottom a "stack" is started
ConsList<const char *> stack{nullptr, nullptr};
So we have
stack: {value: null, tail: null}
At the first iteration of push
we create guard0
guard0: oldNodeStack: {value: null, tail: null}, nodeStack: stackstack: {value: "depth 5", tail: guard0}
At the 2nd iteration of push
we'd have this
guard1: oldNodeStack: {value: "depth 5", tail: guard0}, nodeStack: stackguard0: oldNodeStack: {value: null, tail: null}, nodeStack: stackstack: {value: "depth 4", tail: guard1}
At the 3rd iteration of push
we'd have this
guard2: oldNodeStack: {value: "depth 4", tail: guard1}, nodeStack: stackguard1: oldNodeStack: {value: "depth 5", tail: guard0}, nodeStack: stackguard0: oldNodeStack: {value: null, tail: null}, nodeStack: stackstack: {value: "depth 3", tail: guard2}
Following that you see stack->guard2->guard1->guard0
Going backward, at the end of push
guard2's destructorwill restore `stack' so we'd have this
guard2: destroyedguard1: oldNodeStack {value: "depth 5", tail: guard0}, nodeStack: stackguard0: oldNodeStack {value: null, tail: null}, nodeStack: stackstack: {value: "depth 4", tail: guard1} // restored by guard2's destructor
again
guard1: destroyedguard0: oldNodeStack {value: null, tail: null}, nodeStack: stackstack: {value: "depth 5", tail: guard0} // restored by guard1's destructor
again
guard0: destroyedstack: {value: null, tail: null} // restored by guard0's destructor
The issue is, with -Wdangling-pointer
GCC complains
warning: storing the address of local variable
As far as I can tell there's nothing wrong with the code above.
A pointer is made to a variable that exists on the stack but that pointer is deleted when the local variable is deleted so nothing is dangling AFAICT.
I'm am missing where the dangling pointer is? Is this a bug in GCC? Is there some quirk of C++ I'm unaware of that's being triggered here? Is this just a limitation of what's possible it check for and I need to disable the warning?