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。
我正在尝试最小化我的 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。