• 结构体大小的计算往往是面试笔试常考的知识。对于简单的结构体,可以一眼看出来,对于复杂的结构体,该如何计算结构体占用内存的大小呢?
  • 本文学习所使用的编译器是gcc 4.4.5 使用其他编译器或者使用Windows 上的编译器有可能不太一样。

文章目录

  • 1 什么是内存对齐?
  • 2 struct占用的内存大小如何计算
    • 2.1 struct结构体大小计算案例分析-结构体中没有结构体
    • 2.2 struct结构体大小计算案例分析-结构体中有一个结构体成员
  • 3 总结

1 什么是内存对齐?

先来加单的说一下为什么需要内存对齐:

  1. CPU对内存的读取不是连续的,而是分成块读取的,块的大小只能是1,2,4,8,16…字节
  2. 当读取的数据未对齐,则需要两次总线周期来访问内存才能将整个数据读完,这样会降低CPU性能
  3. 某些硬件平台只能从规定的相对地址处读取特定类型的数据,否则产生硬件异常

什么是内存对齐?

  • 不同类型的数据在内存中按照一定的规则排列
  • 但是不一定是顺序的一个接一个的排列

例如下图中的两个结构体的大小是不一样的,因为它们的内存布局是不一样的:

它们的内存布局如下图:

拓展: #pragma pack 用于指定内存的对齐方式。用于修改编译器的默认对齐方式
一般来讲,在Linux系统中,编译器的默认对我方式是4字节对齐。下图中的代码,可以将对齐方式修改为1字节对齐:

2 struct占用的内存大小如何计算

对于不同的内存对齐方式,上面的结构体在内存中的布局是不一样的,那么我们如何来计算不同的对齐方式在内存中的布局是什么样的呢?

需要根据以下三点:

  1. 第一个struct成员永远起始于 0偏移处

  2. 每个成员按其类型大小和pack参数中较小的一个 进行对齐

    2.1 偏移地址必须能被对齐参数整除
    2.2 如果一个结构体中有一个变量也是结构体,那么这个内部的结构体成员的对齐大小就按照其内部最大的数据成员作为其大小来计算。(这里有点绕,看最后的一个例子就会明白)

  3. 结构体总长度,必须为所有对齐参数的整数倍

  • 只需要按照上述三点计算方法,就可以计算出所有结构体的大小以及内存布局的样式

2.1 struct结构体大小计算案例分析-结构体中没有结构体

如下面的代码:

  • 24-3.c
#include <stdio.h>#pragma pack(2)
struct Test1
{char  c1;short s;char  c2;int   i;
};
#pragma pack()#pragma pack(4)
struct Test2
{char  c1;char  c2;short s;int   i;
};
#pragma pack()int main()
{printf("sizeof(Test1) = %d\n", sizeof(struct Test1));printf("sizeof(Test2) = %d\n", sizeof(struct Test2));return 0;
}
  • 编译运行结果为:

sizeof(Test1) = 10
sizeof(Test2) = 8

  • 结果分析1–Test1:

对于结构体Test1,2字节对齐,按照上述三个计算条件有:

struct Test1
{              //对齐方式             大小         起始地址                占用的内存地址位置      char  c1;       2          大于     1            0                            0            short  s;       2          等于     2            2(被对齐参数2整除)            2~3           char  c2;       2          大于     1(对齐参数)   4                            4            int   i;        2(对齐参数) 小于     4            6(被对齐参数2整除)            6~9
};
  1. Test1的对齐方式是2字节。

然后根据:

  1. 每个成员按其类型大小和pack参数中较小的一个 进行对齐。比如上面的起始地址计算那里,都是被对齐参数2整除,这个2是类型大小和pack参数中较小的一个

    2.1 偏移地址必须能被对齐参数整除

这一条规则计算每个成员的起始地址。如上面的计算。

  1. 最后再看总体大小是否是对齐参数的整数倍。上面Test1 大小是10,是对齐参数2的整数倍
  • 结果分析2–Test2

对于结构体Test2,4字节对齐,按照上述三个计算条件有:

struct Test2
{              //对齐方式         大小           起始地址                   占用的内存地址位置      char  c1;       4      大于    1(对齐参数)       0                             0            char  c2;       4      大于    1(对齐参数)       1(被对齐参数1整除)             1           short s;        4      大于    2(对齐参数)       2(被对齐参数2整除)             2~3          int   i;        4      等于    4(对齐参数)       4(被对齐参数4整除)             5~7
};
  1. Test2 的对齐方式是4字节对齐

然后再根据:

  1. 每个成员按其类型大小和pack参数中较小的一个 进行对齐。比如上面的起始地址计算那里,对齐参数分别为1,1,2,4

    2.1 偏移地址必须能被对齐参数整除

这一条规则计算每个成员的起始地址。如上面的计算。

  1. 最后再看总体大小是否是对齐参数的整数倍。上面Test2 大小是8,是对齐参数4的整数倍

经过上面的两个结构体大小的计算,可以很容易的画出其内存图

2.2 struct结构体大小计算案例分析-结构体中有一个结构体成员

再看一个复杂的结构体大小的计算

  • 代码 24-4.c
#include <stdio.h>struct S1
{short a;long b;
};struct S2
{char c;struct S1 d;double e;
};int main()
{printf("sizeof(struct S1) = %d\n", sizeof(struct S1));printf("sizeof(struct S2) = %d\n", sizeof(struct S2));return 0;
}

运行结果为:

sizeof(struct S1) = 8
sizeof(struct S2) = 20

S1很好分析。下面我们分析S2

struct S2         //对齐方式          大小                     起始地址    占用的内存地址位置
{char c;           4      大于     1(对齐参数)                0                0 struct S1 d;      4      等于     4(这个是S1中最大参数大小)   4(被4整除)       4~11(S1实际大小为8) double e;         4(对齐)小于     8                         12(被4整除)      12~19 };

注意: 上面S2中的S1实际大小是8,那个4是作为与对齐方式比较时(选取较小的作为对齐参数)选取S1中最大的数据成员大小。

  1. S2 的对齐方式是4字节对齐

然后再根据:

  1. 每个成员按其类型大小和pack参数中较小的一个 进行对齐。比如上面的起始地址计算那里,对齐参数分别为0,4,12

    2.1 偏移地址必须能被对齐参数整除
    2.2 如果一个结构体中有一个变量也是结构体,那么这个内部的结构体成员的对齐大小就按照其内部最大的数据成员作为其大小来计算

这一条规则计算每个成员的起始地址。如上面的计算。

  1. 最后再看总体大小是否是对齐参数的整数倍。上面S2 大小是20,是对齐参数4的整数倍

我所使用的编译器gcc 4.4.5 不支持8字节对齐方式

3 总结

注意好好理解上述计算结构体大小的计算方法。然后自己画画内存图。

【C语言进阶深度学习记录】二十 结构体大小计算与结构体内存布局的详细方法相关推荐

  1. 【C语言进阶深度学习记录】十九 #pragma使用与分析

    文章目录 1 #pragma 概念简介 1.1 #pragma message 的用法 1.2 #pragma once 的用法 1.3 #pragma pack 的用法 1.31 struct占用的 ...

  2. 【C语言进阶深度学习记录】十六 静态库与动态库的创建与使用

    上一篇文章学习了编译的过程,点击链接查看:[C语言进阶深度学习记录]十五 编译过程简介,每一个C源文件编译后将会生成目标文件,那么这些目标文件,还需要链接起来,生成可执行文件. 文章目录 1 链接的意 ...

  3. 【C语言进阶深度学习记录】十 C语言中:struct的柔性数组和union分析

    本文并不讲C语言的基础 文章目录 1 空struct的大小 2 结构体与柔性数组 2.1 柔性数组的使用方法 2.2 柔性数组使用代码案例分析 3 C语言中的union分析 3.1 使用union判断 ...

  4. 【C语言进阶深度学习记录】十二 C语言中的:字符和字符串

    文章目录 1 C语言中的单引号和双引号 1.1 双引号带来的BUG 2 总结 1 C语言中的单引号和双引号 C语言中的单引号用来表示字符字面量 C语言中的双引号用来表示字符串字面量,存储于全局的只读存 ...

  5. 【C语言进阶深度学习记录】十五 编译过程简介

    文章目录 1 初识编译器 2 总结 1 初识编译器 我们平时口中所说的编译器,是广泛的编译器.实际上,编译器包括了以下四个部分: 一个C代码被编译为可执行代码,包括以下几个过程: 下面就对上述的各个过 ...

  6. 【C语言进阶深度学习记录】十四 C语言中 三目运算符和逗号表达式

    文章目录 1 三目运算符 1.1 三目运算符的返回类型的代码案例分析 2 逗号表达式 2.1 逗号表达式代码案例分析 2.2 如何用一行代码实现 strlen函数 3 总结 1 三目运算符 三目运算符 ...

  7. 【C语言进阶深度学习记录】十八 条件编译的使用与分析

    文章目录 1 基本概念 1.1 代码分析 1.2 通过命令行定义宏 2 #include 的本质 2.1 解决重复包含头文件的问题 3 条件编译的应用 4 总结 1 基本概念 条件编译的行为类似于C语 ...

  8. 【C语言进阶深度学习记录】二十六 C语言中的字符串与字符数组的详细分析

    之前有一篇文章是学习了字符和字符串的,可以与之结合学习:[C语言进阶深度学习记录]十二 C语言中的:字符和字符串 文章目录 1 字符串的概念 1.1 字符串与字符数组 1.2 字符数组与字符串代码分析 ...

  9. 【C语言进阶深度学习记录】三十五 程序中的堆、栈以及静态存储区(数据区)

    学习交流加 个人qq: 1126137994 个人微信: liu1126137994 学习交流资源分享qq群: 962535112 在我之前学习底层的知识的时候,也写过相关的内容.可以对比的学习:[软 ...

  10. 【C语言进阶深度学习记录】三十八 C/C++语言中的函数声明与函数定义

    文章目录 1 函数的声明和定义 1.1 代码分析 2 总结 1 函数的声明和定义 声明的意义在于告诉编译器程序单元的存在.只是告诉编译器它存在但是不在声明这里定义,有可能在当前文件中的其他地方或者其他 ...

最新文章

  1. clientdataset 用法
  2. 05-Java通过Executors提供四种线程池
  3. if条件的默认转换规则:
  4. Quartz.Net 2.0 bate1 使用
  5. 算法洗脑系列(8篇)——第四篇 枚举思想
  6. 将钉钉应用内浮窗_手机钉钉怎么设置悬浮窗 几步轻松开启
  7. 用HTML语言怎样打印出九九乘法表,jsp/javascript打印九九乘法表代码
  8. 如何开发YUI3的扩展
  9. golang struct数组排序_go语言中排序sort的使用方法示例
  10. java小球挡板游戏_多线程的一个小球游戏,就是以前的那个Pong游戏
  11. Ubuntu16.04安装gazebo8并加载模型库
  12. CalendarPicker
  13. kubernetes dev client-go 进入pod执行命令
  14. 什么是网关模块 工业物联网以太网网关WiFi模块的选型
  15. 小米8刷入Magisk24.0并安装riru和EdXposed之刷机篇
  16. 网页向女友告白和纪念日专用特效
  17. ssm 一对多的映射关系
  18. ibdp课程体系中要选择经济课吗?
  19. html base64 img 图片显示
  20. 耀之阳电商:店铺怎么样打造爆款

热门文章

  1. 第三十七期:刷脸支付叫好不叫座,为啥消费者和商家都不愿用先进科技?
  2. java学习(133):泛型
  3. 00_python安装与配置(mac)
  4. Extjs4开发中的一些问题
  5. JavaScript创建命名空间
  6. c语言 字符串map,C语言实现BitMap
  7. RabbitMQ六种队列模式-简单队列模式
  8. filter过滤器实现验证跳转_返回验证结果
  9. 2019金球奖——梅西
  10. Spring Boot SchedulingConfigurer定时执行任务(配置式反射调用)