【c语言进阶】结构体最常用知识点大全 中

简介: 【c语言进阶】结构体最常用知识点大全 中

1.6:结构体的内存对齐(超重点!!★★★):

1.6.1:结构体的数据在内存中如何存储的?

经过上面的学习,我们就已经基本掌握了结构体的使用了。接下来我们将要深入研究结构体大小的计算过程,即结构体内存对齐,而这也是近年来许多公司面试与笔试中的热门考点。

我们先来看看下面这段计算结构体变量大小的代码:

struct S1
{
  char c1;
  int i;
  char c2
};
struct S2
{
  char c1;
  char c2;
  int i;
};
int main()
{
  printf("%d\n", sizeof(struct S1));
  printf("%d\n", sizeof(struct S2));
  return 0;
}
//结果:
12
8

这里计算结果显示struct S1的大小是12个字节,而struct S2的结果是8个字节,为什么是这样的结果呢?

 要想弄清楚究竟是如何进行结构体变量大小计算的,我们首先得掌握结构体的对齐规则:

第一个成员在结构体变量偏移量为0的位置(偏移量:该成员的存放地址与结构体空间起始地址之间的距离)

其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

对齐数=编译器默认的一个对齐数与该成员自身大小的较小值

vs中的默认对其数是8,Linux和gcc都没有默认对齐数,对齐数就是成员自身大小

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

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

结构体成员中如果有数组,可以这样来理解:比如结构体中有一个int c[5];,其实这就相当于这个结构体中放了5个int型的变量。

 知晓了结构体的对齐规则,我们再回过头来分析上面的结构体变量大小计算过程。下面,我们以结构体变量 test1 中为例展示结构体的对齐存储。

第一个成员为占据一个字节的 char 类型变量 a,我们按照规则将其放置在偏移量为0,即结构体空间的起始位置.

第二个成员为占据4个字节的 int 类型变量 b,按照规则我们首先要计算它的对齐数,我们将变量 b 的大小4与对齐数默认值8进行比较,得出较小值为4,即对齐数为4,于是我们将它放在对齐数的整数倍处,即最近位置第四字节处。

第三个结构体成员占据1个字节的 char 类型变量 c,同样按照规则我们计算出它的对齐数为1,并将它放在对齐数的整数倍处,即最近位置第九字节处:

最后,根据规则,结构体的总大小为最大对齐数的整数倍,而这三个变量中,对齐数最大的是 int 类型变量的对齐数4,则总大小应当为4的倍数。而既为4的倍数,又要能够容纳所有的结构体成员,最小的结构体大小应当为12个字节,即为结构体变量 test1 的大小。

同理各位小伙伴们下去以后可以自己尝试推算结构体变量 test2 的大小并进行验证。

1.6.2:为什么存在内存对齐?

但是我们发现,这样的方式造成了很大程度上的空间浪费,以 test1 为例,12个字节的大小中有六个字节的空间申请了但却没有被使用。那么为什么还要采用这样的办法呢?主要有以下两个原因:

平台原因(移植原因):

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

性能原因:

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

总的来说,结构体的内存对齐是拿空间来换取时间的做法

那么在设计结构体的时候如何实现节省空间呢?

让占用空间小的成员尽量集中在一起。

1.7:修改默认对齐数:

在我们的代码编写过程中,默认的对齐数可能会不够合适。而当这个时候,我们就可以通过使用下面这个预处理指令来修改我们的默认对齐数:

#pragma pack(8)
//修改默认对齐数为8

我们也可以通过该指令在修改过默认对齐数之后,取消设置的默认对齐数,将其还原:

#pragma pack()
//取消设置的默认对齐数,还原为默认

1.8:结构体传参:

结构体传参与函数传参类似,没有什么疑难点,我们直接来看下面的示例:

//结构体传参
struct S
{
  int data[4];
  int num;
};
//直接传结构体变量
void print1(struct S s)//实参传的是一个结构体变量,所以形参用一个对应的结构体变量来接收
{
  int i = 0;
  for (i = 0; i < 4; i++)
  {
    printf("%d ", s.data[i]);
  }
  printf("%d\n", s.num);
}
//传结构体的地址
void print2(struct S* ps)//实参传的是结构体变量的地址,所以形参用一个对应类型的结构体指针来接收
{
  int i = 0;
  for (i = 0; i < 4; i++)
  {
    printf("%d ", ps->data[i]);
  }
  printf("%d\n", ps->num);
}
int main()
{
  struct S s = { {1,2,3,4}, 1000 };
  print1(s);  //传结构体
  print2(&s); //传地址
  return 0;
}

而在上面这段代码中,我们一般认为 Print2 函数更为优秀。原因是当函数传参的时候,参数是需要压栈的,在这个过程中就会产生时间和空间上的系统开销。如果传递一个结构体对象时结构体过大,那么将会导致参数压栈的的系统开销较大,最终将会导致程序性能的下降。

目录
相关文章
|
23天前
|
存储 C语言
如何在 C 语言中实现结构体的深拷贝
在C语言中实现结构体的深拷贝,需要手动分配内存并逐个复制成员变量,确保新结构体与原结构体完全独立,避免浅拷贝导致的数据共享问题。具体方法包括使用 `malloc` 分配内存和 `memcpy` 或手动赋值。
30 10
|
22天前
|
存储 大数据 编译器
C语言:结构体对齐规则
C语言中,结构体对齐规则是指编译器为了提高数据访问效率,会根据成员变量的类型对结构体中的成员进行内存对齐。通常遵循编译器默认的对齐方式或使用特定的对齐指令来优化结构体布局,以减少内存浪费并提升性能。
|
27天前
|
编译器 C语言
共用体和结构体在 C 语言中的优先级是怎样的
在C语言中,共用体(union)和结构体(struct)的优先级相同,它们都是用户自定义的数据类型,用于组合不同类型的数据。但是,共用体中的所有成员共享同一段内存,而结构体中的成员各自占用独立的内存空间。
|
27天前
|
存储 C语言
C语言:结构体与共用体的区别
C语言中,结构体(struct)和共用体(union)都用于组合不同类型的数据,但使用方式不同。结构体为每个成员分配独立的内存空间,而共用体的所有成员共享同一段内存,节省空间但需谨慎使用。
|
1月前
|
编译器 C语言 C++
C语言结构体
C语言结构体
25 5
|
1月前
|
编译器 Linux C语言
C语言 之 结构体超详细总结
C语言 之 结构体超详细总结
19 0
|
1月前
|
存储 编译器 Linux
深入C语言:探索结构体的奥秘
深入C语言:探索结构体的奥秘
|
1月前
|
存储 编译器 C语言
c语言回顾-结构体(2)(下)
c语言回顾-结构体(2)(下)
28 0
|
1月前
|
存储 编译器 程序员
c语言回顾-结构体(2)(上)
c语言回顾-结构体(2)(上)
28 0
|
1月前
|
存储 C语言
c语言回顾-结构体
c语言回顾-结构体
16 0