我正在尝试最小化我的类在内存中占用的大小(包括数据和指令)。我知道如何最小化数据大小,但我不太熟悉GCC如何放置成员函数。
它们是否存储在内存中,与它们在类中声明的顺序相同?
答案 0 :(得分:3)
出于内存数据表示的目的,C ++ class
可以有普通或静态成员函数,或者virtual
成员函数(包括一些virtual
析构函数,如果有的话)
普通或静态成员函数不占用数据存储器中的任何空间,但是当然它们的编译代码需要一些资源,例如作为文本中的二进制代码或可执行文件或进程的code segment。当然,他们还可以在call stack上需要static
数据(或线程局部数据)或本地数据(例如局部变量)。
我的回答是面向Linux的。我不了解Windows,也不知道GCC是如何处理它的。
虚拟成员函数通常通过virtual method table(或 vtable )实现;具有一些虚拟成员函数的class
通常具有指向该vtable的单个(假设单个 - inheritance)vtable指针的实例(实际上是文本段中包含的一些数据)。
请注意,vtables不是强制性的,并且不是C ++ 11标准所要求的。但我不知道任何不使用它们的C ++实现。
当你使用multiple-inheritance事情变得更复杂时,对象可能会有几个 vtable指针。
因此,如果您有class
(根类或使用单继承),虚拟成员函数的消耗是每个实例一个vtable指针(加上小空格) 单个 vtable本身所需要的)。如果你只有一个虚拟成员函数(或析构函数)或者其中的一千个(改变的是vtable本身),它就不会改变(对于每个实例)。每个类都有自己的单个 vtable(除非它没有虚拟成员函数),并且每个实例通常都有一个(对于单继承情况)vtable指针。
GCC编译器可以按照自己的意愿自由组织vtable(它的顺序和布局是你不应该关心的实现细节);另见this。在最近的GCC版本的实践中(对于单继承),vtable指针是对象的第一个单词,vtable包含虚拟方法声明顺序的函数指针,但是你不应该依赖这些细节。
GCC编译器可以按照自己的意愿自由组织代码段中的函数,并且它实际上会对它们进行重新排序(例如,用于优化)。上次我看,它按相反的顺序排序。但你当然不应该依赖那个命令! BTW GCC可以内联函数(即使未标记为inline
)并在优化时克隆函数。您还可以使用link-time optimizations编译和链接(例如make CXX='g++ -flto -Os'
),然后您可以要求profile-guided optimizations(GCC:{{1} },-fprofile-generate
,-fprofile-use
等...)
您不应该依赖于编译器(和链接器)如何组织功能代码或 vtables 。 将优化保留给编译器(此类优化取决于您的目标机器,编译器标志和编译器版本)。您也可以使用function attributes向GCC(或Clang / LLVM)编译器提供提示(例如-fauto-profile
,__attribute__((cold))
等等。)
如果你真的需要知道函数是如何放置的(IMHO是非常错误的),请研究生成的汇编代码(例如使用__attribute__((noinline))
)并注意它可能因编译器版本而异!
如果您需要在运行时在Linux和Posix系统上从名称中找出函数的地址,请考虑使用dlsym(对于Linux,请参阅dlsym(3),其中也记录g++ -O -fverbose-asm -S
})。请注意name mangling,您可以通过声明dladdr
等功能来禁用该功能(请参阅C++ dlopen minihowto)。
顺便说一句,您可以编译并链接extern "C"
(这对-rdynamic
等非常有用......)。如果您确实需要知道函数的地址,请使用nm(1)作为dlopen
您的可执行文件。
您可能还会阅读目标平台(和编译器)的ABI规范和calling conventions,例如Linux x86-64 ABI spec
答案 1 :(得分:1)
我们假设我们有一个T
类型,有4个实例方法。
class T {
public:
void member_function_1() { ... }
void member_function_2() { ... }
void member_function_3() { ... }
void member_function_4() { ... }
};
如果我们实例化T
的1个副本,或者我们实例化{100}个T
副本,那么这些方法占用的内存量是相同的。