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,过程自己去算。

相关文章
|
9天前
|
存储 Java 程序员
结构体和类的内存管理方式在不同编程语言中的表现有何异同?
不同编程语言中结构体和类的内存管理方式既有相似之处,又有各自的特点。了解这些异同点有助于开发者在不同的编程语言中更有效地使用结构体和类来进行编程,合理地管理内存,提高程序的性能和可靠性。
16 3
|
11天前
|
存储 缓存 Java
结构体和类在内存管理方面的差异对程序性能有何影响?
【10月更文挑战第30天】结构体和类在内存管理方面的差异对程序性能有着重要的影响。在实际编程中,需要根据具体的应用场景和性能要求,合理地选择使用结构体或类,以优化程序的性能和内存使用效率。
|
11天前
|
存储 缓存 算法
结构体和类在内存管理方面有哪些具体差异?
【10月更文挑战第30天】结构体和类在内存管理方面的差异决定了它们在不同的应用场景下各有优劣。在实际编程中,需要根据具体的需求和性能要求来合理选择使用结构体还是类。
|
22天前
|
存储 C语言
如何在 C 语言中实现结构体的深拷贝
在C语言中实现结构体的深拷贝,需要手动分配内存并逐个复制成员变量,确保新结构体与原结构体完全独立,避免浅拷贝导致的数据共享问题。具体方法包括使用 `malloc` 分配内存和 `memcpy` 或手动赋值。
30 10
|
20天前
|
C语言
【c语言】动态内存管理
本文介绍了C语言中的动态内存管理,包括其必要性及相关的四个函数:`malloc`、``calloc``、`realloc`和`free`。`malloc`用于申请内存,`calloc`申请并初始化内存,`realloc`调整内存大小,`free`释放内存。文章还列举了常见的动态内存管理错误,如空指针解引用、越界访问、错误释放等,并提供了示例代码帮助理解。
32 3
|
21天前
|
存储 大数据 编译器
C语言:结构体对齐规则
C语言中,结构体对齐规则是指编译器为了提高数据访问效率,会根据成员变量的类型对结构体中的成员进行内存对齐。通常遵循编译器默认的对齐方式或使用特定的对齐指令来优化结构体布局,以减少内存浪费并提升性能。
|
26天前
|
编译器 C语言
共用体和结构体在 C 语言中的优先级是怎样的
在C语言中,共用体(union)和结构体(struct)的优先级相同,它们都是用户自定义的数据类型,用于组合不同类型的数据。但是,共用体中的所有成员共享同一段内存,而结构体中的成员各自占用独立的内存空间。
|
26天前
|
存储 C语言
C语言:结构体与共用体的区别
C语言中,结构体(struct)和共用体(union)都用于组合不同类型的数据,但使用方式不同。结构体为每个成员分配独立的内存空间,而共用体的所有成员共享同一段内存,节省空间但需谨慎使用。
|
30天前
|
编译器 C语言 C++
C语言结构体
C语言结构体
25 5
|
22天前
|
存储 C语言
【c语言】字符串函数和内存函数
本文介绍了C语言中常用的字符串函数和内存函数,包括`strlen`、`strcpy`、`strcat`、`strcmp`、`strstr`、`strncpy`、`strncat`、`strncmp`、`strtok`、`memcpy`、`memmove`和`memset`等函数的使用方法及模拟实现。文章详细讲解了每个函数的功能、参数、返回值,并提供了具体的代码示例,帮助读者更好地理解和掌握这些函数的应用。
19 0