问题
#include <iostream>
template <typename T>
struct node {
T value;
node const* prev;
constexpr node(const T& value, node const* prev = nullptr)
: value{value}, prev{prev} {}
constexpr node push_front(const T& value) const {
return node(value, this);
}
};
struct Something {
node<int> n;
constexpr Something(const int i) : n{node<int>(i)} {}
constexpr Something(const node<int>& n) : n{n} {}
};
constexpr void print(const Something& s) {
bool first = true;
for (const node<int>* i = &s.n; i != nullptr; i = i->prev) {
if (first) {
first = false;
} else {
std::cout << ", ";
}
std::cout << i->value;
}
}
constexpr Something recursive_case(Something& s, const unsigned int i) {
Something result(s.n.push_front(i % 10));
auto j = i / 10;
return j != 0 ? recursive_case(result, j) : result;
}
constexpr Something base_case(const unsigned int i) {
Something result(i % 10);
auto j = i / 10;
return j != 0 ? recursive_case(result, j) : result;
}
int main() { print(base_case(21)); }
I have a recursive function like the one shown above (base_case
and recursive_case
). I got the idea for the node
object from this link: https://gist.github.com/dabrahams/1457531#file-constexpr_demo-cpp-L66, and I modified it to suit my needs. The problem with what I have above is that I am getting a segmentation fault.
Thanks.
Edit(s):
Sorry for not trying a debugger earlier. Here is the output:
$ lldb ./ww ~/scratch/ww (lldb) target create "./ww" Current executable set to './ww' (x86_64). (lldb) run Process 32909 launched: './ww' (x86_64) Process 32909 stopped * thread #1: tid = 0x4d4e8e, 0x000000010000109b ww`print(s=0x00007fff5fbfec80) + 91 at ww.cpp:32, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT) frame #0: 0x000000010000109b ww`print(s=0x00007fff5fbfec80) + 91 at ww.cpp:32 29 } else { 30 std::cout << ", "; 31 } -> 32 std::cout << i->value; 33 } 34 } 35 (lldb)
I will try to use
new
or smart pointers, but from what I have read, they cannot be used inconstexpr
functions.I tried using using both
new
and smart pointers.For
new
, I get this error:ww.cpp:19:15: error: constexpr constructor never produces a constant expression [-Winvalid-constexpr] constexpr Something(const int i) : n{new node<int>(i)} {} ^ ww.cpp:19:42: note: subexpression not valid in a constant expression constexpr Something(const int i) : n{new node<int>(i)} {} ^
For
unique_ptr
, I get this error:ww.cpp:26:11: note: non-constexpr constructor 'unique_ptr' cannot be used in a constant expression : n{std::unique_ptr<node<int>, deleter<node<int>>>(new node<int>(i))} {}
I looked into it some more, and I think it is possible to solve this problem using C++ templates. I just need a way to capture the intermediate results of the recursion e.g. some kind of compile-time list and then reverse the order.
回答1:
It is an interesting question, but as Joachim Pileborg said in comment, executing the program in a debugger would have given you the why of the crash.
First the diagnostic:
- all is fine when hitting
print
function:s
contains a node with a previous node and that's all (s.prev->prev == nullptr
) - after the
std::cout << i->value;
it's broken:s.prev->prev != nullptr
when the instruction should not have changed that!
As it breaks when calling a function, it smells like there's a dangling pointer or reference around...
Now the explaination:
In your recursive calls, everything (the Something
and the node
s are all allocated as local variables and passed to recursive calls as reference. When calling down, all is fine, everything is allocated in a caller function. But when you return, only the Something
and its node
are corrects: all following nodes were allocated as local variables in now ended function and (calling result
the return value) result.prev
is a dangling pointer.
Using a dangling pointer is undefined behaviour and a SIGSEGV is possible.
TL/DR: you are allocating nodes of a linked list as local variables in recursive calls and end with dangling pointers so the undefined behaviour that hopefully caused a crash immediately.
BEWARE: undefined behaviour could work in all your tests and break later in production
Possible fix: as a class cannot contain an instance of itself, you cannot use return by value in a linked list and must use dynamic allocation. So you must implement explicit destructors or use smart pointers.
Above was mainly the explaination for the crash. It can be fixed by using smart pointers but constexpr
can no longer be used. If struct node
is modified that way:
template <typename T>
struct node {
T value;
std::shared_ptr<node const> prev;
node(const T& value, node const* prev = nullptr)
: value{value} {
this->prev = (prev == nullptr)
? std::shared_ptr<struct node const>(nullptr)
: std::make_shared<struct node const>(*prev);
}
node push_front(const T& value) const {
return node(value, this);
}
};
It can be safely returned by copy, because the shared_ptr
ensures that you will never get a dangling pointer. But it is true that the constructor can no longer be a constexpr
, so only print
remains one...
But it makes sense. As recursive_case
recusively builds a linked list, I cannot imagine a way to make that a constexpr. It might be possible by first allocating an array of node
s (since you will need a node
per digit here) and then linking those existing nodes. But if they are allocated as local variables in a recursive function you will not be able to avoid the dangling problem, and if they are dynamically allocated, it cannot be a constexpr
回答2:
Adding
constexpr node(const node& i_rhs)
: value(i_rhs.value)
, prev(i_rhs.prev == nullptr ? nullptr : new node(*i_rhs.prev))
{}
constexpr node(const node&& i_rhs)
: value(i_rhs.value)
, prev(i_rhs.prev)
{}
worked for me.
As far as I understand, the whole scenario is the following:
- Program enters base_case and creates first Something on stack.
- Program enters recursive_case and creates a second Something on stack, linking it with the first something.
- Program leaves recursive_case and a second Something is copied and returned.
- Program leaves base_case returning second Something and the first Something is not copied anywhere - it still lives on stack (in unused part of it).
- Program enters print - this instruction uses stack memory and first Something (which lives in unused part of stack) is filled by trash values.
- Program tries to access first Something and gets seg fault.
The fix solves this issue by performing deep copying of the objects. But this fix leads to memory leak: memory allocated in constructor is never freed. This can be fixed by using smart pointers or explicitly implementing destructor.
来源:https://stackoverflow.com/questions/33252137/how-do-i-capture-the-results-of-a-recursive-function-at-compile-time