【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 函数更为优秀。原因是当函数传参的时候,参数是需要压栈的,在这个过程中就会产生时间和空间上的系统开销。如果传递一个结构体对象时结构体过大,那么将会导致参数压栈的的系统开销较大,最终将会导致程序性能的下降。

目录
相关文章
|
1天前
|
存储 C语言
C语言进阶⑮(自定义类型)(结构体+枚举+联合体)(结构体实现位段)(中)
C语言进阶⑮(自定义类型)(结构体+枚举+联合体)(结构体实现位段)
8 0
|
1天前
|
C语言 C++
C语言进阶⑭(内存函数_以字节操作)momcpy+mommove+memcmp+memset
C语言进阶⑭(内存函数_以字节操作)momcpy+mommove+memcmp+memset
5 0
|
1天前
|
C语言
C语言进阶⑬(字符串函数)+(指针编程题)strlen+strcpy+strcat+strstr+strtok+strerror(下)
C语言进阶⑬(字符串函数)+(指针编程题)strlen+strcpy+strcat+strstr+strtok+strerror
5 0
|
1天前
|
安全 C语言
C语言进阶⑬(字符串函数)+(指针编程题)strlen+strcpy+strcat+strstr+strtok+strerror(中)
C语言进阶⑬(字符串函数)+(指针编程题)strlen+strcpy+strcat+strstr+strtok+strerror
10 0
|
1天前
|
C语言
C语言进阶⑫(指针下)(指针和数组笔试题解析)(杨氏矩阵)(中)
C语言进阶⑫(指针下)(指针和数组笔试题解析)(杨氏矩阵)
11 0
|
1天前
|
C语言
C语言进阶⑫(指针下)(指针和数组笔试题解析)(杨氏矩阵)(上)
C语言进阶⑫(指针下)(指针和数组笔试题解析)(杨氏矩阵)
10 0
|
9月前
|
存储 C语言
【C语言】 条件操作符 -- 逗号表达式 -- []下标访问操作符,()函数调用操作符 -- 常见关键字 -- 指针 -- 结构体
【C语言】 条件操作符 -- 逗号表达式 -- []下标访问操作符,()函数调用操作符 -- 常见关键字 -- 指针 -- 结构体
|
8月前
|
C语言
【C语言】——define和指针与结构体初识
【C语言】——define和指针与结构体初识
|
11月前
|
存储 C语言
C语言初识-关键字-操作符-指针-结构体
C语言初识-关键字-操作符-指针-结构体
45 0
【C语言】指针,结构体,链表
【C语言】指针,结构体,链表