非交互式网站可以做商城吗,长春网站建设中心,做网站被网监叫去很多次,乐清在线网来源#xff1a;http://dsqiu.iteye.com/blog/1669614 之前一直对C内部的原理的完全空白#xff0c;然后找到《Inside The C Object Model》这本书看了下#xff0c; 感觉收获很大#xff0c;因为书写得比较早#xff0c;有些知识应该要更新#xff0c;但是还是值得好好研…来源http://dsqiu.iteye.com/blog/1669614 之前一直对C内部的原理的完全空白然后找到《Inside The C Object Model》这本书看了下 感觉收获很大因为书写得比较早有些知识应该要更新但是还是值得好好研读由于该书的内容给人比较散的感觉所以一直想找个时间整理一下遂成此文虽然都是抄书上的但是却让我有了温故而知新的觉悟附近里有三个好资料一并共享了2012年9月2日凌晨 4:31 谢谢 张雨生的歌声的相伴 《深度探索C对象模型Inside The C Object Model 》学习笔记 第一章 关于对象 使用class封装之后的布局成本 class并没有增加成本data members直接内含在每一个class object之中就像C struct一样。而member functions虽然被包含在class的声明之内但是不出现在Object之中。每一个non-inline function 只会产生一个函数实体。至于inline function则会在每一个调用使用的地方产生一个函数实体在调用点展开函数体。 class在布局以及存取时间上主要的额外负担是由virtual 引起包括 virtual function 机制 用以支持一个有效率的“执行期绑定runtime binding)”。 virtual base class 用以实现“多次出现在继承体系中的 base class ,有一个单一的被共享的实体”。 当然还有一些剁成继承下的额外负担发生在“一个 derived class 和其第二或后继之 base class 的转换”之间。 C 对象模型 在C对象模型中nonstatic data members 被放置在每一个class object之内static data members 则被存放在所以class object 之外。static和nonstaitc function也被放在所有 class object 之外。virtual functions 则以两个步骤支持之 1.每一个 class 产生出一堆指向 virtual functions 的指针放在表格之中这个表格被称为 virtual table(vtbl). 2.每一个 class object被添加一个指针指向相关的virtual table 。通常这个指针被称为 vptr vptr的设定和重置都由每一个 class 的 constructor 、destructor 和 copy assignment 运算符自动完成。 C 以下列方法支持多态 1.经由一组隐含的转化操作。例如把一个 derived class 指针转换为一个指向其 public base type的指针。 2.经由 virtual function 机制。 3.经由dynamic_cast和typied运算符。 class object 需要多少内存 1.其 nonstatic data members的总和大小。 2.加上任何犹豫alignment的需求和padding填补上去的空间。 3.加上为了支持 virtual 而由内部产生的任何额外负担。 指针类型告诉编译器如何解释某个特定地址中的内存内容及其大小例如一个string是传统的8 bytes包括一个 4byte的字符指针和一个用来表示字符串长度的整数。转型cast)其实是一个编译指令大部分不会改变一个指针所含有的真正地址它只影响“被指出值内存的大小和其内容”的解释方式。 第二章 构造函数语意学 2.1 Default Constructor 当编译器需要的时候default constructor会被合成出来只执行编译器所需要的任务将members适当初始化。 带有 Default Constructor 的Member Class Object 编译器的出来是如果一个class A 内含一个或者一个以上 member class objects 那么class A 的每一个 constructor 必须调用每一个member classes 的default constructor 。编译器会扩张已存在的constructors,在其中安插一些代码使得 user code在被执行之前先调用调用顺序一member objects在class 的声明次序一致必要的 default constructors。 带有 Default Constructor 的 Base class 编译器会在 Member Class Object 的default constructor 被插入调用之前调用调用次序根据他们的声明次序所有 base class constructor 的default constructor 。 带有一个 Virtual Function 的Class 下面两种情况同样需要合成default constructor 1.class 声明或继承一个 virtual function。 2.class派生自一个继承串链其中一个或者更多的 virtual base class。 扩展constructor操作会在编译期间发生 1.一个virtual function table 会被编译器产生出来内放class 的virtual functions 的地址。 2.在每一个 class object 中一个额外的pointer membervptr)会被编译器合成出来内含相关的class vtbl的地址。 带有一个 Virtual Base Class 的class Virtual base class的实现法在不同编译器之间有很大差异然而每一个实现的共同点在于必须使 virtual base class 在其每一个 derived class object中的位置能够在执行期准备妥当。对于class所定义的每一个constructor 编译器都会安插那些“允许每一个virtual base class 的执行期存取操作”的码。 总结 以上四种情况会导致“编译器必须为未声明constructor 的class 合成一个default constructor ”这只是编译器而非程序的需要。它之所以能够完成任务是借着“调用member object 或base class的default constructor ”或是“为每一个object初始化其 virtual function 机制或virtual base class 机制”完成。至于没有存在这四种情况而又没有生命constructor的class 实际上是不会被合成出来的。 在合成的default constructor 中只有base class subobjects(子对象和member class objects会被初始化。所有其他的nonstatic data member 如整数整数指针整数数组等是不会被初始化的这些初始化操作对程序是必须的但对编译器则并非需要的。 C新手一般有两个误解 1.任何class 如果没有定义default constructor 就会被合成出来一个。 2.编译器合成出来的default constructor 会明确设定 class 内每一个data member的默认值。 2.2 Copy Constructor 有三种情况会以一个object的内容作为另一class object的初值。 1.最明显的当然是对一个object做明确的初始化操作。 2.当object被当做参数交给某个函数 3.当函数返回一个class object。 这三种情况需要有 copy constructor。 Default Memberwise Initialization 如果class 没有提供一个 explicit copy constructor时当class object以“相同的另一个object作为初值是其内部是以所谓的default memberwise initialization方式完成的。也就是把每一个内建的或派生的 data member例如一个数组或指针的值从某个object拷贝一份到另一个object上但不拷贝其具体内容。例如只拷贝指针地址不拷贝一份新的指针指向的对象这也就是浅拷贝不过它并不会拷贝其中member class object而是以递归的方式实行memberwise initialization。 这种递归的memberwise initialization是如何实现的呢 答案就是Bitwise Copy Semantics和default copy constructor。如果class展现了Bitwise Copy Semantics则使用bitwise copybitwise copy semantics编译器生成的伪代码是memcpy函数否则编译器会生成default copy constructor。 那什么情况下class不展现Bitwise Copy Semantics呢有四种情况 1.当class内含有一个member class object而这个member class 内有一个默认的copy 构造函数[不论是class设计者明确声明或者被编译器合成] 2.当class 继承自 一个base class而base class 有copy构造函数[不论是class设计者明确声明或者被编译器合成] 3.当一个类声明了一个或多个virtual 函数 4.当class派生自一个继承串链其中一个或者多个virtual base class 下面我们来理解这四种情况为什么不能使用bitwise copy以及编译器生成的copy constructor都干了些什么。 在前2种情况下编译器必须将member或者base class的“ copy constructor的调用操作”安插到被合成的copy constructor中。 重新设定Virtual Table 的指针 第3种情况下因为class 包含virtual function 编译时需要做扩张操作 1.增加virtual function table内含有一个有作用的virtual function的地址 2.创建一个指向virtual function table的指针安插在class object内。 所以编译器对于每一个新产生的class object的vptr都必须被正确地赋值否则将跑去执行其他对象的function了其后果是很严重的。因此编译器导入一个vptr到class之中时该class 就不在展现bitwise semantics必须合成copy Constructor并将vptr适当地初始化。 处理Virtual Base Class Subobject virtual base class的存在需要特别处理。一个class object 如果以另一个 virtual base class subobject那么也会使“bitwise copy semantics”失效。 每一个编译器对于虚拟继承的支持承诺都是表示必须让“derived class object 中的virtual base class subobject 位置”在执行期就准备妥当维护“位置的完整性”是编译器的责任。Bitwise copy semantics 可能会破坏这个位置所以编译器必须自己合成出copy constructor。 这也就是说拷贝构造函数和默认构造器一样需要的时候会进行构建而并非程序员不写编译器就帮着构建。 2.4 初始化列表 下面四种情况必须使用初始化列表来初始化class 的成员 1.当初始化一个reference member时 2.当初始化一个const member时 3.当调用一个base class 的 constructor 而它拥有一组参数其实就是自定义的构造函数时 4.当调用一个 member class 的 constructor而它拥有一组参数时。 不过初始化的顺序是class members声明次序决定的不是由初始化列表决定的。 第三章 Data 语意学 3.2 Data Member 的布局 nonstatic data members 在class object中的排列顺序将和其声明的顺序一样的。但C standard 允许编译器将多个access sections之中的data members自由排列不必在乎他们的出现在class中的声明顺序。 3.3 Data Member 的存取 每一个member 的存取许可private public protected以及与class的关联并不会导致任何空间上或执行时间上的额外负担——不论是在个别的class objects 或是在static data member 本身。 static data members 被视为global变量只有一个实体存放在程序的data segment之中每次取static member 就会被内部转化为对该唯一的extern 实体的直接参考操作。若取一个static data member的地址会得到一个数据类型的指针而不是只想起class member的指针。 nonstatic data members 欲对一个nonstatic data member 进行存取操作编译器需要吧class object的起始地址加上data member的偏移量在编译事情就可以获知。 3.4 继承与Data Member 只要继承不要多态 这种情况并不会增加空间或存储时间上的额外负担。这种情况base class和derived class的objects都是从相同的地址开始其差异只在于derived object 比较大用以容纳自建的nonstatic data members把一个derived class object指定给base class 的指针或引用并不需要编译器去调停或修改地址它很滋润的可以发生而且提供了最佳执行效率。 加上多态 这种情况会带来空间和存取时间的额外负担 1.导入一个和virtual table 用来存储它所声明的每一个virtual functions的地址。 2.在每一个class object中导入一个vptr,提供执行期的链接使每一个object能够找到相应的virtual table。 3.加强constructor使它能够为vptr设定初始值让它指向class 所对应的virtual table 。 4.加强destructor使它能够消抹“指向class 相关virtual table”的vptr。 多重继承 对于一个多重派生对象将其地址指定给“最左端第一个base class的指针”情况和单一继承时相同因为二者都指向了相同的起始地址至于第二个或后面的base class 的地址指定操作则需要将地址修改过加上或减去如果是downcast介于中间的base class subobject(s)的大小。 如果要存取第二个或后面的base class 中的一个data member 不需要付出额外的成本因为members的位置在编译时就固定了因此存取member只是一个简单的offset的运算。 虚拟继承 class如果含有一个或多个virtual base class subobjects将被分割为两部分一个不变局部和一个共享局部。不变局部中的数据总是能有固定的offset这部分可以被直接存取至于共享部分所表现的就是virtual base class subobject 这个部分数据其位置因为每次派生操作而有变化所以只能间接存取。 如果没有virtual functions的情况下它们和C struct完全一样。 第四章 Function 语意学 4.1 Member的各种调用方式 Nonstatic Member Functions 实际上编译器是将member function被内化为nonmember的形式经过下面转化步骤 1.给函数添加额外参数——this。 2.将对每一个nonstaitc data member的存取操作改为this指针来存取。 3.将member function 重写成一个外部函数。对函数名精选mangling 处理使之成为独一无二的语汇。 Virtual Member Functions 将 ptr-f(); //f()为virtual member function 内部转化为 *ptr-vptr[1](ptr); 其中 vptr表示编译器产生的指针指向virtual table。它被安插在每一个声明有或继承自一个或多个virtual functions 的class object 中。 1 是virtual table slot的索引值关联到normalize()函数。 第二个ptr表示this指针。 Static Member Functions 不能被声明为 const volatile 或virtual。 一个static member function 会提出于class声明之外并给予一个经过mangling的适当名称。如果取一个static member function 的地址获得的是其在内存的位置也就是地址而不是一个指向“class member function”的指针如下 Point::count(); 会得到一个数值类型是 unsigned int(*)(); 而不是 unsigned int(Point::*)(); 4.2 Virtual Member Funcitons C中多态表示以“一个public base class 的指针或reference)寻址出一个derived class object”。 每一个class 只会有一个virtual table每一个table 含有对应的class object中所有active virtual functions 函数实体地址。这些active virtual function 包括 1.这个class 所定义的函数实体改写overriding)一个可能存在的base class virtual function函数实体。 2.继承自base class 的函数实体不被derived class改写 3.一个pure_virtual_called()。 一个类继承函数virtual table的三种可能性 1.继承base class 所声明的virtual functions的函数实体。正确地说是该函数实体的地址会被拷贝到derived class的virtual table相对应的slot之中。 2.使用自己的函数实体。这表示它自己的函数实体地址必须放在对应的slot之中。 3.可以加入一个新的virtual function。这时候virtual table 的尺寸会增大一个slot放进这个函数实体地址。 编译时期设定virtual function的调用 一般而言我并不知道ptr 所指对象的真正类型。然而可以经由ptr 可以存取到该对象的virtual table。 虽然我不知道哪个Z()函数实体被调用但知道每一个Z()函数地址都被放置slot 4的索引。 这样我们就可以将 ptr-z(); 转化为*ptr-vptr[4])(ptr); 唯一一个在执行期才能知道的东西是slot4所指的到底是哪一个z()函数实体。 多重继承下的 Virtual Functions 在多重继承中支持virtual functions其复杂度围绕在第二个及其后面的base class 上以及“必须在执行期调整this 指针”这一点。一般规则是经由指向“第二或后继base class 的指针”来调用derived class virtual function。调用操作连带的“必要的this指针调整”操作必须在执行期完成。 虚拟继承下的 Virtual Functions 4.3 函数的效能 nonmemeber、static member或nonstatic member函数都被转换为完全相同形式所以三者效率完全相同。 4.4 指向Member Function的指针 取一个nonstatic data member的地址得到的结果是该member在class 布局中的bytes位置所以它需要绑定于某个class object的地址上才能够被存取。 取一个nonstatic member function的地址如果该函数是nonvirtual则得到的是内存的真正地址然后这个值也是不完全的也需要绑定于某个class object的地址上才能够调用函数。 支持“指向Virtual Member Function”之指针 对于一个virtual function其地址在编译时期是未知的所能知道的仅是virtual function在其相关之virtual table的索引值也就是说对于一个virtual member function 取其地址所能获得的只是一个索引值。 4.5 Inline Funcitons 形参 传入参数直接替换 传入常量连替换都省了直接变成常量 传入函数运行结果则需要导入临时变量 局部变量 局部变量会被mangling以便inline函数被替换后名字唯一 也就是说一次性调用N次就会出现N个临时变量……程序的体积会暴增 第五章 构造、解构、拷贝 语意学 继承体系下的对象构造 constructor的调用伴随了哪些步骤 1.初始化列表member initialization list)的data members初始化操作会被放进constructor的函数本身并以membs的声明顺序为顺序。 2.如果有一个member并没有在初始化列表中但它在一个default constructor那么该default constructor 必须被调用手动。 3.在那之前如果class object有virtual table pointer(s)它们必须被设定初始值指定适当的virtual table(s)。 4.在那之前所有上一层的base class constructors 必须被调用以base class 的声明顺序为顺序与初始化列表的顺序没有关联。 如果base class 被列于初始化列表中那么任何明确指定参数都应该传递过去。 如果base class 没有列于初始化列表那么调用default constructor。 如果base class 是多重继承下的第二或后面的base class 那么this指针必须有所调整。 5.在那之前所有 virtual base class constructors 必须被调用从左到右从最深到最浅。 如果class 被列于初始化列表中那么如果有任何明确指定的参数都应该传递过去若没有列于初始化列表中则调用default constructor。 此外class中的每一个virtual base class subobject的偏移量必须在执行期可存取。 如果class object 是最底层的class某constructors可能被调用某些用以支持这个行为的机制必须被放进来。 对象复制语意学 当设计一个class并以一个class object 指定另一个class object时有三种选择 1.什么都不做实施默认行为。 2.提供一个explicit copy assignment operator。 3.明确拒绝一个class object指定给另一个class object。 一个class对于默认的copy assignment operator在以下情况下不会表现出 bitwise copy语意 1.当一个class的base class 有一个copy assignment operator时 2.当一个class 的member object而其class 有一个copy assignment operator时 3.当一个class 声明了任何virtual functions时 4.当class继承一个virtual base class 时。 vptr语意学 vptr在constructor何时被初始化在base class constructors调用操作之后但是在程序员供应的码或是初始化列表中所列的members初始化操作之前。 解构语意学 destructor被扩展的方式 1.destructor的函数本身首先被执行。 2.如果class拥有member class objects而后拥有destructor那么它们会以声明顺序的相反顺序被调用。 3.如果object内带一个vptr则现在被重新设定指向适当的base class virtual table。 4.如果有任何直接的上一层nonvirtual base classes 拥有destructor 它们会以声明顺序相反顺序调用。 5.如果有任何virtual base classes 拥有destructor而当前讨论的这个class 是最尾端的class那么它们会以其原来顺序相反顺序被调用。 第六章 执行期语意学 第七章 …… 补充类型向上转型和多态的混淆 构造这样的一个继承体系 class Base { public: virtual ~Base() {} virtual void show() { cout Base endl; } }; class Derived : public Base { public: void show() { cout Derived endl; } }; 子类Derived类重写了基类Base中的show方法。 编写下面的测试代码 Base b; Derived d; b.show(); d.show(); 结果是 Base Derived Base的对象调用了Base的方法而Derived的对象调用了Derived的方法。因为直接用对象来调用成员函数时不会开启多态机制故编译器直接根据b和d各自的类型就可以确定调用哪个show函数了也就是在这两句调用中编译器为它们每一个都确定了一个唯一的入口地址。这实际上类似于一个重载多态虽然这两个show函数拥有不同的作用域。 那这样呢 Base b; Derived d; b.show(); b d; b.show(); 现在一个Base的对象被赋值为子类Derived的对象。 那这样呢 Base b; Derived d; b.show(); b d; b.show(); 现在一个Base的对象被赋值为子类Derived的对象。 结果是 Base Base 对于熟悉Java的人而言这不可理解。但实际上C不是Java它更像C。“b d”的意思并不是Java中的“让一个指向Base类的引用指向它的子类对象”而是“把Base类的子类对象中的Base子对象分割出来赋值给b”。所以只要b的类型始终是Base那么b.show()调用的永远都是Base类中的show函数换句话说编译器总是把Base中的那个show函数的入口地址作为b.show()的入口地址。这根本就没用上多态。 单继承下的重写多态 那我们再这样 Base b; Derived d; Base *p b; p-show(); p d; p-show(); 这时结果就对了 Base Derived p是一个指向基类对象的指针第一次它指向一个Base对象p-show()调用了Base类的show函数而第二次它指向了一个Derived对象p-show()调用了Derived类的show函数。 总结也就是说只有是指针或者引用才是真正的多态将子对象赋给父类对象其实类型向上转型…… 个人觉得C容易弄混淆的地方持续更新 1.const和指针的修饰问题 const char * a; //一个指针a指向const char char const *a; //这两个是a指向的内容是常量不能改变 char * const a; //首先a 是指针然后还是const const (char*) a; //这两个是a指针本身是常量指针本身不能改变 其实可以看出如果const修饰的char(也就是类型本身或者是 *variable对指针的解引用就是指针指向的内容是常量反之就是修饰指针本身的。那我们可以总结一个识别方法就是看const 两边当然有的只有一边的类型是类型指针指向的内容就是类型变量本身是常量如const char * a和char const *a 的const两边是char*a。 当然两者都是常量就是const char * const a;第一个const是类型常量第二个才是指针常量。同样给出 const char a ;const char *a;在传递参数时使用。 2.数组和指针的组合问题 char * a[M]; 这是指针数组就是每一个元素是指针的数组每个元素都要初始化。a[M]一看就是数组这个数组每一个元素是char *所以可以将char *扩展为一维数组然后a[M]就是二维数组了。其实就是M个指针。 char (*a)[N]; 这是一个指针这个指针指向N个char元素即指向数组的指针其实就是一个指针。把*a)看着一个变量这个变量是指向N个元素的指针所以只是一个一维数组。把char (*a)[N]看成是char b[N]就可以了。 同理也可以用修饰的道理来区分可以自行体会。具体二维数组的动态分配的更多精彩可以查阅我的另一个博客http://dsqiu.iteye.com/blog/1683142 3.C变量的初始化 对于内置类型局部变量不进行初始化但是分配地址全局变量会进行默认初始化。对于类类型局部变量没有显式初始化会进行默认初始化有默认构造函数否则报错但其内部的内置数据成员不会进行初始化如果在默认构造函数没有进行初始化。数组也是同样。