【C语言】关键字的深入理解(第六期)(上)

简介: 说起 struct 关键字,学过 C 语言的小伙伴应该都有了解,struct 也可以理解成自定义类型,我们知道 C 语言内置类型远远还不够满足我们的需求,假设说我们要录入一个学生的信息,简简单单的 int char 这些是完全不够的

1、struct 关键字的理解和柔性数组

说起 struct 关键字,学过 C 语言的小伙伴应该都有了解,struct 也可以理解成自定义类型,我们知道 C 语言内置类型远远还不够满足我们的需求,假设说我们要录入一个学生的信息,简简单单的 int char 这些是完全不够的,一个学生的信息拥有姓名,性别,年龄,身高,地址... 所以 C 语言就提供了 struct 可以使我们程序猿自定义想要的类型,我们就先简单看下它的语法吧:

但是我们通常在创建结构体的时候会用 typedef 来重命名一下,避免书写的复杂:

typedef struct stu
{
  char name[10];
  char sex;
  int age;
  float high;
  char addr[30];
}stu;
int main()
{
  stu s;
  return 0;
}

1.1 结构体传参问题

虽然现在我们还没讲到指针这个章节,所以这部分内容有指针基础的小伙伴可以看下,没基础的小伙伴可以等学完指针再回过头来看这个问题:

上面两个打印函数哪个函数会更好呢?

这里我们简单了解一个知识点,当结构体作为参数传递给函数时,我们可以选择传值调用或者是传址调用,简单来说,传值是需要在函数接收时在开辟一个相同大小的结构体来接收,(形参只是实参的一份临时拷贝)传址则只需要用一个同类型指针变量来即可,然后通过指针可以直接访问到这个结构体。

有了这样一个概念我们就应该能明白,显然是 Printf2 这个函数会更好一些!

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。 如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。

结论: 结构体传参的时候,要传结构体的地址。

1.2 柔性数组的理解与简单使用:

或许有很多小伙伴没听过柔性数组的概念,但它确实存在,C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组,但结构体中的柔性数组成员前必须至少有一个其他成员。包含柔性数成员的结构用 malloc( ) 函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

struct Data
{
  int num;
  int arr[];
};

定义好了之后,我们通常就可以使用了(简单使用):

通过以上代码我们可以发现,我们用 malloc( ) 函数申请了一个 struct Data 类型大小的空间,并加上 10 个整型的空间,这里我想问小伙伴们一个问题,此时如果我们用 sizeof 求结构体的大小它会是多大呢?

在 Linux 平台环境下我们也做了一个测试:

这里有小伙伴就纳闷了,我们明明已经给结构体里数组分配空间了啊,但是为何只占 4 个字节呢?

其实在定义这个结构体的时候,已经确定不包含柔性数组的内存大小了,柔性数组可以理解不占结构体的内存,只是说我们在使用柔性数组时需要把它当作结构体的一个成员!

没完全理解没关系,接下来我们用图解让小伙伴们可以更直观的理解柔性数组:

在看下面一张图片之前,我们得先了解 malloc 函数开辟的空间是在堆区开辟的,堆区是先使用低地址后使用高地址(栈区相反),数组的下标是随着地址的增长而增长的!

通过上面的讲解,相信你们已经了解了柔性数组这个概念,实在不了解也无所谓,这个东西确实不常用。

2、union 内存级布局理解

union 关键字的用法与 struct 关键字的用法非常相似,union 并不会为每一个数据成员分配空间,在 union 中所有的数据成员共用一个空间,联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。所有数据成员具有相同的起始地址

一个 union 只分配一个足够大的空间来容纳最大长度的数据成员,比如上面的例子,最大成员是 int,所以 union Data 大小就是 int 数据类型的大小! 而且,union 中的成员,同一时间只能存储其中一个数据成员!

上例注意:c 变量永远在 a 变量的低地址处!每一个都是第一个元素!

2.1大小端对 union 的影响:

前几期中我们了解了大小端的概念,既然联合体是共用内存的,而且联合体中每个元素都能称得上是第一个元素,我们不妨来思考如下这样一段代码:

显然打印小端的结果我们并不意外,但是这段代码内存中的布局是怎么样的呢?这跟大小端存储又有什么关系呢?

这里我们来回忆一下大小端存储的概念:

  • 大端:按照字节为单位,低权值位数据存储在高地址处;
  • 小端:按照字节为单位,低权值位数据存储再低地址处;

这里也就验证了我们之前的一个结论:在 union 中里面每个成员都是第一个元素!

2.2 内存对齐的概念:

可能有的小伙伴写过 union 包括 struct 的代码,在求这些自定义类型大小的时候, 可能求出的大小并不是我们想的一样,这就要考虑到我们的内存对齐了,这里我们不细讲,只是一笔带过,小伙伴感兴趣可以自行下来了解!

当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

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

VS 中默认的值为 8


相关文章
|
22天前
|
存储 数据可视化 编译器
【C语言】union 关键字详解
联合体(`union`)是一种强大的数据结构,在C语言中具有广泛的应用。通过共享内存位置,联合体可以在不同时间存储不同类型的数据,从而节省内存。在嵌入式系统、硬件编程和协议解析等领域,联合体的使用尤为常见。理解和正确使用联合体可以使代码更加高效和灵活,特别是在内存受限的系统中。
58 3
【C语言】union 关键字详解
|
22天前
|
编译器 C语言
【C语言】extern 关键字详解
`extern` 关键字在C语言中用于跨文件共享变量和函数的声明。它允许你在一个文件中声明变量或函数,而在其他文件中定义和使用它们。理解 `extern` 的使用可以帮助你组织和管理大型项目的代码。
62 3
|
22天前
|
C语言
【C语言】break 关键字详解
- `break` 关键字用于提前退出循环体或 `switch` 语句的执行。 - 在 `for`、`while` 和 `do-while` 循环中,`break` 可以帮助程序在满足特定条件时退出循环。 - 在 `switch` 语句中,`break` 用于终止 `case` 代码块的执行,避免代码“穿透”到下一个 `case`。 - 注意 `break` 只会退出最内层的循环或 `switch` 语句,确保在嵌套结构中正确使用 `break` 以避免意外的控制流行为。
72 2
|
22天前
|
传感器 安全 编译器
【C语言】enum 关键字详解
`enum`关键字在C语言中提供了一种简洁而高效的方法来定义一组相关的常量。通过使用枚举,可以提高代码的可读性、可维护性,并减少错误的发生。在实际应用中,枚举广泛用于表示状态、命令、错误码等,为开发者提供了更清晰的代码结构和更方便的调试手段。通过合理使用枚举,可以编写出更高质量、更易维护的C语言程序。
98 2
|
22天前
|
缓存 安全 编译器
【C语言】volatile 关键字详解
`volatile` 关键字在 C 语言中用于防止编译器对某些变量进行优化,确保每次访问该变量时都直接从内存中读取最新的值。它主要用于处理硬件寄存器和多线程中的共享变量。然而,`volatile` 不保证操作的原子性和顺序,因此在多线程环境中,仍然需要适当的同步机制来确保线程安全。
56 2
|
22天前
|
存储 编译器 程序员
【C语言】auto 关键字详解
`auto` 关键字用于声明局部变量的自动存储类,其作用主要体现在变量的生命周期上。尽管现代C语言中 `auto` 的使用较少,理解其历史背景和作用对于掌握C语言的存储类及变量管理仍然很重要。局部变量默认即为 `auto` 类型,因此在实际编程中,通常不需要显式声明 `auto`。了解 `auto` 关键字有助于更好地理解C语言的存储类及其在不同场景中的应用。
41 1
|
22天前
|
C语言
【C语言】continue 关键字详解
`continue` 关键字在 C 语言中用于跳过当前循环中的剩余代码,并立即开始下一次迭代。它主要用于控制循环中的流程,使程序在满足特定条件时跳过某些代码。
54 1
【C语言】continue 关键字详解
|
22天前
|
存储 C语言
【C语言】static 关键字详解
`static` 关键字在C语言中用于控制变量和函数的作用域和生命周期。它可以用于局部变量、全局变量和函数,具有不同的效果。理解 `static` 关键字的用法有助于封装和管理代码,提高代码的可维护性和可靠性。
36 3
|
22天前
|
C语言
【C语言】return 关键字详解 -《回家的诱惑 ! 》
`return` 关键字在 C 语言中用于终止函数的执行,并将控制权返回给调用者。根据函数的类型,`return` 还可以返回一个值。它是函数控制流中的重要组成部分。
54 2
|
22天前
|
C语言
【C语言】sizeof 关键字详解
`sizeof` 关键字在C语言中用于计算数据类型或变量在内存中占用的字节数。它是一个编译时操作符,对性能没有影响。`sizeof` 可以用于基本数据类型、数组、结构体、指针等,了解和正确使用 `sizeof` 对于内存管理和调试程序非常重要。
47 2