The C language itself does not provide this ability, but most executable formats do provide a way to enable this capability, since it's required by debuggers.
You can often get function names from the callstack by using a library like libunwind or libbacktrace, but they're not always portable and they aren't always trivial to perform (execution cost), and they require that you build your program with debug symbols available.
In either case, this is only reliable when you build without optimization. As soon as the optimizer is involved, all bets are off.
For example,
if (pointer && pointer->sub_->something_) {
pointer->sub_->action(); //1
return nullptr;
}
/// ...
if (pointer) {
pointer->sub_->action(); //2
return nullptr;
}
/// ...
I've actually seen this in a production crash bug: the compiler told us we were accessing a null pointer at //1, which is clearly impossible. We couldn't repro the crash in testing, and the function was particularly long and complex.
What happened was that the compiler collapsed all the pointer->sub_->action(); return nullptr
s to one stub function which came from //1, and it was actually the unchecked call at //2 that was the source of the crash.
Between optimizations like this, inlining of functions, whole program optimization, etc, it can be incredibly difficult to accurately tell what is what from the machine state of a running program relative to the source code.
A further complication for stack traces is that in optimized code they often contain a forwarding address. Consider:
int f() {
g();
h();
}
If you were to check the callstack in g
there is a good chance it would look like it had been called from h
: the compiler can manipulate the stack so that when g
returns it goes straight to h
rather than wastefully returning to f
just to get another jump.
Variables are even harder - the optimizer works hard to eliminate them entirely, to usefully shuffle them around in registers, and so forth.
But you could in-theory build your own simple reflection system, wrapping variables in containers. This often gets clumsy, though.
For tracking the call stack:
#include <iostream>
#include <vector>
struct Callsite {
const char* file_;
size_t line_;
static thread_local std::vector<Callsite*> callStack;
Callsite(const char* file, size_t line) : file_(file), line_(line) {
callStack.push_back(this);
}
~Callsite() noexcept { callStack.pop_back(); }
};
thread_local std::vector<Callsite*> Callsite::callStack;
#define ENTER Callsite __callsite_entry(__FILE__, __LINE__);
void f() {
ENTER;
for (auto&& stack: Callsite::callStack) {
std::cout << stack->file_ << ":" << stack->line_ << "\n";
}
}
int main() {
ENTER;
f();
}
Live demo: http://ideone.com/ZAUVib