C语言之结构体内存的对齐

guguguhuha 2021-03-07 原文


C语言之结构体内存的对齐

C语言之结构体内存的对齐
大纲:
   零.引例
   一.结构体内存对齐规则
   二.怎样计算结构体的大小
   三.设计结构体时要注意的方面
  四.为什么存在内存对齐
   五.修改默认对齐数

C语言之结构体内存的对齐

 

大纲:

   零.引例

   一.结构体内存对齐规则

   二.怎样计算结构体的大小

   三.设计结构体时要注意的方面

   四.为什么存在内存对齐

   五.修改默认对齐数

 

 

 

在前面的章节中,我们谈到了C语言中整数以及浮点数的储存

今天,我们来谈一谈一些关于结构体内存的知识。

 

我们先来看一个例子:

struct S1
{
    char c1;
    int i;
    char c2;
};

大家来猜猜这个结构体S1的内存是多少?

相信会有人给出 6 的结果,他们或许是这样想的,两个 char 类型分别为一个字节,一个 int 类型又为4个字节,加起来刚好为6个

但是

结果真是如此吗?

我们来看看运行结果:

 

 

 为什么呢,接下来我们就引出正文。

 

一.结构体内存对齐规则

首先,正如引例所示,结构体的内存并不是简简单单的将结构体各个成员的大小相加。

结构体的大小往往遵循着结构体的对齐规则:

  1. 第一个成员在与结构体变量偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
  3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

这里要注意的一点就是要解释一下这个对齐数的概念

对齐数:编译器默认的一个对齐数 与 该结构体变量成员自身大小的较小值。

  注:

    不是所有的编译器都有自己默认的对齐数。

    在VS下其默认的对齐数为8

    在linux下的默认值为4

 

二.怎样计算结构体的大小

在讲计算之前,我们继续来看一看上面的那个例子:

#define _CRT_SECURE_NO_WARNINGS

#include<stdio.h>
#include<stddef.h>

struct S1
{
    char c1;
    int i;
    char c2;
};

int main()
{
    printf("该结构体成员 c1 开始的位置为第 %d 个字节\n", offsetof(struct S1, c1));
    printf("该结构体成员 i 开始的位置为第 %d 个字节\n", offsetof(struct S1, i));
    printf("该结构体成员 c2 开始的位置为第 %d 个字节\n", offsetof(struct S1, c2));
    printf("该结构体所占的内存空间为 %d 个字节\n", sizeof(struct S1));
    return 0;
}

注:

  宏 offsetof() 可以计算出结构体各成员所相对开始位置的一个偏移量。

  偏移量 :  我们可以理解为把结构体变量第一个成员所储存的第一个位置置于0,以此递增 

我们来看看结果:

 

 

 这是为什么呢?

我们来看看上面所提到的结构体内存对齐规则:

 

 

 然后我们来看示意图:

 

 

 

此时,关于结构体的大小,我们应该清楚了不少,接下来,我们继续来看几道例题:

struct S2
{
    char c1;
    char c2;
    int i;
};
int main()
{
    printf("%d\n", sizeof(struct S2));
    return 0;
 }

我们看到,S1与S2的区别仅仅只是调换了一下各成员间的顺序,那它所占的内存还是刚才的值吗:

运行结果:

 

 

 我们继续来分析一下:

 

 

 趁此机会,我们再来巩固一下:

struct S3
{
    double d;
    char c;
    int i;
};

int main()
{
    printf("%d\n", sizeof(struct S3));
    return 0;
}

它的结果会是多少呢?

 

 

 不知道大家作对了吗?

解析:

   首先 double 类型占8个字节

   char 又展览接下来的一个

   而 int 的对齐数为 4,所以空3个字节从12开始

   而这个结构体的最大对齐数为8

   所以该结构体占 2*8 = 16个字节

 

最后,我们再来看一道嵌套结构体的例题:

struct S3
{
    double d;
    char c;
    int i;
};

struct S4
{
    char c1;
    struct S3 s3;
    double d;
};

int main()
{
    printf("%d\n", sizeof(struct S4));
    return 0;
}

它的结果又为多少呢?

 

 

 解析:

  我们先来看看规则 4: 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,

            结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

   S3的最大对齐数为 8,它的大小为 16 个字节

   首先,毋庸置疑的是 char 先放到首位

   接下来因为S3的对齐数为 8,所以S3放在了以位置8开始的16个字节

   最后是double,对齐数为8,所以放在了24的位置

   最后,该结构体的大小为 4*8 = 32 个字节

 

在进行结构体所占大小的计算中,我们又可以得到一个基本编程常识:

 

三.设计结构体时要注意的方面

在我们进行结构体的设计中,我们可以把一些所占空间小的,来凑到一起,提高资源的利用率。

正如上文所提到的例1与例2,结构体成员完全相同,但顺序不同,两个结构体的大小也截然不同

//例1
struct S1
{
    char c1;
    int i;
    char c2;
};

//例2
struct S2
{
    char c1;
    char c2;
    int i;
};

int main()
{
    printf("%d\n", sizeof(struct S1));//12
    printf("%d\n", sizeof(struct S2));//8
    return 0;
}

 

四.为什么存在内存对齐

对于这个原因,目前话没有一种完全正确的答案,但是:

 

大部分的参考资料都是如是说的:

  1. 平台原因(移植原因):

    不是所有的硬件平台都能访问任意地址上的任意数据的;

    某些硬件平台只能在某些地址 处取某些特定类型的数据,否则抛出硬件异常。

   2. 性能原因:

     数据结构(尤其是栈)应该尽可能地在自然边界上对齐。

     原因在于,为了访问未对齐的内存,处理器 需要作两次内存访问;而对齐的内存访问仅需要一次访问。

 

对于原因而我们来看一个示意图:

 

 

五.修改默认对齐数

我们通常使用如下的预处理命令来修改编译器的默认对齐数:

 #pragma pack()                                                                                                                  

 

 如果()里面不加数字,则默认为编译器的默认对齐数

 我们修改的时候,只需在()里加一个数字就行

 取消的时候再添加一次 #pragma pack()  即可

注:

  再()里添加的数字,我们通常加的都是2的多少次方

 

下面来举一个实例:

#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
    char c1;
    int i;
    char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为8
struct S2
{
    char c1;
    int i;
    char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认

int main() { //输出的结果是什么? printf("%d\n", sizeof(struct S1));//12 printf("%d\n", sizeof(struct S2));//6 return 0; }

 

由此可见,我们也可以通过修改默认对齐数来节约  结构体使用的空间。

 

 

 

关于结构体内存的讲解便到此为止。

笔者水平有限,若有错误之处,还望多多指正。

 

若有想回忆  C语言中整数以及浮点数是怎样储存的读者,可以参见 :C语言之数据在内存中的存储

 

发表于
2021-03-07 22:08 
guguguhuha 
阅读(0
评论(0
编辑 
收藏

 

版权声明:本文为guguguhuha原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/guguguhuha/p/14496813.html

C语言之结构体内存的对齐的更多相关文章

  1. 小议缓冲区溢出

            最近一个朋友学习信息安全方面的知识,然后发来一题和我一起讨论,虽然觉得简单,但是实际还是有点意 […]...

  2. Linux Kernel C语言编程范式

    内部DSL是嵌入到开发语言内部,与开发语言混合使用的DSL,它可以是一个接口,如printf,也可以是一个宏,如下示例。UNUSUAL_DEV呈现了2种信息,一种是设备id_table信息,用于驱动匹配,一种是unusual_dev_list,用于标示非标准设备。具体设计和实现细节可以参考《Linux设备驱动框架设计》一文中的“USB块设备驱动框架设计”小节,不再赘述。...

  3. C语言(面积计算器)

    简单面积计算器(自定义函数利用)  调试了好久也没有从default再次进入switch;   程序不能输入负 […]...

  4. 数据结构–链表

       网上关于链表的文章很多,比我写的好的前辈也多不胜数。工作一年总是感觉前面学的后面忘,于是就诞生了写博客的 […]...

  5. [源创] STM32F103ZET6 基于XMODEM 通讯的 BOOTLOADER案列IAP

    网上好多初学者 都想知道如何更好的用IAP,BOOTLOADER 功能 我给大家一个我自己的基于Xmodem的 […]...

  6. C语言多维数组的指针传递

    在C语言中为了节省空间,提高运行速度经常使用指针来完成数组的传递。 对于一维数组而言可以直接传递首地址 而对于 […]...

  7. 数据结构 集合_集合(数学)抽象数据类型的C语言实现

    链表是实现集合的一种理想的方式。将List以typedef的方式重命名为Set。这样做能保留链表简洁的特性,还 […]...

  8. C 存储类

        存储类定义 C 程序中变量/函数的范围(可见性)和生命周期。这些说明符放置在它们所修饰的类型之前。下面 […]...

随机推荐

  1. visualsvn的迁移及svn的一些问题

    从A服务器将visualsvn的内容迁移到B服务器的visualsvn。 1 在B服务器上安装visualsv […]...

  2. Hadoop(五)C#操作Hive

    HiveHive将HiveQL(类sql语言)转为MapReduce,完成数据的查询与分析,减少了编写MapReduce的复杂度。它有以下优点:学习成本低:熟悉sql就能使用良好的数据分析:底层基于MapReduce实现同样存在一些缺...

  3. Linux集群搭建

    集群分类:  HAC(高可用集群)  尽可能的保障网络通讯的正常状态  实现方案:   心跳检测  故障:   […]...

  4. 机器学习—核函数

    前言:当我跟你说起核的时候,你的脑海里一定是这样的: 想到的一定是BOOMBOOM。谈核色变,但是今天我们说的 […]...

  5. Android开发——Notification通知的使用及NotificationCopat.Builder常用设置API

    想要看全部设置的请看这一篇 【转】NotificationCopat.Builder全部设置 常用设置:   […]...

  6. java 抽象类 abstract

    package cn.sasa.com; //抽象类 被abstract修饰的类 public abstrac […]...

  7. 关于U盘“无法复制…磁盘被写保护…”的解决办法

      关于U盘“无法复制…磁盘被写保护…”的解决办法 相信很多午饭们在使用移动存储设备,如U盘、移动硬盘时,曾遇 […]...

  8. linux文件连接和传输工具

      链接:https://www.chiark.greenend.org.uk/~sgtatham/putty […]...

展开目录

目录导航