C中关于malloc 内存地址是如何分配的?

https://www.jianshu.com/p/2ebd415b947d

思考: 在C语言中我们向操作系统请求malloc内存空间地址是连续的吗???

测试 1 每次申请一块内存空间


void *a1 = malloc(1);
void *a2 = malloc(2);
printf("%p\n",a );

printf("%p\n", a1);
printf("%p\n", a2);

printf("%ld\n", a1 - a);
printf("%ld\n", a2 - a1);
free(a);
free(a1);
free(a2);

/*
测试结果:0x7f8a7a402600
0x7f8a7a402610
0x7f8a7a402620
16
16

感觉像是有个固定的间隔但地址并不是连续地址,具体为什么后面我们会讲到。
*/

测试 2 如果我们用 数组 一次malloc 分配多个虚拟地址 那地址否连续 ?


p = (int *)malloc(10*sizeof(int));

for (int i = 0; i < 10; i++)
{
p[i] = i;
printf("%d----%p\n", p[i],&p[i]);
}

getchar();

/*
输出结果 :

1----0x7fb820c02604
2----0x7fb820c02608
3----0x7fb820c0260c
4----0x7fb820c02610
5----0x7fb820c02614
6----0x7fb820c02618
7----0x7fb820c0261c
8----0x7fb820c02620
9----0x7fb820c02624

可以看出,用一次malloc申请多个(数组)地址的是连续地址 ,这个应该没有什么疑问
*/

测试3 多次malloc 申请空间看是否连续


for (int i = 0; i < 1000; i++)
{
p = (int*)malloc(sizeof(int));
*p = i;

printf("%d-----%p----%p\n", *p, p, p-1);
}
getchar();

/*
运行结果 : 循环次数分别是 10 100 1000

0-----0x7fd608402600----0x7fd6084025fc
1-----0x7fd608402610----0x7fd60840260c
2-----0x7fd608402620----0x7fd60840261c
3-----0x7fd608402630----0x7fd60840262c
4-----0x7fd608402640----0x7fd60840263c
5-----0x7fd608402650----0x7fd60840264c
6-----0x7fd608402660----0x7fd60840265c
7-----0x7fd608402670----0x7fd60840266c
8-----0x7fd608402680----0x7fd60840267c
9-----0x7fd608402690----0x7fd60840268c

总结如下:
(1)我们用一次malloc申请多个(数组)地址的是连续地址 。
(2)多次malloc 申请地址,通过对每一次 申请的内存空间地址和上一块地址 (p-1) 作比较发现,地址并不是连续的 ,但系统会在每次malloc时,从相隔固定长度起开始分配,这是因为什么呢?? 那么接下来就需要引入一个新的知识点 :内存边界对齐。

为什么需要内存对齐?
通俗点将,为了让内存存取更有效率而采用的一种编译阶段优化内存存取的手段。 这里需要有必要了解下cpu 是如何工作的的:

 

cpu是这么看内存的.jpg

CPU把内存当成是一块一块的,块的大小可以是2,4,8,16字节大小,因此CPU在读取内存时是一块一块进行读取的。块大小为 memory access granularity(有的资料成为粒度,类似于单元)
那么CPU读取数据时到底是怎么操作的?让我们来看一张寡人手绘图图解(加强下记忆)

 

 

假设CPU要读取一个int型4字节大小的数据到寄存器中,分两种情况讨论:
1、数据从0字节开始
2、数据从1字节开始

假设块大小成为memory access granularity(粒度) 为4 那么如上图所示:
当该数据是从0字节开始时,很CPU只需读取内存一次即可把这4字节的数据完全读取到寄存器中。
当该数据是从1字节开始时,问题变的有些复杂,此时该int型数据不是位于内存读取边界上,此时CPU先访问一次内存,读取0—3字节的数据进寄存器,并再次读取4—5字节的数据进寄存器(6-7没有数据存储),接着把0字节和6,7,8字节的数据剔除,最后合并1,2,3,4字节的数据进寄存器。对一个内存未对齐的数据进行了这么多额外的操作,大大降低了CPU性能。所以使用字节对齐可以大大提升cpu 的执行效率。

我们知道了对其的中重要性 ,那么计算机内是如何进行内存对齐呢?? 下面是摘自前人总结的 内存对齐三大原则(有的资料可能是4个):

原则1、数据成员对齐规则:结构(struct或联合union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。

原则2、结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储

原则3、结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐

具体大家可以参考这篇文章,讲的非常nice 满18周岁可进入

上述三条便是计算几种对内存优化处理的一种手段,为了提高计算机的访问和执行效率,也是我们日常计算内存分布与大小的依据,不过需要注意的是在不同的计算机架构下,不同数据类型所占的字节数并不是一定相同的,例如 32 位和 64 位C数据类型

 

感谢女朋友火力支援.jpg

上表中第一行的大写字母和数字含义如下所示:
I表示:int类型
L表示:long类型
P表示:pointer指针类型
32表示:32位系统
64表示64位系统
如:LP64表示,在64位系统下的long类型和pointer类型长度为64位。
64位Linux 使用了 LP64 标准,即:long类型和pointer类型长度为64位,其他类型的长度和32位系统下相同类型的长度相同,32位和64位下类型的长度比较见上图的蓝色部分。

对于简单类型,如int,char,float等,其对齐大小为其本身大小;对于复合类型,如struct,class,其本身并无所谓对齐,因为CPU没有直接存取一个struct的指令。对于struct而言,它的对齐指的是它里面的所有成员变量都是对齐的,class同理。讲到这里我们不再对struct做深一步解释了,只需要大体知道struct 结构就像是一个袋子,实际我们在编译的时候并不会给它分配内存空间,但它确实对内存分布有一定的影响,引用一个比较经典的比喻,struct就像电影剧本,而我们所看到的只是剧本的具体表现形式--电影,至于为什么这么演,该怎么演,it's none of your business .
当然对于程序员而言,我们也可以指定某些数据类型的对齐字节大小 (这里需要注意:只能指定2的n次方作为对齐大小,对于指定对齐大小为6,9,10这样的编译器可能会不予理会)
那么问题来了,如果我自己指定的对其字节大小和系统的有冲突,比如int 类型,系统指定位4的整数倍,而我自定义的是8 这个怎么办??其实很好记忆,只要记住下面这句话就好了
align(x) = min ( sizeof(x) , packalign) , 即sizeof(x)和指定对齐大小哪个小,对齐大小就为哪个。
因此,上面的疑问答案是align(int)=sizeof(int)=4

对于怎么计算内存大小,只要根据我们前面提的内存法则来计算,就不会有什么问题,因为针对这个情况比较多,在这里就不做过多的解释了 ,网上有很多优秀的资料这里

到这里关于内存的介绍先告一段落,静下心来,不骄不躁。各位共勉!

--------------------------最后感谢女朋友亲手给剥的橘子--------------------------------

下面是来自google拓展:
/* 1、在有操作系统和虚拟地址管理情况下,一次malloc的内存虚拟地址是连续的,物理地址不连续,连续多次malloc的
内存之间不一定连续。例如linux实现的就是“虚拟内存系统”,对用户而言,所有内存都是虚拟的。也就是说程序并不是
直接运行在物理内存上,而是运行在虚拟内存上,然后由虚拟内存转换到物理内存。linux将所有的内存都以页为单位进
行划分,通常每一页是4KB。在对虚拟内存地址到物理内存地址进行转换时,内核会对地址的正确性进行检查,如果地址是
合法的,内核就会提供对应的物理内存分页;如果是申请内存空间,内核就会检查空余的物理内存分页,并加以分配,如果
物理内存空间不足,内核会拒绝此次申请。使用malloc分配的内存空间在虚拟地址空间上是连续的,但是转换到物理内存空
间上有可能是不连续的,因为有可能相邻的两个字节是在不同的物理分页上。*/

 

 

  • 13
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值