C++ 内存分为 5 个区域:
- 堆 heap :由 new 分配的内存块,其释放编译器不去管,由程序员自己控制。如果程序员没有释放掉,在程序结束时系统会自动回收。涉及的问题:“缓冲区溢出”、“内存泄露”。栈 stack :是那些编译器在需要时分配,在不需要时自动清除的存储区。存放局部变量、函数参数。存放在栈中的数据只在当前函数及下一层函数中有效,一旦函数返回了,这些数据也就自动释放了。全局/静态存储区 (.bss段和.data段) :全局和静态变量被分配到同一块内存中。在 C 语言中,未初始化的放在.bss段中,初始化的放在.data段中;在 C++ 里则不区分了。常量存储区 (.rodata段) :存放常量,不允许修改(通过非正当手段也可以修改)。代码区 (.text段) :存放代码(如函数),不允许修改(类似常量存储区),但可以执行(不同于常量存储区)。
注意:静态局部变量也存储在全局/静态存储区,作用域为定义它的函数或语句块,生命周期与程序一致。
其中对象数据中存储非静态成员变量、虚函数表指针以及虚基类表指针(如果继承多个)。这里就有一个问题,既然对象里不存储类的成员函数的指针,那类的对象是怎么调用公用函数代码的呢?对象对公用函数代码的调用是在编译阶段就已经决定了的,例如有类对象a,成员函数为show(),如果有代码a.show(),那么在编译阶段会解释为 类名::show(&a)。会给show()传一个对象的指针,即this指针。
从上面的this指针可以说明一个问题:静态成员函数和非静态成员函数都是在类的定义时放在内存的代码区的,但是类为什么只能直接调用静态成员函数,而非静态成员函数(即使函数没有参数)只有类对象能够调用的问题?原因是类的非静态成员函数其实都内含了一个指向类对象的指针型参数(即this指针),因而只有类对象才能调用(此时this指针有实值)。
虚函数表
C++中虚函数是通过一张虚函数表(Virtual Table)来实现的,在这个表中,主要是一个类的虚函数表的地址表;这张表解决了继承、覆盖的问题。在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以当我们用父类的指针来操作一个子类的时候,这张虚函数表就像一张地图一样指明了实际所应该调用的函数。
C++编译器是保证虚函数表的指针存在于对象实例中最前面的位置(是为了保证取到虚函数表的最高的性能),这样我们就能通过已经实例化的对象的地址得到这张虚函数表,再遍历其中的函数指针,并调用相应的函数。