Commit a44fc412 authored by bbguimaraes's avatar bbguimaraes
Browse files

blog: c++ virtual tables

parent d0b1737a
This diff is collapsed.
struct A { virtual void a(void); long long i; };
struct B { virtual void b(void); };
struct D : A, B {};
void f(B*);
void g(D *p) {
f(p);
}
#!/bin/bash
set -euo pipefail
gcc \
-std=c18 -S -masm=intel -O2 \
-fno-stack-protector -fno-asynchronous-unwind-tables \
-o - \
"$@" \
| c++filt
#!/bin/bash
set -euo pipefail
g++ \
-std=c++20 -S -masm=intel -O2 \
-fno-exceptions -fno-rtti -fno-stack-protector \
-fno-asynchronous-unwind-tables \
-o - \
"$@" \
| c++filt
struct B { virtual void b(void); };
void *f(B *p) {
return dynamic_cast<void*>(p);
}
#include <stddef.h>
struct typeinfo;
struct vtable_header {
ptrdiff_t offset_to_top;
const struct typeinfo *typeinfo;
};
struct S;
struct S_vtable {
void (*const f)(struct S*);
};
void S_f(struct S*) {}
const struct {
const struct vtable_header header;
const struct S_vtable vtable;
} S_vtable = {
.vtable.f = S_f,
};
struct S {
const struct S_vtable *vptr;
};
struct S s = {
.vptr = &S_vtable.vtable,
};
void f(struct S *p) {
p->vptr->f(p);
}
struct S {
virtual void f(void);
} s;
void f(S *p) {
p->f();
}
struct S {
virtual void f(void);
virtual void g(void);
virtual void h(void);
} s;
void f(S *p) {
p->f(), p->g(), p->h();
}
struct S {
virtual void f(void) {}
virtual void g(void) {}
virtual void h(void) {}
} s;
void f(S *p) {
p->f(), p->g(), p->h();
}
#include <stddef.h>
struct typeinfo;
struct vtable_header {
ptrdiff_t offset_to_top;
const struct typeinfo *typeinfo;
};
struct S;
struct S_vtable {
void (*const f)(struct S*);
};
void S_f(struct S*) {}
const struct {
const struct vtable_header header;
const struct S_vtable vtable;
} S_vtable = {
.vtable.f = S_f,
};
struct S {
const struct S_vtable *vptr;
};
struct S s = {
.vptr = &S_vtable.vtable,
};
void f(struct S *p) {
void (*f)(struct S*) = p->vptr->f;
__builtin_expect(f == S_f, 1) ? S_f(p) : f(p);
}
struct S {
virtual void f(void) {}
} s;
void f(S *p) {
p->f();
}
CFLAGS := -O2 -g3
CXXFLAGS := $(CFLAGS) -fno-rtti
all: test
test.o: test.cpp
test: test.o evil.o
$(CXX) -o $@ $^
.PHONY: check clean
check: test
./test | c++filt
clean:
rm -f *.o test
#include <stddef.h>
#include <stdio.h>
#define container_of(p, t, m) ((t*)((char*)(p) - offsetof(t, m)))
struct typeinfo;
struct vtable_header {
ptrdiff_t offset_to_top;
const struct typeinfo *typeinfo;
};
struct S;
struct T;
struct U;
struct S_vtable {
void (*const sf)(struct S*);
};
struct T_vtable {
void (*const tf)(struct T*);
};
struct U_vtable {
const struct S_vtable S;
void (*const uf)(struct U*);
};
// base class definition in c
struct S {
const struct S_vtable *vptr;
};
// base class definition in c
struct T {
const struct T_vtable *vptr;
};
// derived class definition in c
struct U {
union {
const struct U_vtable *vptr;
struct S s;
};
struct T t;
};
// external references to member functions, defined in c++
// S::sf()
void _ZN1S2sfEv(struct S *p) { printf("%s in %s\n", __func__, __FILE__); }
// T::tf()
void _ZN1T2tfEv(struct T *p) { printf("%s in %s\n", __func__, __FILE__); }
// U::tf()
void _ZN1U2tfEv(struct U *p) { printf("%s in %s\n", __func__, __FILE__); }
// U::uf()
void _ZN1U2ufEv(struct U *p) { printf("%s in %s\n", __func__, __FILE__); }
// pointer-adjusting entrypoint for overidden function
// non-virtual thunk to U::tf()
void _ZThn8_N1U2tfEv(struct T* p) {
printf("%s in %s\n", __func__, __FILE__);
// U::tf()
return _ZN1U2tfEv(container_of(p, struct U, t));
}
// vtable for S
const struct {
const struct vtable_header header;
const struct S_vtable vtable;
} _ZTV1S = {
// S::sf()
.vtable.sf = _ZN1S2sfEv,
};
// vtable for T
const struct {
const struct vtable_header header;
const struct T_vtable vtable;
} _ZTV1T = {
// T::tf()
.vtable.tf = _ZN1T2tfEv,
};
// vtable for U
const struct {
const struct vtable_header header;
const struct U_vtable vtable;
const struct vtable_header T_header;
const struct T_vtable T_vtable;
} _ZTV1U = {
// S::sf()
.vtable.S.sf = _ZN1S2sfEv,
// U::uf()
.vtable.uf = _ZN1U2ufEv,
.T_header.offset_to_top = -offsetof(struct U, t),
// T::tf()
.T_vtable.tf = _ZN1T2tfEv,
.T_vtable.tf = _ZThn8_N1U2tfEv,
};
struct S s = {
// vtable for S
.vptr = &_ZTV1S.vtable,
};
struct T t = {
// vtable for T
.vptr = &_ZTV1T.vtable,
};
struct U u = {
// vtable for U
.vptr = &_ZTV1U.vtable,
// vtable for U
.t.vptr = &_ZTV1U.T_vtable,
};
// free functions declared in c++
// f(S*)
void _Z1fP1S(struct S *p) {
printf("%s in %s\n", __func__, __FILE__);
p->vptr->sf(p);
}
// f(T*)
void _Z1fP1T(struct T *p) {
printf("%s in %s\n", __func__, __FILE__);
p->vptr->tf(p);
}
// f(U*)
void _Z1fP1U(struct U *p) {
printf("%s in %s\n", __func__, __FILE__);
p->vptr->S.sf(&p->s);
p->t.vptr->tf(&p->t);
p->vptr->uf(p);
}
#include <cstdio>
// base and derived classes declared in C++
struct S {
// base function
virtual void sf(void);
};
struct T {
// base overridden function
virtual void tf(void);
};
struct U : S, T {
// derived overridden function
void tf(void) override;
// leaf virtual function
virtual void uf(void);
};
// derived class defined in C++
struct V : U {
void tf(void) override { std::printf("V::tf() in %s\n", __FILE__); }
void uf(void) override {
std::puts("\n=== C++ obj -> C++ offset -> C base impl ===\n");
U::uf();
std::puts("\n=== C++ obj -> C++ offset -> C derived impl ===\n");
std::printf("V::uf() in %s\n", __FILE__);
}
virtual void vf(void) { std::printf("V::vf() in %s\n", __FILE__); }
};
// C++ objects defined in C
extern S s;
extern T t;
extern U u;
// C++ functions defined in C
void f(S *p);
void f(T *p);
void f(U *p);
int main(void) {
extern S s;
extern T t;
std::puts("=== C obj -> C impl ===\n");
f(&s);
f(&t);
extern U u;
std::puts("\n=== C obj -> C++ offset -> C base impl ===\n");
f(static_cast<S*>(&u));
std::puts("\n=== C obj -> C++ offset -> C prelude -> C derived impl ===\n");
f(static_cast<T*>(&u));
std::puts("\n=== C obj -> C impl -> virtual calls ===\n");
f(&u);
V v;
std::puts("\n=== C++ obj -> C++ offset -> C base impl ===\n");
static_cast<S*>(&v)->sf();
std::puts("\n=== C++ obj -> C++ offset -> C++ derived impl ===\n");
static_cast<T*>(&v)->tf();
static_cast<U*>(&v)->uf();
std::puts("\n=== C++ obj -> C++ impl ===\n");
(&v)->vf();
}
#include <stddef.h>
#define container_of(p, t, m) ((t*)((char*)(p) - offsetof(t, m)))
struct typeinfo;
struct vtable_header {
ptrdiff_t offset_to_top;
const struct typeinfo *typeinfo;
};
struct S;
struct T;
struct U;
struct S_vtable {
void (*const sf)(struct S*);
};
struct T_vtable {
void (*const tf)(struct T*);
};
struct U_vtable {
const struct S_vtable S;
const struct T_vtable T;
void (*const uf)(struct U*);
};
struct S {
const struct S_vtable *vptr;
};
struct T {
const struct T_vtable *vptr;
};
struct U {
union {
const struct U_vtable *vptr;
struct S s;
};
struct T t;
};
void S_sf(struct S*) {}
void T_tf(struct T*) {}
void U_tf(struct U*);
void U_uf(struct U*) {}
void U_t_tf(struct T* p) {
U_tf(container_of(p, struct U, t));
}
const struct {
const struct vtable_header header;
const struct S_vtable vtable;
} S_vtable = {
.vtable.sf = S_sf,
};
const struct {
const struct vtable_header header;
const struct T_vtable vtable;
} T_vtable = {
.vtable.tf = T_tf,
};
const struct {
const struct vtable_header header;
const struct U_vtable vtable;
const struct vtable_header T_header;
const struct T_vtable T_vtable;
} U_vtable = {
.vtable.S.sf = S_sf,
.vtable.T.tf = (void(*)(struct T*))U_tf,
.vtable.uf = U_uf,
.T_header.offset_to_top =
-offsetof(struct U, t),
.T_vtable.tf = U_t_tf,
};
struct S s = {
.vptr = &S_vtable.vtable,
};
struct T t = {
.vptr = &T_vtable.vtable,
};
struct U u = {
.vptr = &U_vtable.vtable,
.t.vptr = &U_vtable.T_vtable,
};
void f_S(struct S *p) {
p->vptr->sf(p);
}
void f_T(struct T *p) {
p->vptr->tf(p);
}
void f_U(struct U *p) {
p->vptr->S.sf(&p->s);
p->vptr->T.tf((struct T*)p);
p->vptr->uf(p);
}
struct S {
virtual void sf(void);
} s;
struct T {
virtual void tf(void);
} t;
struct U : S, T {
void tf(void) override;
virtual void uf(void);
} u;
void f(S *p) {
p->sf();
}
void f(T *p) {
p->tf();
}
void f(U *p) {
p->sf(), p->tf(), p->uf();
}
#include <stddef.h>
#define container_of(p, t, m) ((t*)((char*)(p) - offsetof(t, m)))
struct typeinfo;
struct vtable_header {
ptrdiff_t offset_to_top;
const struct typeinfo *typeinfo;
};
struct S;
struct T;
struct U;
struct S_vtable {
void (*const sf)(struct S*);
};
struct T_vtable {
void (*const tf)(struct T*);
};
struct U_vtable {
const struct S_vtable S;
const struct T_vtable T;
void (*const uf)(struct U*);
};
struct S {
const struct S_vtable *vptr;
};
struct T {
const struct T_vtable *vptr;
};
struct U {
union {
const struct U_vtable *vptr;
struct S s;
};
struct T t;
};
void S_sf(struct S*) {}
void T_tf(struct T*) {}
void U_tf(struct U*) {}
void U_uf(struct U*) {}
void U_t_tf(struct T* p) {
return U_tf(
container_of(p, struct U, t));
}
const struct {
const struct vtable_header header;
const struct S_vtable vtable;
} S_vtable = {
.vtable.sf = S_sf,
};
const struct {
const struct vtable_header header;
const struct T_vtable vtable;
} T_vtable = {
.vtable.tf = T_tf,
};
const struct {
const struct vtable_header header;
const struct U_vtable vtable;
const struct vtable_header T_header;
const struct T_vtable T_vtable;
} U_vtable = {
.vtable.S.sf = S_sf,
.vtable.T.tf = U_tf,
.vtable.uf = U_uf,
.T_header.offset_to_top =
-offsetof(struct U, t),
.T_vtable.tf = U_t_tf,
};
struct S s = {
.vptr = &S_vtable.vtable,
};