通信人家园

标题: [原创连载]大话C++对象模型(8月3日更新)  [查看完整版帖子] [打印本页]

时间:  2011-8-2 23:31
作者: calitrean     标题: [原创连载]大话C++对象模型(8月3日更新)

一.从struct开始说起
相信每一个学过C语言的同学都知道struct,用于定义用户自己的抽象数据类型

OK,下面来看一段代码:
  1. #include <iostream.h>
  2. struct A
  3. {
  4.     int a;
  5.     int b;
  6. };
  7. A a;
  8. int main()
  9. {
  10.     cout<<sizeof(a)<<"\n";   
  11.     return 0;
  12. }
复制代码
这是一段很普通的代码,定义了一个struct A类型的变量,然后将这个变量所占空间的大小给打印出来

细心的读者可能发现了这里的语法和以前学的纯C语言有点不太一样,在很多纯C编译器中,是不能这样定义的,要么在定义struct A类型的同时定义一个该类型的变量:
  1. struct A
  2. {
  3.     int a;
  4.     int b;
  5. }a;
复制代码
要么先把struct A定义为一个类型,再定义一个该类型的变量:
  1. typedef struct A
  2. {
  3.     int a;
  4.     int b;
  5. };

  6. A a;
复制代码
为什么C++里面就可以这样定义呢?因为C++编译器对C进行了扩展

下面来看看C++特有的东西:class
  1. class B
  2. {
  3.     int a;
  4.     int b;
  5. };
  6. B b;
  7. int main()
  8. {
  9.     cout<<sizeof(b)<<"\n";
  10.     return 0;
  11. }
复制代码
和前面一样,我们定义了class B和它的对象b,并且打印了这个对象所占空间的大小。


运行这两个程序,发现它们的运行结果是相同的:


呵呵,是不是感觉struct和class有些相似啊!但是您可能会问“class里可以定义函数,struct里也可以吗?”,答案当然是肯定的。

现在我们将代码修改一下:
  1. #include <iostream.h>
  2. struct A
  3. {
  4.     int a;
  5.     int b;
  6.     void f()
  7.     {
  8.     }
  9. };
  10. A a;
  11. class B
  12. {
  13.     int a;
  14.     int b;
  15.     void f()
  16.     {
  17.     }
  18. };
  19. B b;
  20. int main()
  21. {
  22.     cout<<"\n"<<sizeof(a)<<"\n";
  23.     cout<<"\n"<<sizeof(b)<<"\n";
  24.     return 0;
  25. }
复制代码
运行结果如下:


可以看出,struct里面也是可以定义函数的。那么,struct和class真的完全一样吗?答案是否定的

我们再将代码修改一下:
  1. #include <iostream.h>
  2. struct A
  3. {
  4.     int a;
  5.     int b;
  6.     void f()
  7.     {
  8.     }
  9. };
  10. A a;
  11. class B
  12. {
  13.     int a;
  14.     int b;
  15.     void f()
  16.     {
  17.     }
  18. };
  19. B b;
  20. int main()
  21. {
  22.     a.a = 1;
  23.     b.a = 2;
  24.     return 0;
  25. }
复制代码
运行代码,结果。。。无法通过编译,编译器报错:


原来出错点在这里:


看来struct和class还是有不同的!不同点就在于:
struct成员的默认访问权限是public的,而class成员的默认访问权限是private的!
可见,struct和class并没有本质区别,除了默认的成员访问权限不一样。

也许您学过JAVA,会发现在C++中定义class和在JAVA里不大一样,在C++里定义一个class后需要一个“;”,但是在JAVA中缺不需要。
这是因为C++是从C演化而来,这个“++”就是在C的基础上加了一些东西,一些面向对象的东西。同理,GPRS可以叫GSM++,HSPA可以叫3G++,呵呵
回到正题,因为C++是从C语言演化而来,是C语言的超集,因此保留了向前兼容的特性,因为C++中的class是从C语言中的struct演化而来,因此保留了struct定义时的格式:
  1. struct A{};
  2. class B{};
复制代码
在这点上,JAVA似乎要洒脱得多:
  1. public class A{}
复制代码
可以看出,在面向对象方面,JAVA比C++更彻底,因为JAVA自诞生之日起就是一门纯粹的面向对象的编程语言。

[ 本帖最后由 家园小助手 于 2011-12-20 14:30 编辑 ]
时间:  2011-8-3 01:44
作者: calitrean

二.成员对齐方式
来看看我们之前定义的struct:
  1. struct A
  2. {
  3.     int a;
  4.     int b;
  5. };
复制代码
假设我们用的是32位CPU,那么sizeof(A)的结果是什么?
嗨,那还不简单嘛,32位机器上int类型占4个字节,struct A里面有2个int型变量,那就是8个字节呗!

没错,在这个例子中,sizeof(A)的结果的确是8,上一节中的运行结果证明了这个结论。

下面,我们把代码修改一下:
  1. struct A
  2. {
  3.     int a;
  4.     short b;
  5. };
  6. int main()
  7. {
  8.     cout<<sizeof(short)<<"\n";
  9.     cout<<sizeof(A)<<"\n";
  10.     return 0;
  11. }
复制代码
运行结果如下:


呵呵,是不是感觉有点不对啊,int a占4个字节,short b占2个字节,struct A应该是4+2=6个字节啊,怎么变成8个了???

我们还是从CPU的内存地址对齐开始说起吧。。。

以32位CPU为例,在自然对齐方式下,基本数据类型(如short,int,double)变量的地址必须被他们的大小整除。这句话怎么理解呢?打个比方,对于int类型的变量,因为sizeof(int)等于4,因此存放int类型变量的起始地址必须能被4整除。同理,short类型变量的起始地址要能被2整除,对于char和bool类型的变量则没有特别要求。

回到我们的程序中,在struct A中,int a的起始地址应该能被4整除,short b的起始地址应该能被2整除。我们来看看下面这段程序的运行结果:
  1. struct A
  2. {
  3.     int a;
  4.     short b;
  5. };
  6. A a;
  7. int main()
  8. {
  9.     cout<<"\n"<<&a.a<<"\n";
  10.     cout<<"\n"<<&a.b<<"\n";
  11.     return 0;
  12. }
复制代码


可见,struct A中,int a的起始地址是0x0042E058,可以被4整除;short b的起始地址是0x0042E05C,可以被2整除。

但是这里有个问题,为什么sizeof(A)的值是8呢?这说明编译器在short b之后又自动填充了2个字节,因此sizeof(A)=sizeof(int a)+sizeof(short b)+2个填充字节。


编译器为什么要在short b的后面自动填充2个字节呢?当然是为了地址对齐。可是在这个例子中,即使不填充这2个字节,int a和short b的起始地址不也满足了CPU的对齐要求了吗?
是的,在单个struct A变量中,short b后面即使没有填充2个字节一样能满足CPU的地址对齐要求,但是如果我们定义一个struct A的数组呢,情况会怎样?

情景一:假设编译器没有在short b后面填充2个字节
我们定义一个struct A的数组:
  1. struct A
  2. {
  3.     int a;
  4.     short b;
  5. };
  6. A a[2];
复制代码
对应的内存地址如下:

可以看出,数组的第一个元素中的int a和short b都满足CPU的地址对齐要求,但是第二个元素中的int a显然不能满足CPU的地址对齐要求,因为0x0042E05E这个地址显然无法被4整除

情景二:假设编译器自动在short b后面填充2个字节

可以看出,第二个元素中的int a的地址是0x0042E060,显然该地址是能被4整除的

Oh,编译器自动填充那2个字节的目的就在于此!

这里有一个问题,那就是CPU为什么需要地址对齐呢?
在32位CPU中,数据总线是32位,一次可以存取4个字节的数据
现在,让我们重温一下前面的两个情景
情景一:假设编译器没有在short b后面填充2个字节

可以看出,对于A a[2]中第一个元素中的int a,CPU可以很方便地将其取出;对于之后的short b,CPU可以一次取出4个字节后丢弃高位的2个字节
但是对于A a[2]中第二个元素中的int a,CPU无法一次取出,须先去一次丢弃低位的2个字节再取一次丢弃高位的2个字节,然后将结果拼凑在一起

情景二:假设编译器自动在short b后面填充2个字节

可以看出,CPU只需一次就可以很方便地取出第二个元素中的int a

这就是以空间换取时间啊!

那么有没有办法能让CPU按照我自己的要求进行地址对齐呢?当然可以,利用#pragma pack宏就可以
比如,我想让CPU按照2个字节的方式对齐,则代码如下:
  1. #include <iostream.h>
  2. #pragma pack(push,2)
  3. struct A{
  4.     int a;
  5.     short b;
  6. };
  7. A a;
  8. int main()
  9. {        
  10.     cout<<"\n"<<sizeof(a)<<"\n";
  11.     cout<<"\n"<<&a.a<<"\n";
  12.     cout<<"\n"<<&a.b<<"\n";
  13.     return 0;
  14. }
复制代码
运行结果如下:


可以看出,在我们要求的2字节对齐方式下,sizeof(a)的结果是6,struct A只占了6个字节,一点也没有浪费

[ 本帖最后由 calitrean 于 2011-8-3 02:16 编辑 ]
时间:  2011-8-3 02:33
作者: calitrean

三.没有成员变量的struct
来看看下面这段代码:
  1. #include <iostream.h>
  2. struct A
  3. {
  4. };

  5. A a;
  6. int main()
  7. {
  8.     cout<<"\n"<<sizeof(a)<<"\n";
  9.     return 0;
  10. }
复制代码
运行结果如下:


似乎是有点不可思议,一个没有任何元素的struct居然占了1个字节的空间,这是为什么呢?
因为编译器自动的给它填充了一个字节
那么,为什么要填充这一个字节呢?
因为我们需要在内存中能找到变量A a。
如果一个变量不占任何空间,那它岂不是相当于不存在嘛!那不是变量,那是幽灵,至于您信不信,反正我是信了
所以,找一个字节必须要填充,那是A a存在的证据!

好了,我们再来看看另一端代码:
  1. #include <iostream.h>
  2. struct A
  3. {
  4.   int a[0];
  5. };

  6. int main()
  7. {
  8.     cout<<"\n"<<sizeof(A)<<"\n";
  9.     return 0;
  10. }
复制代码
运行结果如下:


这似乎也是一个空的struct,运行的结果不出意料。

但是如果struct中还有其他变量呢?比如:
  1. #include <iostream.h>
  2. struct A
  3. {
  4.   int a;
  5.   int b[0];
  6. };

  7. int main()
  8. {
  9.     cout<<"\n"<<sizeof(A)<<"\n";
  10.     return 0;
  11. }
复制代码
运行结果如下:

好像这次编译器没有在里面填充一个字节了,因为struct A里面有一个int a,因此struct A至少占了4个字节的空间,不会以幽灵的方式存在了,因此编译器就没有必要再往里面填充字节了。

但是,在struct A中定义一个int a[0]有意义吗?当然有
比如,某个家庭里有一个爸爸一个妈妈还有很多个孩子:
  1. struct Family
  2. {
  3.   int father;
  4.   int mother;
  5.   int children[0];
  6. };
复制代码
某个周末,这对父母要带孩子们出去玩,他们可以带一个孩子,也可以带2个孩子,还可以带N个孩子:
  1. int main()
  2. {
  3.     Family *p=(Family*)malloc(sizeof(Family)+sizeof(int)*N);
  4.     p->children[0] = LUCY;
  5.     p->children[1] = TOM;
  6.     ...
  7.     return 0;
  8. }
复制代码
有一点要注意,0元素数组在struct和class之外定义


编译器会报错:



因为编译器认为之所以使用0元素数组是为了对struct和class作一个长度不可预知的扩展。

[ 本帖最后由 calitrean 于 2011-8-3 03:20 编辑 ]
时间:  2011-8-3 08:29
作者: zxh315

ding
时间:  2011-8-3 08:38
作者: ctao

c难学吗?想问问
时间:  2011-8-3 10:23
作者: rillhu

写的很好啊,对于学C,又想学C++的很有指导意义。
时间:  2011-8-3 10:40
作者: pxl888     标题: 回复 2# 的帖子

非常好啊,希望LZ能连载啊!
时间:  2011-8-3 11:51
作者: 猪没本事

貌似又是个连载,占楼学习,这种帖得留名
时间:  2011-8-3 12:06
作者: 愤怒的小麻雀

呵呵,学习一下。喜欢这种类型的
时间:  2011-8-3 12:36
作者: 北回归线以北

好东西  可以看看
时间:  2011-8-3 13:20
作者: iamcatcher

呵呵 支持。不过这个论坛技术氛围不浓。
时间:  2011-8-3 13:58
作者: klarke

不错,以资鼓励
时间:  2011-8-3 13:59
作者: klarke

发帖随机事件
熬夜2周,软件调测工程被评优,省移动奖励 200 金钱,公司老总奖励 200 金钱,回家,为讨女友欢心,奖金全部上缴之外,还花去 2 金钱买礼物!
时间:  2011-8-3 14:21
作者: 923450

还没学过C++,学习一下
时间:  2011-8-3 16:47
作者: bellinwater

终于看到楼主开贴了
时间:  2011-8-3 20:28
作者: yonka

有意思~
东西比较基础~但是很耐人寻味~
时间:  2011-8-3 22:15
作者: xiaoshan429

赶上直播了?留名。楼主加油
时间:  2011-8-4 08:47
作者: 13527598856

试试
时间:  2011-8-4 13:16
作者: tjhwa

透彻
时间:  2011-8-4 14:13
作者: mingming1105

mark
时间:  2011-8-4 20:17
作者: irg723

看了此贴,一些原来模糊的概念清晰了,希望继续下去!
时间:  2011-8-6 13:19
作者: xueshanfeiyign

风姐姐该给我们讲讲微电子。
时间:  2011-8-7 15:18
作者: tbq2007

谢谢楼主,好人一生平安。
时间:  2011-8-7 21:58
作者: AmazonBoy

楼主也应该加个#include <malloc.h>吧。。。
时间:  2011-8-7 23:25
作者: loey

赶上直播帖了,对于只学过c,看c++的还有点恼火,不过希望lz继续连载,我要天天捧场
时间:  2011-8-8 09:48
作者: qyf404

楼主分析的很好。至于你们信不信,反正我信了。


时间:  2011-8-9 07:53
作者: yinhexitaiyang

又来一个好贴,谢谢lz. 学习!
时间:  2011-8-9 10:59
作者: irg723

这更新速度有点慢啊!
时间:  2011-8-9 16:22
作者: bossop     标题: 你是干什么的,刚开始学C++吗

我们有的谈
时间:  2011-8-10 16:02
作者: matrowang

谢谢分享。
时间:  2011-8-15 14:04
作者: ironman2006

赶紧继续连载呀
时间:  2011-8-22 11:23
作者: bird1015

怎么没有继续更新了呢?
时间:  2011-8-25 20:43
作者: 663799675     标题: 阿斯顿撒旦

实打实大队的
时间:  2011-8-27 22:43
作者: songchy008


时间:  2011-8-31 19:11
作者: 好男人我是

顶后再看,楼主威武。
时间:  2011-8-31 21:27
作者: 涂国强

好东西!学习的好资料
时间:  2011-9-2 10:51
作者: yadodo

很好,加油啊
时间:  2011-9-3 23:04
作者: mt8870     标题: 顶楼主,期待连载

顶楼主,期待连载
时间:  2011-9-9 19:10
作者: woi071031

楼主强大!
时间:  2011-9-9 19:46
作者: 二进制

挺感兴趣
时间:  2011-9-10 23:46
作者: ylibill

C++的咋在这开搞了?
时间:  2011-9-25 12:51
作者: z7xiaoh

很好,很强大
时间:  2011-10-8 20:22
作者: nich2009

赞,分析的很不错
时间:  2011-10-9 13:42
作者: MichaelShang

对齐那里讲得很透彻,支持楼主!!!
时间:  2011-10-11 15:56
作者: loilih

C++都忘光了 struct 是结构体吧
时间:  2011-10-17 21:16
作者: lovesky126

还没学过C++,学习一下
时间:  2011-10-18 15:53
作者: Timberlalalala

很好啊,mark一下
时间:  2011-10-18 19:04
作者: xddsp

写得很好,支持楼主继续写下去
时间:  2011-11-2 14:39
作者: raowenlin


时间:  2011-11-3 23:46
作者: leah2012

这个东西整理出来应该不错哦  多讲讲  受教了
时间:  2011-12-21 12:35
作者: luo2627

长叹一声~
时间:  2012-3-9 20:06
作者: chuqingwang

为什么不连载了呢?
时间:  2012-11-29 14:57
作者: wing_in_sky

mark......
时间:  2014-12-3 23:47
作者: Dawn_zg

手机看不了代码,郁闷




通信人家园 (https://www.txrjy.com/) Powered by C114