关于extern "C",关于C++对象内存模型


extern "C" 主要作用是为了正确实现C++ 代码调用C语言代码,加上extern"C"后,编译器识别到关键字之后会按照C语言的方式来编译,当然C++支持函数重载,所以编译器编译函数的过程会将函数的参数类型和函数名字也加到编译后的代码中,但是C语言不支持函数重载,所以编译C语言代码的函数是不会带入函数的参数类型,只会带入函数名字。 注意的是,按照C语言方式编译,采取一种和 C兼容的语言。

extern

  1. extern 告诉编译器,其声明的函数和变量可以在本模块或者其他模块中使用,
  2. 其方式是在模块的头文件中对本模块提供给其他模块引用函数和全局变量以关键字extern 声明。
  3. 例如当模块B引用模块A中定义的全局变量和函数,只需要包涵模块A的头文件即可,
  4. 即使在编译阶段,模块B调用模块A中的函数找不到,但是不会报错,在链接阶段从模块A中生成,目标代码中找到该函数。
  5. 与extern 对应是static,static 修饰的变量和函数只能在本模块中使用,不能被extern "C" 修饰。

extern "C"

extern 修饰的目标是C ,一般来说 c void foo(int x, int y); c语言编译器编译后为_foo ,c++编译后为_foo_int_int(可能编译器不同,名字不同),extern ”C“ 解决了foo 这个函数名按照C规则翻译不要按照C++ 翻译。

__BEGIN_DECLS __END_DECLS

这些一般都在标准库文件定义好的,一般用来扩充C语言在编译的时候按照C++编译器进行统一处理,让C++代码能够调用C编译生成的中间代码。

关于C++对象内存模型

原则:类对象成员变量在内存中的排列符合”较晚出现的成员变量在对象内存中有较高的内存地址“

单一对象

  1. 如果类中没有虚函数,那么对象在内存中就只有对象的非静态成员变量,变量存储顺序就是声明的顺序,内存中没有具体函数是因为函数不属于具体的某一个对象,而是属于类本身,所有对象共享。
  2. 如果类中有虚函数,那么对象内存的首地址会额外增加一个虚函数指针,该指针指向的内存区域存储类中所有虚函数,对象size 会变大,虚函数指针之后就是非静态成员变量。
  3. 如果类中析构函数为虚函数,那么虚函数表中会产生两个析构函数。在GCC 中虚析构函数在虚表中是一对,为complete object destructor 和 deleting destructor ,前者执行析构函数不进行delete,后者在析构之后执行deleting 操作,把non-delete析构函数轮训一遍后,然后用delete 析构直接清理掉内存。

单一继承

普通继承

  1. 如果都没有虚函数,那么基类的非静态成员按序分布,派生类现在内存中分配基类对象,然后分配自己的非静态成员变量。派生类自己的成员变量分配在对象内存的尾部。
  2. 如果基类有虚函数,那么派生类会开辟一块内存空间作为虚函数表,保存继承下来的所有虚函数(虚析构函数除外)然后子类重写虚函数,就将对应的函数地址替换成子类的虚函数地址,重写虚函数时,只要函数同名同参同返回类型,就可以不写virtual关键字,如果子类新增了虚函数,那么直接添加到虚函数表尾部。
  3. 如果某个类有虚析构函数,那么继承链中位于其后的派生类都会自己生成默认的虚析构函数,如果派生类有自定义的非虚构函数,那么虚函数表中仍有自己的默认虚析构函数地址,调用的也是自己自定义的。

虚继承

  1. 首先虚继承会在对象的首地址新增一个虚函数指针,然后是自己新定义的变量,然后是一个完整的基类对象。
  2. 如果基类中有虚函数,那么虚继承将基类完整保存下来包括基类的虚函数指针,但是虚函数有自己的虚函数表空间,自己的虚函数指针会变化。如果子类重写了基类的虚函数表,那么也会更改自己虚函数表中对应的虚函数地址。

多继承

  1. 没有虚函数:对象内存分配按照继承的顺序分布,过程中涉及到字节对齐,自己的成员变量在对象内存结尾。如果没有虚继承,那么继承的基类中可能有重复对象。
  2. 有虚函数,每一个有虚函数的对象中多了一个虚函数指针,继承的基类中有虚析构函数,派生里对象的基类对象首部虚函数指针指向的虚函数表中也会有该继承类的虚析构函数,如果有多个这样的基类,派生类对象的首部包含多个虚函数指针,并且每个虚函数指针所指向的虚函数表中都会包含响应的虚析构函数。

菱形继承

菱形继承是多继承一种,派生类继承多个基类,多个基类又继承同一个基类,使用菱形继承对象空间就只存在一个上上基类,大大节约了内存空间。

内存分布:(一般来说) 1. 首先是多继承的上级基类的虚函数指针(有的话)以及非静态成员变量,上级基类按照继承顺序放在内存中,上基类存储完毕后就是自己的成员变量,最后是上上级被虚继承的完整的上上级基类。 2. 也就是说顶端的基类被完整存在派生类对象的尾部,中间的多个基类按照顺序放在首部,中间部分是派生类的自定义非静态成员变量。