请选择 进入手机版 | 继续访问电脑版

C++编程

 找回密码
 立即注册

QQ登录

只需一步,快速开始

楼主: 000

000山寨的笔记

[复制链接]

6

主题

62

帖子

206

积分

初软

Rank: 3Rank: 3

积分
206
 楼主| 发表于 2017-4-11 08:47:07 | 显示全部楼层
忘记了,附上结果:
00EFFA4C
00EFF8F4
Used
00FDD868
00FDD868
Used
0xdddddddd      2       3
0xdddddddd
Used
回复 支持 反对

使用道具 举报

6

主题

62

帖子

206

积分

初软

Rank: 3Rank: 3

积分
206
 楼主| 发表于 2017-4-11 09:26:52 | 显示全部楼层
继续吧(图片源于网络)
3)C++的函数编译原理(类部分)
C++的函数编译原理和C的有一点点不同,C和C++的函数编译原理是遵循一个函数调用惯例的
我们随便打开一个标准库头文件就可以看见类似的函数声明:
  1. _ACRTIMP char* __cdecl gets_s(
  2.             _Out_writes_z_(_Size) char*   _Buffer,
  3.             _In_                  rsize_t _Size
  4.             );
复制代码

函数调用惯例在函数声明和函数定义时都可以指定,语法格式为:
返回值类型  调用惯例  函数名(函数参数)
这个调用惯例一般包含了一下4个规定:
1.参数传递方式,主要是看是由寄存器传递还是栈传递
2.参数传递方式,看是由左到右入栈还是相反方式
3.函数参数出栈方式(使得栈在函数调用前后保持一致),是由被调用方出栈还是由调用方出栈
4.函数名修饰方式,每个修饰方式都用不同,例如C的通常只是在编译时加一个_在函数名前,而C++使用的是Name Mangling 算法

例子:
  1. #include<iostream>
  2. using namespace std;
  3. class MyClass
  4. {
  5. public:
  6.         MyClass();
  7.         ~MyClass();
  8.         void test();
  9. private:
  10.         int a;
  11.         double b;
  12.         char c;
  13. };

  14. MyClass::MyClass()
  15. {
  16. }

  17. MyClass::~MyClass()
  18. {
  19. }
  20. void MyClass::test()
  21. {
  22. }
  23. struct MyStruct
  24. {
  25.         MyStruct();
  26.         ~MyStruct();
  27.         void test();
  28.         int a;
  29.         double b;
  30.         char c;
  31. };
  32. MyStruct::MyStruct()
  33. {
  34. }
  35. MyStruct::~MyStruct()
  36. {
  37. }
  38. void MyStruct::test()
  39. {
  40. }
  41. int main()
  42. {
  43.         cout << sizeof(MyClass) << "     " << sizeof(MyStruct);
  44.         return 0;
  45. }
复制代码

结果:
24      24
由此我们可以知道,在结构体&类中,都遵循的是内存对齐并且只针对于成员变量来说的(类中不论是public或是private)


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复 支持 反对

使用道具 举报

6

主题

62

帖子

206

积分

初软

Rank: 3Rank: 3

积分
206
 楼主| 发表于 2017-4-11 09:32:44 | 显示全部楼层
接上面的编译原理,在类中,我们发现编译后其实函数的名称都变成了一个正常的函数,不存在类&名称空间(算法标记除外),那么类是如何被访问到的,这里其实就像this指针一样,在编译的时候会添加一个参数为...... *const ...(意思是指针类的const,指针不可指向其他地方)
回复 支持 反对

使用道具 举报

6

主题

62

帖子

206

积分

初软

Rank: 3Rank: 3

积分
206
 楼主| 发表于 2017-4-11 10:06:27 | 显示全部楼层
3.static在类里
static在类中,不论是成员变量还是成员函数都是分配在了内存中的全局数据区(链接为静态,不可跨编译域访问既extern)
要注意的是,static在类中多出来了一个private和public以及protected的限制,同时也不可以被构造函数直接初始化,其他的就和普通的静态变量相似,(为public的可以直接访问)
对于public的静态成员参数来说,他不会随函数的结束被析构函数释放,而是会一直存在(内存位置不同,为静态不会自动出栈)
对于static的成员函数来说,这个函数就只可以访问类内的static成员变量,并且没有普通成员函数的特性(和普通函数没有区别,遵从public等的条件下)即没有this指针等。
但是静态成员函数可以用过参数来访问到对象中的其他普通成员参数。
回复 支持 反对

使用道具 举报

6

主题

62

帖子

206

积分

初软

Rank: 3Rank: 3

积分
206
 楼主| 发表于 2017-4-12 10:31:53 | 显示全部楼层
本帖最后由 000 于 2017-4-12 22:02 编辑
000 发表于 2017-4-11 09:26
继续吧(图片源于网络)
3)C++的函数编译原理(类部分)
C++的函数编译原理和C的有一点点不同,C和C++的函 ...

这里关于Name Mangling 算法的一个例子:
  1. #include<iostream>
  2. using namespace std;
  3. class MyClass
  4. {
  5. public:
  6.         void print();
  7. protected:
  8.         int m_a;
  9.         char m_c;
  10. private:
  11.         int m_m;

  12. };
  13. namespace my
  14. {
  15.         void prin();
  16. }
  17. void prin();
  18. int main()
  19. {
  20.         MyClass a;
  21.         a.print();
  22.         my::prin();
  23.         prin();
  24.         return 0;
复制代码

报错信息:
1>源.obj : error LNK2019: 无法解析的外部符号 "public: void __thiscall MyClass::print(void)" (?print@MyClass@@QAEXXZ),该符号在函数 _main 中被引用
1>源.obj : error LNK2019: 无法解析的外部符号 "void __cdecl my::prin(void)" (?prin@my@@YAXXZ),该符号在函数 _main 中被引用
1>源.obj : error LNK2019: 无法解析的外部符号 "void __cdecl prin(void)" (?prin@@YAXXZ),该符号在函数 _main 中被引用
可知,编译阶段,所有函数都会被编译为普通函数,类的函数会自动加入一个参数(也就是this指针)

补充,上面的函数调用惯例出现了一个thiscall
(查了下Wiki有了以下结果和总结)
调用惯例出栈方参数传递名字修饰
cdecl函数调用方从右至左的顺序压参数入栈下划线+函数名
pascal函数本身从左至右的顺序入栈较为复杂,参见pascal文档
stdcall函数本身从右至左的顺序压参数入栈下划线+函数名+@+参数的字节数, 如函数 int func(int a, double b)的修饰名是 _func@12
fastcall函数本身头两个 DWORD(4字节)类型或者更少字节的参数 被放入寄存器,其他剩下的参数按从右至左的顺序入栈@+函数名+@+参数的字节数
thiscall不一定从右至左的顺序压参数入栈(有时会通过寄存器传递this指针)不详



再对调用管理做一个解释:
以下是调用者来完成被调用函数出栈的:(解堆栈的代码每次调用时都需要生成一遍以确定需要出栈字节数)
cdecl:
这个是在X86平台上C的一个调用标准了(用得非常普遍)
首先是实参由右往左入栈
函数名称在编译时只加一个_
函数退出时由被调用者清除栈
返回值过大时(大于64字节并且不是POD类型的值,满足条件的值一般都是由寄存器传递的)会在调用时为函数多申请一部分空间并将该控件地址当作第一个参数隐式传递给调用函数(GCC是不论任何大小都是如此)
syscall和cdecl差不多 syscall是32位OS/2 API的标准。
optlink和cdecl差不多optlink在IBM VisualAge编译器中被使用。
以下是被调用函数自身完成出栈的(此时在编译阶段就要求知道栈上有多少字节需要处理,所以不支持可变参)并且是在返回原函数前解栈的
pascal参数是从左到右入栈的
register(这个也是一个关键词,用于申请变量储存在寄存器上(并不是每次都成功,具体有计算机决定)这个关键词的变量不可寻址(寄存器无地址可寻))Borland fastcall的别名
Borland fastcall 从左到右传三个参数到:EAX, EDX和ECX中剩下的入栈。
stdcall 是Windows API的标准调用约定除了由被调用者清除栈之外和cdecl差不多,区别在于命名函数名后缀以符号"@",后跟传递的函数参数所占的栈空间的字节长度


两者皆可(被调用者清栈或是调用者,主要基于是否是可变参)
thiscall是调用C++非静态成员函数时使用此约定
GCC中和cdecl差不多(固定由调用者清除,区别在于this指针在最后入栈(解释了第一个隐藏参数为this同时也是第一个参数)
在微软的Visual C++中区别在于this指针是由寄存器传递的其余同cdecl
回复 支持 反对

使用道具 举报

6

主题

62

帖子

206

积分

初软

Rank: 3Rank: 3

积分
206
 楼主| 发表于 2017-4-14 11:27:54 | 显示全部楼层
4.继承
1)继承时的权限方式:
这里引出了一个新的权限标识符: protected
在类中protected实际上和private一样,区别仅仅就是在于发生继承时private的成员变量不可被派生类读取使用,而protected可以,并且在声明继承时,是按照一个上界来确定权限的,意思就是说按照权限大小应该是public>protected>private,如果 上界是protected的,并且有public的成员,此时这些成员在派生中都是protected的
2)名称遮蔽:
如果派生类内有成员同基类一样,则会发生名称遮蔽,此时如果要访问基类的被遮蔽的应该加上类的名字。之后是一个关于重载的“争议”,实质上这并不会发生重载现象,应为根据Name Mangling算法,两者在编译上并不会出现相同的名称(实质上在重载时名称也是不相同的)所以这个并没有说服力,但是更具说服力的是作用域概念(类的中括号也是一个作用域,发生继承时,基类的作用域比继承类的大(包含了子类))当调用继承类的函数时,会先在继承类作用域寻找相应声明,之后再上升到基类,所以由于作用域的不同(类似于全局变量和具有块作用域的变量不冲突),直接就不能称之为重载。
3)派生类的构造函数和析构函数:
在派生类中,基类难免有private的成员变量,此时继承类是无法直接对其进行初始化的,所以此时的编译器对这些特殊情况做了妥协,由继承类调用基类的构造函数来进行基类成员的初始化(必须这样,与法规定),如果是多层继承,那么构造函数还不可以跨基类来调用(除了虚基类)
析构函数则智能的多,只用调用派生类的即可,顺序是由当前继承类递归(暂时这么理解)调用上去,至于之前的内存分配问题,只要析构函数完善,这就不会有很大的问题。
4)多继承(面对对象语言中C++独占鳌头,没有卵用,不推荐使用的):
从字面意思上理解多继承就是一个派生类同时继承了两个及其以上的基类,实质上和普通继承没有多大的差别,只是在构造函数上要一次性调用所有基类的构造函数(否则就调用默认构造函数)
但是多继承麻烦就麻烦在会出现基类又同时继承了一个基类,此时就会发生冲突(给这个基类写入值得时候)应为编译器无法确定你是要通过哪一条路来访问这个成员,所以就出现了虚继承,就是在继承时添加一个virtual(在一定要用多继承时,一般不推荐使用)这时候虚继承就会对虚派生类的派生造成印象(构造函数时直接调用虚继承的构造函数,而不用有基类再调用)
如果在虚基类中和其虚派生类中出现了同名的成员,此时不会发生二义性(遮蔽)


最后附上一张作用域的图:

回复 支持 反对

使用道具 举报

6

主题

62

帖子

206

积分

初软

Rank: 3Rank: 3

积分
206
 楼主| 发表于 2017-4-14 11:35:47 | 显示全部楼层
吃个饭,理理思路,下午把这一块的内存梳理梳理(我觉得最有意思的地方)
回复 支持 反对

使用道具 举报

6

主题

62

帖子

206

积分

初软

Rank: 3Rank: 3

积分
206
 楼主| 发表于 2017-4-14 13:47:14 | 显示全部楼层
本帖最后由 000 于 2017-4-14 14:28 编辑

接下来是一系列关于类的内存的分布情况的例程和解释说明(虽然我知道这个在实际编程中几乎不可能被使用到,但是有助于对类的理解也就是“修内功”
):前面说过,对象的内存排布是:成员函数所在的是内存的代码段,成员变量是在堆栈上(静态成员在全局变量区,只分配一次空间初始化只可以用普通的方式(不是构造函数),之后所有的对象都可以对其进行修改)
此时我们对于对象来说,就有一种比较“蹩脚”的方法来直接对成员变量进行访问了:
  1. #include<iostream>
  2. using namespace std;
  3. class MyClass
  4. {
  5. public:
  6.         MyClass(int, int, int);
  7.         ~MyClass();

  8. private:
  9.         int m_a;
  10.         int m_b;
  11.         int m_c;
  12. };
  13. class MyClass1
  14. {
  15. public:
  16.         MyClass1(int, int, int);
  17.         ~MyClass1();

  18. private:
  19.         int m_c;
  20.         int m_b;
  21.         int m_a;

  22. };

  23. MyClass1::MyClass1(int a, int b, int c):m_a(a), m_b(b), m_c(c)
  24. {
  25. }

  26. MyClass1::~MyClass1()
  27. {
  28. }
  29. MyClass::MyClass(int a, int b, int c) :m_a(a), m_b(b), m_c(c)
  30. {
  31. }

  32. MyClass::~MyClass()
  33. {
  34. }
  35. int main()
  36. {
  37.         MyClass test(1, 2, 3);
  38.         cout << *(int *)((unsigned int)&test + sizeof(unsigned int) * 0) << endl;
  39.         MyClass1 *test1 = new MyClass1(1, 2, 3);
  40.         cout << *(int *)((unsigned int)test1 + sizeof(unsigned int) * 0) << endl;
  41.         delete test1;
  42.         return 0;
  43. }
复制代码

我们注意到:在类声明的时候成员顺序决定了内存的排布顺序

回复 支持 反对

使用道具 举报

6

主题

62

帖子

206

积分

初软

Rank: 3Rank: 3

积分
206
 楼主| 发表于 2017-4-14 14:36:28 | 显示全部楼层
本帖最后由 000 于 2017-4-14 14:41 编辑

之后知道了上面的方法我们也就对对象内存的模型有了大致了解:

(我电脑是小端序的)是按照这样子存储的
并且是有对齐现象的:
(我对上面的程序在MyClass1中加了一个long long 的成员)

此时在进行访问就要考虑到对齐因素了。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复 支持 反对

使用道具 举报

6

主题

62

帖子

206

积分

初软

Rank: 3Rank: 3

积分
206
 楼主| 发表于 2017-4-14 15:19:46 | 显示全部楼层
本帖最后由 000 于 2017-4-14 15:23 编辑

后面就到了继承:同上面的很相似,但是要注意顺序,首先是最低层的基类,之后逐个派生类,发生名称屏蔽也是同样的。
例程:
  1. #include<iostream>
  2. using namespace std;
  3. class MyClass
  4. {
  5. public:
  6.         MyClass(int, int, int);
  7.         ~MyClass();

  8. private:
  9.         int m_a;
  10.         int m_b;
  11.         int m_c;
  12. };
  13. class MyClass1:public MyClass
  14. {
  15. public:
  16.         MyClass1(int, int, int, long long);
  17.         ~MyClass1();

  18. private:
  19.         long long m_d;

  20. };

  21. MyClass1::MyClass1(int a, int b, int c,long long d):MyClass(a,b,c),m_d(d)
  22. {
  23. }

  24. MyClass1::~MyClass1()
  25. {
  26. }
  27. MyClass::MyClass(int a, int b, int c) :m_a(a), m_b(b), m_c(c)
  28. {
  29. }

  30. MyClass::~MyClass()
  31. {
  32. }
  33. int main()
  34. {
  35.         MyClass1 *test1 = new MyClass1(1, 2, 3, 4);
  36.         cout << *(int *)((unsigned int)test1 + sizeof(unsigned int) * 4) << endl;
  37.         delete test1;
  38.         return 0;
  39. }
复制代码
这里偏移4个int是应为考虑到了对齐

由此也可以得出,继承是派生类进行sizeof运算时也要考虑到基类和派生类之间的内存对齐原则。

(遮蔽时)将类修改为:
  1. class MyClass
  2. {
  3. public:
  4.         MyClass(int, int, int, long long);
  5.         ~MyClass();

  6. private:
  7.         int m_a;
  8.         int m_b;
  9.         int m_c;
  10.         long long m_d;
  11. };
  12. class MyClass1:public MyClass
  13. {
  14. public:
  15.         MyClass1(int, int, int, long long, long long);
  16.         ~MyClass1();

  17. private:
  18.         long long m_d;

  19. };
  20. }
  21. MyClass::MyClass(int a, int b, int c, long long d) :m_a(a), m_b(b), m_c(c), m_d(d)
  22. {
  23. }

  24. MyClass::~MyClass()
  25. {
  26. }
  27. int main()
  28. {
  29.         MyClass1 *test1 = new MyClass1(1, 2, 3, 4, 5);
  30.         cout << *(int *)((unsigned int)test1 + sizeof(unsigned int) * 4) << endl;
  31.         delete test1;
  32.         return 0;
  33. }
复制代码
此时结果显然就是5

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|C++编程  

GMT+8, 2019-7-22 19:39 , Processed in 0.156250 second(s), 22 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表