从内存角度深入看结构体(window/linux)大小

今天我们来看一下windows(32, 64)(dev-c++,vc),linux(32, 64)不同系统下,

它们求结构体大小时,编译器到底给它们分配了哪些内存,又为什么这样分配,为啥子编译器给它们有时空闲3个内存块,有时候又空闲7个内存块,为什么啊,为什么啊


当你们读了上面的内容,还想继续往下看的时候,就说明你开始关注内存的分配问题了,哈哈!!!


关于内存对齐:

简单地理解就是:程序中,数据结构中的变量等等都需要占用内存,系统采用内存对齐,就可以提高访问速度(为了访问未对齐的内存,处理器需要作两次内存访问,而经过内存对齐一次就可以了)


大家看下面这个程序:

#include <stdio.h>

struct stu{
	char a;
	int b;
	char c;
};



int main(){
	struct stu ss;
	
	printf("%d\n", sizeof(ss));
	printf("%p\n", &ss.a);
	printf("%p\n", &ss.b);
	printf("%p\n", &ss.c);

	return 0;
}

当前结构体里面的成员类型是:(char , int, char)

下面大家看程序运行结果:

结果一:

win7,32位,vc中


win7,64位,vc中


结果二:

win7,32位,dev-c++中

win7,64位,dev-c++中


我们知道这涉及一个概念——偏移量

网上给出的解释是:它指的是结构体变量中成员的地址和结构体变量地址的差。

结构体大小的定义是:结构体的大小等于最后一个成员的偏移量加上最后一个成员的大小

哈哈,听饶了吧!

其实,当前偏移量的大小就是编译器为前面变量所分配的内存个数。

大家先看一下上面结构体在内存中(下面的是在dev中分配的)的情况


首先,我们知道编译器给一些变量分配内存的时候,它是一个连续的块,它是以一个4字节简单对其的

如上图所示:

编译器首先分配了一块地址也就是:0022FEB4给了结构体的首地址(也就是结构体中的成员a)因为它是一个字符,它占用一个内存地址,第二个是int类型,它占用4个字节,第一组已经用掉一格,还剩3格,肯定无法放下int型,考虑到内存对齐,所以不得不把它放到第二个组块(0022feb8),第三个类型是char类型,跟第一个类似,占用一个存储块(0022febc),(又因为它是4字节简单对齐方式),所以空闲的三个存储块也同样分给了当前这个结构体。



好,我们接下来继续分析:

看代码2:

#include <stdio.h>

struct stu{
	char a;
	char b;
	int c;
};



int main(){
	struct stu ss;
	
	printf("%d\n", sizeof(ss));
	printf("%p\n", &ss.a);
	printf("%p\n", &ss.b);
	printf("%p\n", &ss.c);

	return 0;
}
哈哈,可别看错了,我可跟上面的代码不一样哎!!!!!

(结构体里面定义的成员是: char, char , int

好,下面大家看一下运行结果:

win7,32位,vc中


win7,32位,vc中


结果二:

win7,32位,dev-c++中

win7,32位,dev-c++中


来,不要急,我们接着看内存,分析分析它:


同样的道理:当前结构体中,第一个类型是char类型,它占用一个字节(也就是0022FEB8),然后,接下来第二个类型还是char,占用一个字节,又因为上一组4个字节(其中0022FEB9,0022FEBA, 0022FEBB均空着)还空着3个,可以放下第二个char,所以第二个char就放在了内存(0022FEB9)处,然后碰到int类型中,占用4个字节,上一组4个字节中还剩下两个空闲(其中0022FEBA, 0022FEBB),不够放int类型,所以,新找了另一组连续的4个字节。所以说:当前结构体总总共占用8个字节。

哈哈,所以说:结构体中,编译器为所分配的所有空闲内存(满足4字节简单对齐)都算在内,总的结构体大小也就相应的出来了。

简单的说:结构体的成员变量类型(如1:char, int , char       如2:char, char, int),它们成员一样,但顺序不一样,编译器给分配的内存也不一样,我们应尽可能的使其内存占用的少一点,这样,总的来说对我们的程序运行只有好处。



这回大家明白的差不多了吧!接下来我们分析一个不一样的东西

看代码3:

#include <stdio.h>

struct stu{
	char a;
	double b;
	char c;
};

int main(){
	struct stu ss;
	
	printf("%d\n", sizeof(ss));
	printf("%p\n", &ss.a);
	printf("%p\n", &ss.b);
	printf("%p\n", &ss.c);
	return 0;
}


我们可以很清楚的看明白上面这个结构体的成员变量是(char , double , char)

所以我们的运行结果是:

win7,32位,vc中:


win7,64位,vc中:


结果二:

win7,32位,dev-c++中


win7,64位,dev-c++中



接下来再看一个小的程序:

代码4:

#include <stdio.h>

struct stu{
	char a;
	int e;
	double b;
	char c;
};

int main(){
	struct stu ss;
	
	printf("%d\n", sizeof(ss));
	printf("%p\n", &ss.a);
	printf("%p\n", &ss.e);
	printf("%p\n", &ss.b);
	printf("%p\n", &ss.c);
	return 0;
}

我们也可以直接看到:这个结构体成员是(char   , int   , double   , char  )

好,我们在看一下简单地运行结果:

win7, 32,  dev:        

                

win7, 64,  dev:  


下面是win7,32,vc

  

下面是win7,32,vc


哈哈,不要害怕它们为什么都是24,,接下来我们需要在看一下他们的内存分配情况,

下面这个图是代码3的图:


代码4的这个内存图(将代码1和代码2的内存图给结合起来了),

哈哈,太难画了,我在这里就不画了啊,大家应该可以理解的

好,接着继续分析内存情况

我们前面提到的在代码1,代码2中内存是简单地4字节对齐,所以我们一直在找4个字节来存放他们(也就是简单地说,char后面如果跟着int类型,那么就会出现3个空闲),

而到了我们这个代码3, 4, 内存就是简单地8字节对齐,所以我们一直在找8个字节来存放他们,(也就是简单地说,char后面如果跟着double类型,那么就会出现7个空闲)

这又是为了什么?





这里我们又要提一下内存的自然对齐了

自然对齐就是说:每一种数据类型都必须放在内存地址中的整数倍上,举一个简单地例子

(地址4)可以放char类型的,也可以放int类型,也可以放short,但是但是就是不可用存放double类型,仅仅只是因为它不是8的整数倍,OK, 就是这么简单。

(地址3)可以用来存放char类型,但是不可用用来存放int, short, double ,也只是因为它们不是整数倍关系

(声明一点,地址0是任何类型整数倍的, 这一点,我们可以这样记着,理解为,可以存放任何数据类型)


所以说:在windows下:

所以,此时也就很好解释上面的几种情况了,哪些空闲的内存块,就是因为它们本身不是数据类型的整数倍,所以被留了下来,在加上为了访问便利,所以才出现了内存对齐这么一说的吧!(个人简单理解),其实我们这样想想,也就这么回事



那么在linux下又是什么样子的呢????

接下来我们继续看:

代码5(在linux下):

#include <stdio.h>
#include <sys/cdefs.h>
struct str{

        char a;
        int  b;
        char c;
};

int main(){
        struct str A;
        printf("%u\n", sizeof(A));
        printf("%p\n", &(A.a));
        printf("%p\n", &(A.b));
        printf("%p\n", &(A.c));

        return 0;

}

这样看,我们知道此时结构体里面的成员类型是: char ,int    , char

好,我们大家一起看运行结果:

linux(ubuntu),64位,:

linux(ubuntu),32位


这时,我们可以看到上面的内存分配是一样的。就是说他们遵从的基本准则是一样的(可以简单的理解为:跟window下面是一样的,(简单的四字节对齐))

好了,我们接下来继续看另外一种情况:

代码6(在linux下)

#include <stdio.h>
#include <sys/cdefs.h>
struct str{

        char a;
        double  b;
        char c;
};

int main(){
        struct str A;
        printf("结构体总大小位:%lu\n", sizeof(A));
        printf("成员a的初始地址:%p\n", &(A.a));
        printf("成员b的初始地址:%p\n", &(A.b));
        printf("成员c的初始地址:%p\n", &(A.c));

        return 0;

}

这时,我们也可以很清楚的看到当前结构体的成员变量类型是: char   , double    , char 

好了,我们接着往下看:运行结果:

liunx(ubuntu下),64位:

linux(ubuntu下), 32位:


哈哈,不一样了吧,懵了吗?不要紧,接着继续看内存分配:

(下图是64位的简单内存分配图)



下面是(32位的简单内存分配图)



好了,这回我们也看到了,在编译器的分配下,

linux(64位) :它是跟window底下的说法是一样的,对于double这个类型,它的初始地址就是仅仅只能在8的倍数的地方,也就是上面我们所说的遵从的是内存的自然对齐,所以(char后面是double的话,它会出现7个空闲区)

linux(32位):它是跟window是不一样的,但是也很好理解,就是说,它的所有东西都是遵从(基本的4字节对齐),而32位的编译器,将其(double)处理成了两个4字节,所以才会出现上面的情况,或者说对于double而言编译器不考虑它的自然对齐,(所以说char后面出现double的话,它会出现3个空闲区)




好了,大家伙:

现在我们来总结一下:

对于windows,关于结构体也就是说,我们不用考虑什么偏移量,什么加什么啊,那些都太麻烦了,只需要明白内存的自然对齐就ok了,(不管什么32,64位)

然而,对于linux下,对于64位:跟window一样就ok了,

而linux下,32位:就用简单的4字节对齐就0k了,遇到double的话,简单的将其当作两个4字节就ok了。



(ps,哎呀吗,太累了,画图太多了,真心不容易啊,但是么事,心不累,哈哈!!!)



上面的内容全是个人理解(均经过内存验证),若有不理解,或是疑问,欢迎指出.









  • 12
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值