c语言结构体的内存对齐

简介: c语言结构体的内存对齐

前言:

c语言中结构体的空间大小怎么算呢?就是把里面所有变量的字节大小全部加起来吗?

如果你这么想,说明你对结构体还不够了解,而要想知道结构体开辟空间的规则,那我们就必须先要知道内存对齐是什么。

接下来我就说说内存对齐是个啥玩意吧!

1.结构体内存对齐

举例:

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

思考:为什么s1结构体和s2结构体只是声明的成员变量顺序不一样,但是求得的空间大小却有差异呢?

首先我们要明白结构体内存对齐的规则。

1.1结构体对齐规则:

1. 第一个成员在与结构体变量偏移量为0的地址处。

2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8

3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。

4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

我们首先来观察结构体s1:

struct s1 {
  char c1;
  int a;
  char c2;
}s1;

根据对齐规则,我们第一个成员变量在与结构体变量偏移量为0的地址处,也就是在0这个位置开始。

注意,图中的编号仅代表地址的相对位置。

然后再看第二个成员变量 int a,a的字节大小为4,4比8小,所以a的对齐数是4,那么我们就在上一个成员变量的位置往下找到a对齐数整数倍的位置,于是我们把a放在4这个位置上。

再然后呢,根剧对齐规则,最后一个成员变量的相对位置放在8这个位置上(char c2的对齐数为1,8是1的整数倍数)。

所以最终结构体s1的成员变量存放的相对位置为:

那么这个时候的整个结构体的空间大小是什么呢?是不是最后一个成员变量的位置呢?

显然不是看最后一个成员变量所占最后一个字节的位置,因为这样一来sizeof (s1)=8,而这是错误的。

结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。

根据这一规则,我们先算出来结构体的最大对齐数,也就是a的对齐数,即4。

从第一个变量所占字节的第一个位置,到最后一个成员变量所占字节的最后一个位置,我们算出来至少需要9个字节才能把装下这三个成员变量(0-8),而大于或者等于9又是4的整数倍的第一个数是12,所以我们得到的结构体s1的空间大小是12个字节。同理观察结构体s2:

根据以上的对齐规则,我们很容易得到s2成员变量的内存结构位置,

算出来结构体的最大对齐数,也就是a的对齐数,即4。

从第一个变量所占字节的第一个位置,到最后一个成员变量所占字节的最后一个位置,我们算出来至少需要8个字节才能把装下这三个成员变量(0-7),而大于或者等于8又是4的整数倍的第一个数是8,所以我们得到的结构体s1的空间大小是8个字节。

根据内存规则我们很快就知道了为什么sizeof(s1)和sizeof(s2)的大小,那内存对齐的意义是什么呢?

1.2为什么会存在内存对齐?

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

不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特 定类型的数据,否则抛出硬件异常。

2. 性能原因:

数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访 问。 

这里我们来举一个例子:

struct s1 {
  char c1;
  int a;
}s1;

如果不遵循内存对齐原则:

第一次读取我们可以顺利读取到变量c1.

第二次读取我们只能再找到整型a的前三个字节

第三次读取我们才能找到a的最后一个字节

所以,如果没有内存对齐,我们要想完整的找到这两个成员变量需要读取三次。

如果遵循内存对齐原则:

第一次读取我们就能找到变量c1.

而第二次读取我们就能找到变量a了

1.3总结:

结构体的内存对齐是拿空间来换取时间的做法。虽然根据内存对齐可能会开辟不必要的空间,但是为了保障读取数据的速度,拿空间换时间是值得的!

其实,在设计结构体的时候,我们的目的是既要满足对齐,又要节省空间,这又如何做到呢?

我们可以通过调整成员变量声明的顺序来减少结构体因为内存对齐的空间亏损。

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

S1和S2类型的成员一模一样,但是S1和S2所占空间的大小有了一些区别。

1.4 修改默认对齐数

vs的默认对齐数是8,那么我们可不可以修改默认对齐数呢?

之前我们见过了 #pragma 这个预处理指令,这里我们再次使用,可以改变我们的默认对齐数。\

#include<stdio.h>
#pragma pack(2)//修改默认对齐数为2
struct s1 {
  char c1;
  int a;
  char c2;
}s1;
#pragma pack()取消设置的默认对齐数,还原为默认
struct s2 {
  char c1;
  char c2;
  int a;
}s2;
int main() {
  printf("%d %d\n", sizeof s1, sizeof s2);
 
  return 0;
}

当我们修改默认对齐数,并且只影响结构体s1,那此时的sizeof(s1)又是多少呢?

答案是8,过程自己去算。

相关文章
|
1月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
|
19天前
|
存储 C语言
C语言程序设计核心详解 第九章 结构体与链表概要详解
本文档详细介绍了C语言中的结构体与链表。首先,讲解了结构体的定义、初始化及使用方法,并演示了如何通过不同方式定义结构体变量。接着,介绍了指向结构体的指针及其应用,包括结构体变量和结构体数组的指针操作。随后,概述了链表的概念与定义,解释了链表的基本操作如动态分配、插入和删除。最后,简述了共用体类型及其变量定义与引用方法。通过本文档,读者可以全面了解结构体与链表的基础知识及实际应用技巧。
|
24天前
|
存储 大数据 C语言
C语言 内存管理
本文详细介绍了内存管理和相关操作函数。首先讲解了进程与程序的区别及进程空间的概念,接着深入探讨了栈内存和堆内存的特点、大小及其管理方法。在堆内存部分,具体分析了 `malloc()`、`calloc()`、`realloc()` 和 `free()` 等函数的功能和用法。最后介绍了 `memcpy`、`memmove`、`memcmp`、`memchr` 和 `memset` 等内存操作函数,并提供了示例代码。通过这些内容,读者可以全面了解内存管理的基本原理和实践技巧。
|
24天前
|
缓存 Linux C语言
C语言 多进程编程(六)共享内存
本文介绍了Linux系统下的多进程通信机制——共享内存的使用方法。首先详细讲解了如何通过`shmget()`函数创建共享内存,并提供了示例代码。接着介绍了如何利用`shmctl()`函数删除共享内存。随后,文章解释了共享内存映射的概念及其实现方法,包括使用`shmat()`函数进行映射以及使用`shmdt()`函数解除映射,并给出了相应的示例代码。最后,展示了如何在共享内存中读写数据的具体操作流程。
|
24天前
|
C语言
c语言中的结构体
本文档详细介绍了C语言中结构体的使用方法,包括结构体的基本定义、变量声明与赋值、数组与指针的应用,以及结构体嵌套、与`typedef`结合使用等内容。通过示例代码展示了如何操作结构体成员,并解释了内存对齐的概念。
|
30天前
|
C语言
C语言结构体赋值的四种方式
本文总结了C语言结构体的四种赋值方式,并通过示例代码和编译运行结果展示了每种方式的特点和效果。
47 6
|
1月前
|
编译器 程序员 Linux
【C语言篇】结构体和位段详细介绍
跟结构相⽐,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。
|
1月前
|
存储 程序员 C语言
【C语言】动态内存管理
【C语言】动态内存管理
|
1月前
|
存储 C语言
C语言------结构体和共用体
这篇文章是关于C语言中结构体和共用体的实训,通过示例代码演示了结构体的定义、赋值、使用,以及如何使用结构体变量进行数据的组织和操作,包括输入、排序、求平均分和查找学生信息等功能。
C语言------结构体和共用体
|
1月前
|
C语言
C语言动态内存管理
C语言动态内存管理
30 4

热门文章

最新文章