当前位置: 首页 > 后端技术 > PHP

为什么鸟哥说int再随机也不能申请奇数地址

时间:2023-03-30 05:44:55 PHP

原文:我的个人博客https://mengkang.net/1046.html都多久了初级和中级phper给自己充电了?需要字节对齐的根本原因在于CPU访问数据的效率。因为CPU每次都是从一个4字节(32位CPU)或者8字节(64位CPU)的整数倍的内存地址读取数据。(更深层次的原因,接下来谁来告诉我),如果不是对齐的,很有可能一个4字节的int需要读两次。具体的演示见下面的实验。数据类型本身的对齐值是根据每个数据类型本身的大小来对齐的。变量的内存地址恰好是其长度的整数倍实验#includeintmain(intargc,charconst*argv[]){chara=1;//0x7fff5fbff77f,sizeof(a):1intb=1;//0x7fff5fbff778,sizeof(b):4intc=1;//0x7fff5fbff774,sizeof(c):4chard=1;//0x7fff5fbff773,sizeof(e):1inte=1;//0x7fff5fbff76c,sizeof(f):4printf("%p,sizeof(a):%lu\n",&a,sizeof(a));printf("%p,sizeof(b):%lu\n",&b,sizeof(b));printf("%p,sizeof(c):%lu\n",&c,sizeof(c));printf("%p,sizeof(d):%lu\n",&d,sizeof(d));printf("%p,sizeof(e):%lu\n",&e,sizeof(e));return0;}辅以图片,图片左边是上面代码中的内存图,灰色部分代表程序没有使用到的内存。右边,根据上面的代码,在chara后面声明了一个shortf。从上面的实验和图表中,我们可以发现如下规律:abcde的五个变量的内存地址是按降序分配的;如果仔细观察,你会发现它们的内存地址并没有靠得很近;而int类型变量的内存地址都是偶数(这就是鸟哥微博不可能有奇数个int变量地址的原因);仔细一看,发现int变量的地址都是可以被4整除的,所以在栈上的每个变量都是按照每个数据类型的大小来对齐的。新加的短f地址不在a的旁边,而是和自己的数据大小对齐的,即偶地址开始申请。栈上每个变量申请的内存,返回的地址是这块连续内存的最小地址。反之,如果不是对齐的,比如上例中a、b、c三个变量的内存地址是紧挨着的,CPU一次只读取8个字节,也就是说变量c还有最后一个字部分没有读入。访问数据的效率降低了。栈上每个变量申请的内存,返回的地址是这块连续内存的最小地址。这里发生了什么?下面通过实验来验证一下我上面画的内存映射。如果我有一个int型变量,它的值占4个字节,它的4个字节是怎么存储数据的?我们用十个十六进制来演示0x12345678。为什么要使用8位十六进制?因为int有4个字节,一个字节有8位,每一位有0/1两种状态,那么就是2^8=256,也就是16^2。所以用一个8位的十六进制数刚好可以填满一个int的内存。为什么用12345678纯粹是为了方便演示。我先存了变量b,然后用char指针p依次访问b的四个字节的用法。#includeintmain(intargc,charconst*argv[]){chara=1;//0x7fff5fbff777intb=0x12345678;//0x7fff5fbff770字符c=1;//0x7fff5fbff76fprintf("%p\n",&a);printf("%p\n",&b);printf("%p\n",&c);字符*p=(字符*)&b;printf("%x%x%x%x\n",p[0],p[1],p[2],p[3]);//78563412printf("%p%p%p%p\n",&p[0],&p[1],&p[2],&p[3]);//0x7fff5fbff7700x7fff5fbff7710x7fff5fbff7720x7fff5fbff773return0;}变量b0x12345678最高位为0x12,最低位为0x78。如图所示,我们可以看到0x12存放的内存地址比0x78要大。这里必须说明一下,大小端模式小端法(Little-Endian)是指低位字节排列在内存的低地址端,也就是值的起始地址,而高位字节排列在内存的高地址端。Big-Endian是指高位字节放在内存的低地址端,即值的起始地址,低位字节放在内存的高地址端。所以,我现在的环境是小端。为什么bigendian和smallendian有区别?这个你得问问硬件厂商,他们比较任性,所以历史就是这样。结构中的字节对齐是基于成员中自对齐值最大的值。实验intmain(intargc,charconst*argv[]){structstr1{chara;短b;诠释c;};printf("sizeof(f):%lu\n",sizeof(structstr1));structstr2{字符a;诠释c;短b;};printf("sizeof(g):%lu\n",sizeof(structstr2));结构str1a;printf("a.a%p\n",&a.a);printf("a.b%p\n",&a.b);printf("a.c%p\n",&a.c);结构str2b;printf("b.a%p\n",&b.a);printf("b.c%p\n",&b.c);printf("b.b%p\n",&b.b);返回0;}结果sizeof(f):8sizeof(g):12a。a0x7fff5fbff778a.b0x7fff5fbff77aa.c0x7fff5fbff77cb.a0x7fff5fbff768b.c0x7fff5fbff76cb.b0x7fff5fbff770采用灰表填充原则进行对齐,保证最终的结构体大小是最长成员大小的整数倍。如果异常不是字节对齐的,它真的有效吗?有的,比如在我们的rpc框架中进行数据传输的时候,我们会选择设置为compact,这样我们就可以轻松实现跨平台、跨语言。在网络程序中使用#pragmapack(1),即变量压缩,不仅可以减少网络流量,还可以兼容各种系统,不会因为系统对齐不同而导致解包错误。实例yar_header中使用#pragmapack(1)和属性((packed))的意义