GCC如何在内存中存储成员函数?

How does GCC store member functions in memory?

我正在尝试最小化我的 class 在内存中占用的大小(包括数据和指令)。我知道如何最小化数据大小,但我不太熟悉 GCC 如何放置成员函数。

它们是否存储在内存中,与它们在 class 中声明的顺序相同?

假设我们有一个类型 T,它有 4 个实例方法。

class T {
    public:
        void member_function_1() { ... }
        void member_function_2() { ... }
        void member_function_3() { ... }
        void member_function_4() { ... }
};

如果我们实例化 1 个 T 的副本,或者如果我们实例化 100 万个 T 的副本,那么这些方法占用的内存量是相同的。

出于内存中数据表示的目的,C++ class 可以具有普通或静态成员函数,或 virtual 成员函数(包括一些 virtual 析构函数,如果任何)。

普通或静态成员函数不占用任何 space 数据内存,但它们的编译代码当然会占用一些资源,例如作为文本中的二进制代码或 code segment of your executable or your process. Of course, they can also require static data (or thread-local data), or local data (e.g. local variables) on the call stack.

我的回答是Linux为主。我不知道 Windows,也不知道 GCC 如何处理它。

虚拟成员函数通常通过 virtual method table (or vtable); a class having some virtual member functions usually have instances with a single (assuming single-inheritance) 指向该 vtable 的 vtable 指针(实际上是文本段中打包的一些数据)实现。

请注意,vtables 不是强制性的,也不是 C++11 标准所要求的。但我不知道任何不使用它们的 C++ 实现。

当您使用 multiple-inheritance 时,事情变得更加复杂,对象可能有 多个 vtable 指针。

所以如果你有一个class(根class,或者使用单继承),虚拟成员函数的消耗是一个每个实例的 vtable 指针(加上 single vtable 本身所需的小 space)。如果您只有一个虚拟成员函数(或析构函数)或一千个虚拟成员函数(或析构函数)(会改变的是 vtable 本身),它不会改变(对于每个实例)。每个 class 都有自己的 single vtable(除非它没有虚成员函数),每个实例通常有一个(对于单继承情况)vtable 指针。

GCC 编译器可以随意组织 vtable(它的顺序和布局是您不应该关心的实现细节);另见 this。在大多数最新的 GCC 版本的实践中(对于单继承),vtable 指针是对象的第一个字,vtable 包含按虚方法声明顺序排列的函数指针,但你不应该依赖这些细节。

GCC 编译器可以随意组织代码段中的函数,实际上它会重新排序它们(例如为了优化)。上次我看的时候,它以相反的顺序排列它们。但是你当然不应该依赖那个命令!顺便说一句,GCC 可以在优化时内联函数(即使未标记 inline)和克隆函数。您还可以使用 link-time optimizations (e.g. make CXX='g++ -flto -Os'), and you could ask for profile-guided optimizations (for GCC 编译 和 link-fprofile-generate-fprofile-use-fauto-profile 等...)

你不应该依赖编译器(和linker)如何组织函数代码或vtables将优化留给编译器(此类优化取决于您的目标机器、编译器标志和编译器版本)。您也可以使用 function attributes 向 GCC(或 Clang/LLVM)编译器提供提示(例如 __attribute__((cold))__attribute__((noinline)) 等等……)

如果您真的需要知道函数是如何放置的(恕我直言,这是非常错误的),请研究生成的汇编代码(例如使用 g++ -O -fverbose-asm -S)并注意它可能因编译器版本而异!

如果您需要在 Linux 和 Posix 系统上在运行时从函数名称中找出函数的地址,请考虑使用 dlsym (for Linux, see dlsym(3), which also documents dladdr). Be aware of name mangling, which you can disable by declaring such functions as extern "C" (see C++ dlopen minihowto).

顺便说一句,您可以使用 -rdynamic 编译和 link(这对 dlopen 等非常有用...)。如果你真的需要知道函数的地址,使用 nm(1) as nm -C your-executable.

您还可以阅读 ABI specification and calling conventions for your target platform (and compiler), e.g. Linux x86-64 ABI spec