资讯专栏INFORMATION COLUMN

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

klinson / 952人阅读

摘要:栈上各个变量申请的内存,返回的地址是这段连续内存的最小的地址。为什么用一个位的十六进制来呢因为个字节,一个字节有位,每位有两个状态,那么就是,也就是。为什么用,纯属演示方便。结构体里的字节对齐以成员中自身对齐值最大的那个值为标准。

原文:我的个人博客 https://mengkang.net/1046.html
初中级 phper 有多久没给自己充电了呢,安利一波我的直播 PHP 进阶之路
鸟哥微博

为什么要字节对齐

需要字节对齐的根本原因在于CPU访问数据的效率问题。因为CPU每次都是从以4字节(32位CPU)或是8字节(64位CPU)的整数倍的内存地址中读进数据的。(更深入的原因,谁告知下),如果不对齐的话,很有可能一个4字节int需要分两次读取。具体演示看下面的实验。

数据类型自身的对齐值

按各数据类型自身大小进行对齐。变量的内存地址正好位于它长度的整数倍

实验
#include 

int main(int argc, char const *argv[])
{   
    char a = 1; // 0x7fff5fbff77f,sizeof(a):1
    int  b = 1; // 0x7fff5fbff778,sizeof(b):4
    int  c = 1; // 0x7fff5fbff774,sizeof(c):4
    char d = 1; // 0x7fff5fbff773,sizeof(e):1
    int  e = 1; // 0x7fff5fbff76c,sizeof(f):4
    
    printf("%p,sizeof(a):%lu
",&a,sizeof(a));
    printf("%p,sizeof(b):%lu
",&b,sizeof(b));
    printf("%p,sizeof(c):%lu
",&c,sizeof(c));
    printf("%p,sizeof(d):%lu
",&d,sizeof(d));
    printf("%p,sizeof(e):%lu
",&e,sizeof(e));

    return 0;
}
辅助以图片说明,该图左侧是上面代码的内存图,灰色部分表示该程序未使用的内存。右侧是在上面代码的基础上在char a后面声明了一个short f

从上面的实验和图上我们可以找出以下规律:

abcde 五个变量的内存地址从大到下依次分配的;

如果你细看,会发现它们的内存地址并不是紧密挨着的;

而且int 类型的变量的内存地址都是偶数(这也就是为什么鸟哥微博中说的不可能存在奇数的 int 变量的地址了);

再细看,发现 int 变量的地址都是可以被4整除,所以在栈上各变量是按各数据类型自身大小进行对齐的。

新增的short f 地址也并没有紧挨着a,而是跟自身数据大小对齐,也就是偶数地址开始申请。

栈上各个变量申请的内存,返回的地址是这段连续内存的最小的地址。

反过来想,如果不对齐,比如上例中的 a,b,c 三个变量的内存地址紧挨着,而CPU每次只读取8个字节,也就是说变量 c 还有最后一个字节没有读取进来。访问数据效率就降低了。

栈上各个变量申请的内存,返回的地址是这段连续内存的最小的地址。这是怎么回事呢?

我们还是通过实验来验证下我上面画的内存图,假如我有一个int变量,它的值占了满了4个字节,那么它的四个字节里是怎么存放数据的,我们用十六进制来演示0x12345678

为什么用一个8位的十六进制来呢?因为int 4个字节,一个字节有8位,每位有0/1两个状态,那么就是2^8=256,也就是16^2。所以用了一个8位的16进制数正好可以填满一个 int 的内存。

为什么用12345678,纯属演示方便。

我先存了变量 b,然后以 char 指针 p 来依次访问 b 的四个字节的使用情况。

#include 

int main(int argc, char const *argv[])
{
    char a = 1;             // 0x7fff5fbff777
    int  b = 0x12345678;    // 0x7fff5fbff770
    char c = 1;             // 0x7fff5fbff76f
    printf("%p
",&a);
    printf("%p
",&b);
    printf("%p
",&c);

    char *p = (char *)&b;
    
    printf("%x %x %x %x
", p[0],p[1],p[2],p[3]); // 78 56 34 12
    printf("%p %p %p %p
", &p[0],&p[1],&p[2],&p[3]); // 0x7fff5fbff770 0x7fff5fbff771 0x7fff5fbff772 0x7fff5fbff773
        
    return 0;
}

变量 b 0x12345678的最高位是0x12,最低位是0x78
针对实验结果我又画了内存图,我们可以看到0x12存放在的内存地址要比0x78的大。

这里呢就必须说明下 大小端模式

小端法(Little-Endian)就是低位字节排放在内存的低地址端即该值的起始地址,高位字节排放在内存的高地址端。

大端法(Big-Endian)就是高位字节排放在内存的低地址端即该值的起始地址,低位字节排放在内存的高地址端。

所以,我当前的环境是小端序的形式。

为什么会有大端小端之分?
这个就得问硬件厂商了,都比较任性,所以历史就这样了。
结构体里的字节对齐

以成员中自身对齐值最大的那个值为标准。

实验
int main(int argc, char const *argv[])
{
    struct str1{
        char a;
        short b;
        int c;
    };
    
    printf("sizeof(f):%lu
",sizeof(struct str1));
    
    struct str2{
        char a;
        int c;
        short b;
    };
    
    printf("sizeof(g):%lu
",sizeof(struct str2));
    
    struct str1 a;
    printf("a.a %p
",&a.a);
    printf("a.b %p
",&a.b);
    printf("a.c %p
",&a.c);
    
    struct str2 b;
    printf("b.a %p
",&b.a);
    printf("b.c %p
",&b.c);
    printf("b.b %p
",&b.b);

    
    return 0;
}

结果

sizeof(f):8
sizeof(g):12
a.a 0x7fff5fbff778
a.b 0x7fff5fbff77a
a.c 0x7fff5fbff77c
b.a 0x7fff5fbff768
b.c 0x7fff5fbff76c
b.b 0x7fff5fbff770
原理

灰色表填充用来对齐,保证最后结构体大小是最长的成员的大小的整数倍。

例外

实际工作中是否不按字节对齐的情况呢?有的,比如我们的 rpc 框架里面进行数据传输的时候,会选择设置为紧凑型,这样就可以轻松做到跨平台,跨语言了。
在网络程序中采用#pragma pack(1),即变量紧缩,不但可以减少网络流量,还可以兼容各种系统,不会因为系统对齐方式不同而导致解包错误。

实战举例 yar_header 中使用 #pragma pack(1) 和 attribute ((packed)) 的意义

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/26255.html

相关文章

  • 【C语言】超详讲解☀️指针是个什么针?(一次性搞定指针问题)

    目录 前言 一、 什么是指针? 引例 计算机是怎么对内存单元编号的呢? 内存空间的地址如何得到 想存地址怎么办? ​ 本质目的不是为了存地址  二、指针和指针类型 为什么有不同类型的指针 1.指针的解引用 2.指针+-整数 三、野指针 造成野指针的原因 1.未主动初始化指针  2.指针越界访问 3.指针指向的空间释放 规避野指针 四、指针运算 1.指针+-整数  2.指针-指针  3.指针的关系运...

    tigerZH 评论0 收藏0
  • C/C++

    摘要:另外栈内存出了作用域就会自动释放掉,所以不需要手动去回收的。,其中指针变量的声明有如下三种形式其中第一种是被推荐的写法。数据类型 C语言中的基本数据类型,对于它分为两种: 1、signed 有符号的类型,也就是支持正负号的。 2、unsigned 无符号的类型,也就是没有负号,取值从0开始。 有符号和无符号的数据类型有啥区别呢?其实就是取值范围不一样,下面看一张对照表: showImg(ht...

    microcosm1994 评论0 收藏0
  • 用React写一个数字华容道,你需要知道的秘密

    摘要:还在上班很无聊数字华容道畅玩地址开发源码地址这个叫前言年末了。光随机生成一个乱序数列是不够的,还得保证这个数列的逆序数为偶数,嗦嘎。所以,我们直接将交换的次数,记为数列逆序数个数,就达到了想要的效果。 还在上班?很无聊?数字华容道畅玩地址 开发源码地址 这个叫前言 年末了。哦,不,要过年了。以前只能一路站到公司的我,今早居然是坐着过来的。新的一年,总要学一个新东西来迎接新的未来吧,所以...

    Jason 评论0 收藏0
  • [零基础学python]复习if语句

    摘要:在学习语句的时候,对编程的基础知识了解的还不是很多,或许没有做什么太复杂的东西。可以通过一个内置函数来判断一个条件的结果还是。有朋友需要看完整教程内容,请点击零基础学,这里会及时更新,并且有完整的目录结构,更吸纳了朋友们提出的意见和建议。 看官是否记得,在上一部分的时候,有一讲专门介绍if语句的:从if开始语句的征程。在学习if语句的时候,对python编程的基础知识了解的还不是很多,...

    array_huang 评论0 收藏0
  • RecyclerView瀑布流优化方案探讨

    摘要:是规则的瀑布流。普通的尺寸会出现错位的问题索引这个是右边这个是左边间距解决办法,可以通过里的来判断,这个方法不管你高度怎样,他都是左右左右开始排列的。 目录介绍 01.规则瀑布流实现02.不规则瀑布流实现2.1 实现方式2.2 遇到问题03.瀑布流上拉加载04.给瀑布流设置分割线05.自定义Manager崩溃06.如何避免刷新抖动07.为何有时出现跳动08.瀑布流图片优化09.onBi...

    zhaofeihao 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<