struct结构体大小的计算(内存对齐)

一. 背景

当普通的类型无法满足我们的需求的时候,就需要用到结构体了。结构体可衍生出结构体数组,结构体还可以嵌套结构体,这下子数据类型就丰富多彩了,我们可以根据需要定义自己的数据类型。有时需要求结构体的大小,这就涉及到内存对齐的知识。概念、理论之类,我没有深入研究,这里主要是验证一下计算结构体大小的方法,证明学习到的方法确实有效。关于内存对齐,最开始是看了《深入理解计算机系统》中关于“数据对齐”一节,上面轻描淡写的写了下求结构体的大小,我没看明白。看《零基础入门C语言》中关于计算结构体大小的规则,算是看明白了。

二. 前奏

更多技术文章、面试资料、工具教程,还请移步:http://www.javatiku.cn/
先说点我觉得有意思的地方。数组之间是不可以直接赋值的,但是用结构体包装一下,就达到了这个效果,前者无法做到的事情却通过结构体做到了。通过代码来验证一下。

定义了两个数组arr和arr2,第14行代码,将arr赋值给arr2,编译时会报错。提示:14: error: array type 'int [5]' is not assignable。

(数组名有二义性,一是表示数组名,相当于数组的定海神针,二是表示首元素的地址。第14行代码把一个数组的首元素的地址赋值给另一个数组首元素,显然这是不允许的)

将第14行代码注释后。定义了一个结构体,里面定义了一个整型数组。然后定义了两个结构体变量tt1和tt2,将tt2赋值给了tt1,然后打印变量tt2中数组里面的每个元素。

#include <iostream>

using namespace std;

struct numArr
{
    int m_arr[5];
};

int main()
{
    int arr[5] = {1, 2, 3, 4, 5};
    int arr2[5] = {0};
//    arr2 = arr;

    struct numArr tt1 = {{1, 2, 3, 4, 5}};
    struct numArr tt2 = {{0}};
    tt2 = tt1;

    for(int i = 0; i < 5; ++i)
    {
        cout<<tt2.m_arr[i]<<endl;
    }

    return 0;
}

运行结果如下
更多技术文章、面试资料、工具教程,还请移步:http://www.javatiku.cn/
图片说明

从结果可以看到,打印结构与tt1中的数组中的元素一致。也就是说,将结构体变量tt1赋值给tt2后,tt2中的数组与tt1中的数组也一样了。

通过结构体这么一包装,就产生了数组"可以"赋值的现象了,挺有意思的。

三. 结构体大小的计算

内存对齐,关于这一点,《深入理解计算机系统》这本书用的篇幅蛮少的,两页不到。书上是这么介绍的。

许多计算机系统对基本数据类型的合法地址做出了一些限制,要求某种类型对象的地址必须是某个值K(通常是2、4或8)的倍数。这种对齐限制简化了形成处理器和内存系统之间的接口的硬件设计。

前半句能理解,后半句,涉及硬件的东西,懵逼了。

《零基础入门C语言》这本书,从现象的角度阐述,它先讲内存不对齐的情况。

一个成员变量需要多个机器周期去读的现象,称为内存不对齐。为什么要对齐呢?本质是牺牲空间,换取时间的方法。

一般来说,大多数系统,即使不对齐,也没什么大的问题,只是原本需要一次进行内存操作读或写,现在需要两次或多次了。不过这个与硬件有关系,比如有些处理器对于某些指令有特定的要求,否则可能就真的出现异常了。有兴趣的话,建议自行去深入学习。

对齐规则/计算方法

x86(Linux默认#pragma pack(4), Window默认#pragma pack(8))。Linux最大支持4字节对齐。

方法:

1) 取pack(n)的值 (n = 1, 2, 4,8......),取结构体中类型最大值为m。两者取小即为外对齐大小 Y = (m < n ? m: n);

2) 将每一个结构体的成员大小与Y比较取小者为X,作为内对齐的大小;

3) 所谓按X对齐,即为地址(设起始地址为0)能被X整除的地方开始存放数据;

4) 外部对齐原则是依据Y的值(Y的最小整数倍),进行补空操作;

以上就是通常计算结构体大小的方法了。接下来,我们通过一些简单实验来验证一下。

首先,定义一个结构体,里面包含了char、short、int类型的变量。

1) 结构体先按照char、short、int的顺序定义。然后定义一个结构体变量s1,求结构体的大小,一个是结构体类型的大小(模子),一个是是结构体变量的大小。我们也可以把结构体成员的地址也打印出来,查看它们的偏移量,分析起来会更清晰一些。

#include <stdio.h>

typedef struct stu
{
    char a;
    short b;
    int c;
} Stu;

int main()
{
    Stu s1 = {'m', 1, 20};
    printf("sizeof(Stu) = %d\n", sizeof(Stu));
    printf("sizeof(s1)  = %d\n", sizeof(s1));

    printf("-----------\n");
    printf("&s1   = %p\n", &s1);
    printf("&s1.a = %p\n", &s1.a);
    printf("&s1.b = %p\n", &s1.b);
    printf("&s1.c = %p\n", &s1.c);
    return 0;
}

a) Windows平台,pack默认为8。先求外对齐大小Y,结构体中类型最大的为int类型,大小为4字节,4比8小,所以Y值为4.

b) 然后将结构体中的每一个成员与Y进行比较,依次求内对齐的大小。结构体中成员分别为char 1字节,short 2字节,int 4字节,与4(外对齐大小Y)比较,得到内对齐大小分别为 1, 2, 4。

c) 假设起始地址为0x00,0可以被1整除,可以存放a了。a为char类型,大小为1个字节。接着地址为0x01,但是0x01不能被2整除,然后下一个地址为0x02,0x022可以被2整除,因此b的起始地址为0x02(此时,a与b之间填充了一个字节)。b为short类型,大小为2个字节。接着地址到了0x04,它可以被4整除,于是可以存放c了,c为int类型,大小为4个字节。

d) 接着地址到了0x08,(0x08-0x00)它可以被4(外对齐大小Y)整除,满足外对齐要求。

经分析,结构体大小为1 + 1 + 2 + 4 = 8个字节。

图如下图所示

图片说明

代码运行结果如下

图片说明

从打印结果来看,结构体大小为8,与上面的分析结果一致,符合预期。

2) 调整结构体中成员的顺序。结构体先按照short、char、int的顺序定义。打印成员地址的时候也需要调整下a与b的打印顺序。代码其它部分保持不变。

typedef struct stu
{
    short b;
    char a;
    int c;
} Stu;

a) Windows平台,pack默认为8。先求外对齐大小Y,结构体中类型最大的为int类型,大小为4字节,4比8小,所以Y值为4。

b) 然后将结构体中的每一个成员与Y进行比较,依次求内对齐的大小。结构体中成员分别为short 2字节,char 1字节,int 4字节,与4(外对齐大小Y)比较,得到内对齐大小分别为 2,1, 4。

c) 假设起始地址为0x00,0可以被2整除,可以存放b了。b为short类型,大小为2个字节。接着地址为0x02,2可以被1整除,a为char类型,大小为一个字节。然后下一个地址为0x03,0x03不可以被4整除,接着地址为0x04(此时,b与c之间填充了一个字节)。0x04可以被4整除,于是可以存放c了,c为int类型,大小为4个字节。

d) 接着地址到了0x08,(0x08-0x00)它可以被4(外对齐大小Y)整除,满足外对齐要求。

经分析,结构体大小为2 + 1 + 1 + 4 = 8个字节。

图如下图所示
更多技术文章、面试资料、工具教程,还请移步:http://www.javatiku.cn/
图片说明

代码运行结果如下.

图片说明

结构体大小为8,符合预期。

3)调整结构体中成员的顺序。结构体先按照int、short、char的顺序定义。打印成员地址的时候也需要调整为c、b、a的打印顺序。代码的其它部分保持不变。

typedef struct stu
{
    int c;
    short b;
    char a;

} Stu;

a) Windows平台,pack默认为8。先求外对齐大小Y,结构体中类型最大的为int类型,大小为4字节,4比8小,所以Y值为4。

b) 然后将结构体中的每一个成员与Y进行比较,依次求内对齐的大小。结构体中成员分别为int 4字节,short 2字节,char 1字节,与4(外对齐大小Y)比较,得到内对齐大小分别为4, 2,1。

c) 假设起始地址为0x00,0x00可以被4整除,可以存放c了。c为int类型,大小为4个字节。接着地址为0x04,0x04可以被2整除,b为short类型,大小为两个字节。然后下一个地址为0x06,0x06可以被1整除,可以存放a,a大小为1个字节。

d) 接着地址到了0x07,(0x07-0x00)不能被4(外对齐大小Y)整除,为满足外对齐要求,后面需要填充1个字节。

经分析,结构体大小为:4 + 2 + 1 + 1 = 8个字节。

图示如下

图片说明

运行结果如下

图片说明

结构体大小为8,符合预期。
更多技术文章、面试资料、工具教程,还请移步:http://www.javatiku.cn/

#C语言##学习路径#
全部评论
谢谢楼主分享!!!太棒了
1 回复 分享
发布于 2022-02-12 23:45

相关推荐

05-11 11:48
河南大学 Java
程序员牛肉:我是26届的双非。目前有两段实习经历,大三上去的美团,现在来字节了,做的是国际电商的营销业务。希望我的经历对你有用。 1.好好做你的CSDN,最好是直接转微信公众号。因为这本质上是一个很好的展示自己技术热情的证据。我当时也是烂大街项目(网盘+鱼皮的一个项目)+零实习去面试美团,但是当时我的CSDN阅读量超百万,微信公众号阅读量40万。面试的时候面试官就告诉我说觉得我对技术挺有激情的。可以看看我主页的美团面试面经。 因此花点时间好好做这个知识分享,最好是单拉出来搞一个板块。各大公司都极其看中知识落地的能力。 可以看看我的简历对于博客的描述。这个帖子里面有:https://www.nowcoder.com/discuss/745348200596324352?sourceSSR=users 2.实习经历有一些东西删除了,目前看来你的产出其实很少。有些内容其实很扯淡,最好不要保留。有一些点你可能觉得很牛逼,但是面试官眼里是减分的。 你还能负责数据库表的设计?这个公司得垃圾成啥样子,才能让一个实习生介入数据库表的设计,不要写这种东西。 一个公司的财务审批系统应该是很稳定的吧?为什么你去了才有RBAC权限设计?那这个公司之前是怎么处理权限分离的?这些东西看着都有点扯淡了。 还有就是使用Redis实现轻量级的消息队列?那为什么这一块不使用专业的MQ呢?为什么要使用redis,这些一定要清楚, 就目前看来,其实你的这个实习技术还不错。不要太焦虑。就是有一些内容有点虚了。可以考虑从PR中再投一点产出
点赞 评论 收藏
分享
大方的大熊猫准备进厂:1.教育背景:你希望从事什么专业的工作你的主修课就是什么;成绩优秀是你应该做的,没什么可描述的,成绩不优秀也许人家在大学忙着创业呢?(成绩优秀不一定是好事,只能说明多元化的大学你上成了高中,没有真正上明白大学,反而体现了你死板,不爱社交,没有别的突出能力) 2.实践经历:你想表达的意思没有说清楚。你是说你会个性化服务,还是你有实习经历。如果没有带来,经济收益,表彰,更好的发展前景,那你还不如说说提升了自己哪些技能。你说有人给你送锦旗我都能明白你优秀,但是你说你会xxxx,你说这话谁信,证据呢。 3.入伍经历:你描述的就是你的工作职责或者你应该做的,并没有体现出来你把这个事情做好了,而且入伍经历并不能证明你能干好你要应聘的工作,不如只写经历其余所有内容都不写。 4.荣誉技能:重点突出一下,但不要过多描述,这些荣誉的含金量懂得都懂。 重点:你要应聘什么工作(具体岗位,实习生不具体),你的期望薪资
点赞 评论 收藏
分享
我知道自己这个念头不好,但是真的很羡慕😭大家的父母长辈都能帮到自己吗?
大飞的诡术妖姬:父母都是普通打工人,身体也不好,能供我读到本科毕业很不容易,毕业以后帮衬心里会有罪恶感。虽然可能会错过很多风景,但还是想活的心安理得。
点赞 评论 收藏
分享
评论
2
5
分享

创作者周榜

更多
牛客网
牛客企业服务