几年前学习V8 javascript engine的时候,发现了一处诡异的实现,当时非常的困惑,这样的实现怎么可能正确运行呢?
下面是我缩减的代码:
#include <iostream> typedef char* Address; class A { public: const static int i = 20; static int a[i]; void print () { for (int j = 0; j < i; ++j) std::cout << a[j] << std::endl; } public: static A* FromAddress (Address addr) { return reinterpret_cast<A*>(addr+1); } }; const int A::i; int A::a[i] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int main(int argc, char *argv[]) { A * a = new A; A * b = A::FromAddress (reinterpret_cast<Address>(a)); b->print (); return 0; }
如果你还没有发现其中的蹊跷指出,请看下面的gdb
输出:
(gdb) start Temporary breakpoint 1 at 0x804862d: file addr.cpp, line 23. Starting program: /home/liang/project/hello_world/a.out Temporary breakpoint 1, main (argc=1, argv=0xbfffe0d4) at addr.cpp:23 23 A * a = new A; (gdb) n 24 A * b = A::FromAddress (reinterpret_cast<Address>(a)); (gdb) n 25 b->print (); (gdb) p a $1 = (A *) 0x804b008 (gdb) p b $2 = (A *) 0x804b009 (gdb)
如果说b
不是一个有效的、指向A
的某个实例的指针,那么为什么无论在编译期还是执行期,上面的程序都没有报错呢?
我的理解:b
指针包含两部分信息——类型和值,在调用print
函数时,并不需要使用b
指针的值,编译器能够在编译期找到正确的函数去调用,这一点可以通过检查汇编输出确认。同样,访问A
的静态成员变量也不要b指针的值。那么,在什么情况下需要使用b指针的值的呢?一是访问非静态成员变量;二是调用虚成员函数。然而,class A
恰恰没有这两类成员,所以上面的程序能够正常地编译和执行。