using g++ 4.4.6, ltrace 0.6.0 and earlier
I built a shared library from foo.cpp and linked it to a program built
from f.cpp. All method names are reported for an auto, but if I call
operator new, the virtual (pure or otherwise) never show up:
$ ltrace -C ./f
(0, 0, 0x219800, -1, 0x1f25bc2) = 0x3715a21160
__libc_start_main(0x4008e4, 1, 0x7fff51839b48, 0x400a40, 0x400a30
<unfinished ...>
foo::static_foo()(1, 0x7fff51839b48, 0x7fff51839b58, 4, 0x3715f8b300)
= 0x3715f8cf60
operator new(unsigned long)(8, 0x7fff51839b48, 0x7fff51839b58, 4,
0x3715f8b300) = 0x1186010
foo::real_foo()(0x1186010, 0x1186020, 33, 0, 135168) = 0x1186010
foo::real_foo()(0x7fff51839a40, 0x1186020, 0x7f4dad83da86, 0, 135168)
= 0x7fff51839a40
foo::virtual_foo()(0x7fff51839a40, 0x1186020, 0x7f4dad83da86, 0,
135168) = 0x7fff51839a40
foo_child::pure_virtual_foo()(0x7fff51839a40, 0x1186020,
0x7f4dad83da86, 0, 135168) = 0x7fff51839a40
+++ exited (status 0) +++
foo.hpp:
class foo
{
public:
foo(){};
~foo(){};
static void static_foo();
virtual void virtual_foo();
virtual void pure_virtual_foo() = 0;
void real_foo();
};
class foo_child: public foo
{
public:
void pure_virtual_foo();
};
foo.cpp:
#include "foo.hpp"
#include <malloc.h>
void foo::static_foo()
{
}
void foo::virtual_foo()
{
}
void foo::real_foo()
{
}
void foo_child::pure_virtual_foo()
{
}
f.cpp:
#include "foo.hpp"
int main()
{
foo::static_foo();
foo_child* f = new foo_child;
f->real_foo();
f->virtual_foo();
// ltrace doesn't report this one?
f->pure_virtual_foo();
foo_child g;
g.real_foo();
g.virtual_foo();
// ltrace doe report this one!
g.pure_virtual_foo();
return 0;
}
Makefile for completeness:
all: f
f: f.cpp libfoo.so
g++ -Wall -L. -lfoo f.cpp -o f
libfoo.so: foo.cpp foo.hpp
g++ -Wall -fPIC -shared -o libfoo.so foo.cpp
clean:
rm -f f *.o *.so
Yes.
If we reduce f.cpp to
#include "foo.hpp"
int main() {
foo* f = new foo_child;
// ltrace doesn't report this one?
f->virtual_foo();
f->pure_virtual_foo();
}
then
$ nm -C f | grep foo
0000000000001190 W foo::foo()
0000000000001190 W foo::foo()
00000000000011aa W foo_child::foo_child()
00000000000011aa W foo_child::foo_child()
0000000000003da0 V vtable for foo
0000000000003d80 V vtable for foo_child
Because f is /actually/ fully virtual,
so calling either function actually looks like
(f->_vtable[1])(f)
and the initialiser for f looks like
f = malloc(...);
memcpy(f._vtable, vtable for foo_child)
Whereas real
#include "foo.hpp"
int main() {
foo_child g;
// ltrace does report this one
g.virtual_foo();
g.pure_virtual_foo();
}
yields
$ nm -C f | grep foo
U foo::virtual_foo()
00000000000011ba W foo::foo()
00000000000011ba W foo::foo()
00000000000011d4 W foo::~foo()
00000000000011d4 W foo::~foo()
U foo_child::pure_virtual_foo()
00000000000011ee W foo_child::foo_child()
00000000000011ee W foo_child::foo_child()
0000000000001218 W foo_child::~foo_child()
0000000000001218 W foo_child::~foo_child()
0000000000003d90 V vtable for foo
0000000000003d70 V vtable for foo_child
because g is a concrete type so it's devirtualised,
so the calls /are/ actually
foo_child::virtual_foo(&g)
&c.
You'd also see this from stack-allocated variables in more complex
scenarios that the compiler can't solve and devirtualise.
I'm pretty sure this is definitionally unsolvable.
Yes.
If we reduce f.cpp to
#include "foo.hpp"
int main() {
foo* f = new foo_child;
// ltrace doesn't report this one?
f->virtual_foo();
f->pure_virtual_foo();
}
then
$ nm -C f | grep foo
0000000000001190 W foo::foo()
0000000000001190 W foo::foo()
00000000000011aa W foo_child::foo_child()
00000000000011aa W foo_child::foo_child()
0000000000003da0 V vtable for foo
0000000000003d80 V vtable for foo_child
Because f is /actually/ fully virtual,
so calling either function actually looks like
(f->_vtable[1])(f)
and the initialiser for f looks like
f = malloc(...);
memcpy(f._vtable, vtable for foo_child)
Whereas real
#include "foo.hpp"
int main() {
foo_child g;
// ltrace does report this one
g.virtual_foo();
g.pure_virtual_foo();
}
yields
$ nm -C f | grep foo
U foo::virtual_foo()
00000000000011ba W foo::foo()
00000000000011ba W foo::foo()
00000000000011d4 W foo::~foo()
00000000000011d4 W foo::~foo()
U foo_child::pure_virtual_foo()
00000000000011ee W foo_child::foo_child()
00000000000011ee W foo_child::foo_child()
0000000000001218 W foo_child::~foo_child()
0000000000001218 W foo_child::~foo_child()
0000000000003d90 V vtable for foo
0000000000003d70 V vtable for foo_child
because g is a concrete type so it's devirtualised,
so the calls /are/ actually
foo_child::virtual_foo(&g)
&c.
You'd also see this from stack-allocated variables in more complex
scenarios that the compiler can't solve and devirtualise.
I'm pretty sure this is definitionally unsolvable.