一个sizeof问题

时间:2008-05-12 13:50:04   来源:论坛整理  作者:  编辑:chinaitzhe
我对下面这段程序的执行结果有些疑问,为什么A的大小不是12,stru_type的大小不是20?
#include <iostream>
using namespace std;

struct stru_type
{
int a;
double d;
char *pch;
stru_type *stru;
};

struct A{
double b;
int a;
};
int main(){
cout < <sizeof(A) < <endl; //16 为什么不是4 8=12?
cout < <sizeof(stru_type) < <endl;//24 为什么不是4 8 4 4=20?
return 0;
}

网友回复:内存对齐
网友回复:7. 结构体的sizeof
这是初学者问得最多的一个问题,所以这里有必要多费点笔墨。让我们先看一个结构体:
struct S1
{
char c;
int i;
};
问sizeof(s1)等于多少?
聪明的你开始思考了,char占1个字节,int占4个字节,那么加起来就应该是5。
是这样吗?
你在你机器上试过了吗?
也许你是对的,但很可能你是错的!
VC6中按默认设置得到的结果为8。
Why?为什么受伤的总是我?
请不要沮丧,我们来好好琢磨一下sizeof的定义 —— sizeof的结果等于对象或者类型所占的内存字节数。好吧,那就让我们来看看S1的内存分配情况:
S1 s1 = { 'a', 0xFFFFFFFF };
定义上面的变量后,加上断点,运行程序,观察s1所在的内存,你发现了什么?
以我的VC6.0为例,s1的地址为0x0012FF78,其数据内容如下:
0012FF78: 61 CC CC CC FF FF FF FF
发现了什么?怎么中间夹杂了3个字节的CC?
看看MSDN上的说明:
When applied to a structure type or variable, sizeof returns the actual size, which may include padding bytes inserted for alignment.
原来如此,这就是传说中的字节对齐啊!一个重要的话题出现了。
为什么需要字节对齐?
计算机组成原理教导我们,这样有助于加快计算机的取数速度,否则就得多花指令周期了。
为此,编译器默认会对结构体进行处理(实际上其它地方的数据变量也是如此),让宽度为2的基本数据类型(short等)都位于能被2整除的地址上,让宽度为4的基本数据类型(int等)都位于能被4整除的地址上。以此类推,这样,两个数中间就可能需要加入填充字节,所以整个结构体的sizeof值就增长了。
让我们交换一下S1中char与int的位置:
struct S2
{
int i;
char c;
};
看看sizeof(S2)的结果为多少?怎么还是8。
再看看内存,原来成员c后面仍然有3个填充字节。
这又是为什么啊?别着急,下面总结规律。
字节对齐的细节和编译器实现相关,但一般而言,满足三个准则:
1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。
对于上面的准则,有几点需要说明:
1) 前面不是说结构体成员的地址是其大小的整数倍,怎么又说到偏移量了呢?
因为有了第1点存在,所以我们就可以只考虑成员的偏移量,这样思考起来简单。想想为什么。
结构体某个成员相对于结构体首地址的偏移量可以通过宏offsetof()来获得,这个宏也在stddef.h中定义,如下:
#define offsetof(s,m) (size_t)&(((s *)0)->m)
例如,想要获得S2中c的偏移量,方法为
size_t pos = offsetof(S2, c);// pos等于4
2) 基本类型是指前面提到的像char、short、int、float、double这样的内置数据类型。这里所说的“数据宽度”就是指其sizeof的大小。由于结构体的成员可以是复合类型,比如另外一个结构体,所以在寻找最宽基本类型成员时,应当包括复合类型成员的子成员,而不是把复合成员看成是一个整体。但在确定复合类型成员的偏移位置时则是将复合类型作为整体看待。
这里叙述起来有点拗口,思考起来也有点挠头,还是让我们看看例子吧(具体数值仍以VC6为例,以后不再说明):
struct S3
{
char c1;
S1 s;
char c2;
};
S1的最宽简单成员的类型为int,S3在考虑最宽简单类型成员时是将S1“打散”看的,所以S3的最宽简单类型为int。这样,通过S3定义的变量,其存储空间首地址需要被4整除,整个sizeof(S3)的值也应该被4整除。
c1的偏移量为0,s的偏移量呢?这时s是一个整体,它作为结构体变量也满足前面三个准则,所以其大小为8,偏移量为4,c1与s之间便需要3个填充字节,而c2与s之间就不需要了,所以c2的偏移量为12,算上c2的大小为13,13是不能被4整除的,这样末尾还得补上3个填充字节。最后得到sizeof(S3)的值为16。
通过上面的叙述,我们可以得到一个公式:
结构体的大小等于最后一个成员的偏移量加上其大小再加上末尾的填充字节数目,即:
sizeof( struct ) = offsetof( last item ) sizeof( last item ) sizeof( trailing padding )

8.类的sizeof

类的sizeof值等于类中成员变量所占用的内存字节数。如:
****************************************************************

class A
{
public:
int b;
float c;
char d;
};
int main(void)
{
A object;
cout < < "sizeof(object) is " < < sizeof(object) < < endl;
return 0 ;
}

***************************************************************


输出结果为12(我的机器上sizeof(float)值为4,字节对其前面已经讲过)。

不过需要注重的是,假如类中存在静态成员变量,结果又会是什么样子呢?

***************************************************************
class A
{
public:
static int a;
int b;
float c;
char d;
};

int main()
{
A object;
cout < < "sizeof(object) is " < < sizeof(object) < < endl;
return 0 ;
}

**************************************************************


16?不对。结果仍然是12.
因为在程序编译期间,就已经为static变量在静态存储区域分配了内存空间,并且这块内存在程序的整个运行期间都存在。
而每次声明了类A的一个对象的时候,为该对象在堆上,根据对象的大小分配内存。

假如类A中包含成员函数,那么又会是怎样的情况呢?看下面的例子

*************************************************************
class A
{
public:
static int a;
int b;
float c;
char d;
int add(int x,int y)
{
return x y;
}
};

int main()
{
A object;
cout < < "sizeof(object) is " < < sizeof(object) < < endl;
b = object.add(3,4);
cout < < "sizeof(object) is " < < sizeof(object) < < endl;
return 0 ;
}

***************************************************************

结果仍为12。

因为只有非静态类成员变量在新生成一个object的时候才需要自己的副本。
所以每个非静态成员变量在生成新object需要内存,而function是不需要的。


注:C 中的多态和虚继续也是非常重要的东西,不过比较复杂,编译器不同,细节也有所不同。
////////////////////////////////////////////////////////////////////////////////////////////////////////
朋友帖了如下一段代码:
  #pragma pack(4)
  class TestB
  {
  public:
    int aa;
    char a;
    short b;
    char c;
  };
  int nSize = sizeof(TestB);
  这里nSize结果为12,在预料之中。
  现在去掉第一个成员变量为如下代码:
  #pragma pack(4)
  class TestC
  {
  public:
    char a;
    short b;
    char c;
  };
  int nSize = sizeof(TestC);
  按照正常的填充方式nSize的结果应该是8,为什么结果显示nSize为6呢?
事实上,很多人对#pragma pack的理解是错误的。
#pragma pack规定的对齐长度,实际使用的规则是:
结构,联合,或者类的数据成员,第一个放在偏移为0的地方,以后每个数据成员的对齐,
按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
也就是说,当#pragma pack的值等于或超过所有数据成员长度的时候,这个值的大小将不
产生任何效果。
而结构整体的对齐,则按照结构体中最大的数据成员 和 #pragma pack指定值 之间,较小
的那个进行。
具体解释
#pragma pack(4)
  class TestB
  {
  public:
    int aa; //第一个成员,放在[0,3]偏移的位置,
    char a; //第二个成员,自身长为1,#pragma pack(4),取小值,也就是1,所以
这个成员按一字节对齐,放在偏移[4]的位置。
    short b; //第三个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以
放在偏移[6,7]的位置。
    char c; //第四个,自身长为1,放在[8]的位置。
  };
这个类实际占据的内存空间是9字节
类之间的对齐,是按照类内部最大的成员的长度,和#pragma pack规定的值之中较小的一
个对齐的。
所以这个例子中,类之间对齐的长度是min(sizeof(int),4),也就是4。
9按照4字节圆整的结果是12,所以sizeof(TestB)是12。
假如
#pragma pack(2)
class TestB
  {
  public:
    int aa; //第一个成员,放在[0,3]偏移的位置,
    char a; //第二个成员,自身长为1,#pragma pack(4),取小值,也就是1,所以
这个成员按一字节对齐,放在偏移[4]的位置。
    short b; //第三个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以
放在偏移[6,7]的位置。
    char c; //第四个,自身长为1,放在[8]的位置。
  };
//可以看出,上面的位置完全没有变化,只是类之间改为按2字节对齐,9按2圆整的结果是
10。
//所以 sizeof(TestB)是10。
最后看原贴:
现在去掉第一个成员变量为如下代码:
  #pragma pack(4)
  class TestC
  {
  public:
    char a;//第一个成员,放在[0]偏移的位置,
    short b;//第二个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以
放在偏移[2,3]的位置。
    char c;//第三个,自身长为1,放在[4]的位置。
  };
//整个类的大小是5字节,按照min(sizeof(short),4)字节对齐,也就是2字节对齐,结果
是6
//所以sizeof(TestC)是6。
感谢 Michael 提出疑问,在此补充:
当数据定义中出现__declspec( align() )时,指定类型的对齐长度还要用自身长度和这里
指定的数值比较,然后取其中较大的。最终类/结构的对齐长度也需要和这个数值比较,然
后取其中较大的。
可以这样理解, __declspec( align() ) 和 #pragma pack是一对兄弟,前者规定了对齐
的最小值,后者规定了对齐的最大值,两者同时出现时,前者拥有更高的优先级。
__declspec( align() )的一个特点是,它仅仅规定了数据对齐的位置,而没有规定数据实
际占用的内存长度,当指定的数据被放置在确定的位置之后,其后的数据填充仍然是按照
#pragma pack规定的方式填充的,这时候类/结构的实际大小和内存格局的规则是这样的:
在__declspec( align() )之前,数据按照#pragma pack规定的方式填充,如前所述。当遇
到__declspec( align() )的时候,首先寻找距离当前偏移向后最近的对齐点(满足对齐长
度为max(数据自身长度,指定值) ),然后把被指定的数据类型从这个点开始填充,其后的
数据类型从它的后面开始,仍然按照#pragma pack填充,直到碰到下一个__declspec( al
ign() )。
当所有数据填充完毕,把结构的整体对齐数值和__declspec( align() )规定的值做比较,
取其中较大的作为整个结构的对齐长度。
非凡的,当__declspec( align() )指定的数值比对应类型长度小的时候,这个指定不起作
用。
网友回复:这个和编译器的字节对齐有关,
数据结构为了方便读取,一般数据结构都会进行字节对齐
(因为内存读取的原因,奇字节要两次操作)
在编译选项里面可以选择单字、双字、四字对齐,
对齐方式都是向前对齐,以其中一个为例:

struct A{
double b; //8
int a; //4个 向前对齐 占用8个
}; =16

假如调换一下顺序就不一样了
struct A{
int a; //4个
double b; //8个向前对齐 占用8个
}; =12

网友回复:结构体A在内存中应该是将数据变量a和b开辟成double类型长分别为8
而sizeof(stru_type)则得看struct stru_type,定义了三个变量和一个指针,我认为在内存里为前三个变量各开辟了8个字长的空间,原因如上。
最后一个指针则是没有开辟空间。stru指向该结构体地址
网友回复:太复杂了。。。
网友回复:补充个,编译器一般不会使任何一个成员处于奇数地址
网友回复:可是我还是有点不懂的是:
struct stru_type
{
int a;
double d;
char *pch;
stru_type *stru;
};

struct A{
double b;
int a;
};

在stru_type和A 中,a,d,pch,stru的大小分别是4,8,4,4,他们都是4的整数倍,这样还有偏移量吗?
还有,对于3楼说的调换顺序,我刚才在VC下运行了一下,还是16和24,没有变化呀!
网友回复:struct stru_type
{
int a; //8
double d; //8
char *pch; //4
stru_type *stru;//4
};

网友回复:
引用 3 楼 barenx 的回复:
这个和编译器的字节对齐有关,
数据结构为了方便读取,一般数据结构都会进行字节对齐
(因为内存读取的原因,奇字节要两次操作)
在编译选项里面可以选择单字、双字、四字对齐,
对齐方式都是向前对齐,以其中一个为例:

struct A{
double b; //8
int a; //4个 向前对齐 占用8个
}; =16

假如调换一下顺序就不一样了
struct A{
int a; //4个
double b; //8个向前对齐 占用8个
}; =12

我用VC6.0编译过了,跟顺序没有关系,具体在内存里怎么开辟的不清楚喽....
网友回复:
引用 7 楼 hujinyong199 的回复:
可是我还是有点不懂的是:
struct stru_type
{
int a;
double d;
char *pch;
stru_type *stru;
};

struct A{
double b;
int a;
};

在stru_type和A 中,a,d,pch,stru的大小分别是4,8,4,4,他们都是4的整数倍,这样还有偏移量吗?
还有,对于3楼说的调换顺序,我刚才在VC下运行了一下,还是16和24,没有变化呀!


补充个,编译器不会使任何一个成员处于奇数地址(相对其长度的,也就是说其读取不被拆分),
为了这样,结构的大小必须是最长类型的整数倍

struct A{
double b;
int a;
};
还是
struct A{
int a;
double b;
};
编译器都需要填充字节使其是8的整数倍

网友回复:上面说明的时候没有考虑这一点
网友回复:
引用 10 楼 barenx 的回复:
引用 7 楼 hujinyong199 的回复:
可是我还是有点不懂的是:
struct stru_type
{
int a;
double d;
char *pch;
stru_type *stru;
};

struct A{
double b;
int a;
};

在stru_type和A 中,a,d,pch,stru的大小分别是4,8,4,4,他们都是4的整数倍,这样还有偏移量吗?
还有,对于3楼说的调换顺序,我刚才在VC下运行了一下,还是16和24,没有变化呀!


补…


默认是int的长度,也就是一般是4bytes. 64位的机器是8bytes.
网友回复:复杂类型(结构体、类等)默认对齐方式是最长的成员对齐方式
网友回复:谢谢!这回长见识了!
网友回复:有高手啊
网友回复:
引用 6 楼 barenx 的回复:
补充个,编译器一般不会使任何一个成员处于奇数地址

请问这样处理有什么好处,还望指教

网友回复:内存对齐!
可以查阅相关资料!2楼也讲解得相当具体
网友回复:
引用 16 楼 billy1985 的回复:
引用 6 楼 barenx 的回复:
补充个,编译器一般不会使任何一个成员处于奇数地址

请问这样处理有什么好处,还望指教

为什么需要字节对齐?
计算机组成原理教导我们,这样有助于加快计算机的取数速度,否则就得多花指令周期了。
为此,编译器默认会对结构体进行处理(实际上其它地方的数据变量也是如此),让宽度为2的基本数据类型(short等)都位于能被2整除的地址上,让宽度为4的基本数据类型(int等)都位于能被4整除的地址上。
网友回复:内存对齐,总度是最小长度的倍数
网友回复:顶2楼
呵呵
网友回复:哇,哥哥姐姐们,能否写得简单些
网友回复:字节对其,。
struct A{
double b; 8字节
int a; 8字节
};

struct stru_type
{
int a; 8字节
double d; 8字节
char *pch; char *pch; stru_type *stru; 一共站8字节
stru_type *stru;
};
字节对齐不是说4字节对齐,一般以最大的为准,个人理解哈
网友回复:内存对齐的问题,可以用#pragma pack(对齐标准)来解决,一般用#pragma pack(1) ,sizeof就可以得到大家想象中的答案。
网友回复:2楼的文章很犀利,能把完整的贴出来吗?想看看
网友回复:2楼真厉害……顶
网友回复:高手云集啊,受教了
网友回复:个人经验,可以参考
1)整个结构体长度一定是结构体内最大对象的长度的整数倍(只能是基本数据类型,用户定义的除外);
2)结构体的每个对象开始存储时,起始地址应该时对象长度的整数倍;
3)不够就填充,除非强制字节对齐。

所以:
#include <iostream>
using namespace std;

struct stru_type
{
int a;
double d;
char *pch;
stru_type *stru;
};

struct A{
double b;
int a;
};
int main(){
cout < <sizeof(A) < <endl; //16 为什么不是4 8=12?
cout < <sizeof(stru_type) < <endl;//24 为什么不是4 8 4 4=20?
return 0;
}

sizeof(A)的长度因该时sineof(double)的整数倍,(在最后填充4个字节)
sizeof(stru_type)是24,同理。

假如在填充的位置上添加正好长度的对象,可以验证。如:
struct A{
double b;
int a;
int c;
};

sizeof(A)长度为16,并没有变长;

网友回复:破解游戏机奔驰宝马技术玩法

要点:序、奔驰宝马机对玩家有利的一面
一、熟悉奔驰宝马系列机型
二、哪些机子不要打
三、关机回分可行吗
四、怎样押“庄、和、闲
五、一些无效的打法
六、让你受益但保证赢钱的一些打法
七、等待时机出手必胜的秘笈
一天发一次 今天发序 明天发一
序】奔驰宝马机对玩家有利的一面 
游戏的发展总的走向,为了吸引玩家,越是新版的机子,总体返奖率就越高一些。相对而言,奔驰宝马系列游戏面市比较晚,
它的返奖率由以前其它游戏的20%-60%上升到60%-90%。同时,为了保证开店老板的利润,其程序启用了新的策略,也就是设置了新的陷阱,
陷阱是专门对付多金而缺乏耐性的玩家的,这一点在本文第四部分加以说明。反过来,假如跳过陷阱,耐心而理智地玩机,就是有输有赢,每次输也不会太多。
技术交流QQ:751287730电话15859526408网址:www.gaoraoqi.net
网友回复:
引用 16 楼 billy1985 的回复:
引用 6 楼 barenx 的回复:
补充个,编译器一般不会使任何一个成员处于奇数地址

请问这样处理有什么好处,还望指教


这和内存寻址有关,从80X86年代就有的问题,从汇编和硬件嵌入式开发的书里面看见过这个说法
网友回复:我晕了
网友回复:
引用 22 楼 dbkcpp 的回复:
字节对其,。
struct A{
double b; 8字节
int a; 8字节
};

struct stru_type
{
int a; 8字节
double d; 8字节
char *pch; char *pch; stru_type *stru; 一共站8字节
stru_type *stru;
};
字节对齐不是说4字节对齐,一般以最大的为准,个人理解哈


!> <不能加个//啊?
网友回复:里面很全
http://blog.sina.com.cn/s/blog_5395a50f010006m1.html
网友回复:
引用 6 楼 barenx 的回复:
补充个,编译器一般不会使任何一个成员处于奇数地址

麻烦你运行一下这个程序
C/C code





Code highlighting produced by Actipro CodeHighlighter (freeware)

http://www.CodeHighlighter.com/





struct test

{

    char a;

    char b;

    char c;

    char d;

};

int main()

{

    test a;

    printf("%d\n", &a.a );

    printf("%d\n", &a.b );

    printf("%d\n", &a.c );

    printf("%d\n", &a.d );

    

    return 0;

}




在我机器上输出是
1245036
1245037《=====这个是奇数吧
1245038
1245039
网友回复:内存对齐,整个结构体的大小为它里面的最大类型大小的整数倍,如stru_type里面最大的类型是double,占8个字节,所以sizeof(stru_type)一定为8的倍数。
网友回复:lz叙述的很精辟,佩服!
网友回复:顶一个!
网友回复:简单点说:

假如成员中最长的成员比机器字长要短(全部成员都是较机器字长要短的),那么就按照机器字长对齐
…………………………………………长…………………………………………,……………最长的成员的长度对齐

这只是指基本数据类型,算来算去,貌似只有double和long long需要第二条

对齐的目的是,方便数据访问,比如你定义一个A的数组,A a[100];每一个数组元素的长度要落在机器轻易访问的长度上,每一个成员也是如此,这样就知道它是怎么排的了,比如一个int成员,那它一定排在首地址a之后的sizeof(int)长的整数倍上,依此类推
关键字:一个,sizeof,问题,

相关文章

文章评论

共有 0 位网友发表了评论 此处只显示部分留言 点击查看完整评论页面