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