当前位置: 首页 > article >正文

3. 自定义类型****

目录

1. 内存对齐(必考)

如何计算?

为什么要内存对齐?

2. 联合

2.1 联合的定义

2.2 联合的特点


 

1. 内存对齐(必考)

结构体内存对齐是一个特别热门的考点。

如何计算?

  1. 第一个成员在与结构体偏移量为0的地址处
  2. 其他成员变量要对齐到对齐数的整数倍的地址处,对齐数=编译器默认的一个对齐数 与 该成员大小的较小值
  3. 结构体总大小为最大对齐数的整数倍,每个成员变量都有一个对齐数
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍,结构体的整体大小就是所有最大对齐数(含嵌套的)的整数倍。

举几个例子:

struct s1
{
    char c1;
    int i;
    char c2;
};

printf("%zu\n", sizeof(struct s1)); 12

内存图:

+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| c1  | 填  | 填  | 填  |      i (4 字节)        | c2  | 填  | 填  | 填  |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|  0  |  1  |  2  |  3  |  4  |  5  |  6  |  7  |  8  |  9  | 10  | 11  |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
  • c1(1 字节):位于偏移量 0。占用 1 个字节。

  • 为了对齐 int i,编译器在 c1 后插入了 3 个填充字节。int的大小是4,默认对齐数是8,他们的较小值是4,所以 i 要对齐到对齐数也就是4的整数倍的地址(4,8,12,16...)

  • c2,对齐数是1,1的整数倍8就可以放。

  • 总大小为最大对齐数的整数倍,char是1, int是4,最大对齐数是4,对齐到4的整数倍12。

struct s2
{
    char c1;
    char c2;
    int i;
};

printf("%zu\n", sizeof(struct s2)); 8
+-----+-----+-----+-----+-----+-----+-----+-----+
| c1  | c2  | 填  | 填  |      i (4 字节)        |
+-----+-----+-----+-----+-----+-----+-----+-----+
|  0  |  1  |  2  |  3  |  4  |  5  |  6  |  7  |
+-----+-----+-----+-----+-----+-----+-----+-----+
struct s3
{
    double d;    8
    char c;      1
    int i;       4
};

printf("%zu\n", sizeof(struct s3));  16
偏移量  内容
-----  -----
  0     d (字节 0)
  1     d (字节 1)
  2     d (字节 2)
  3     d (字节 3)
  4     d (字节 4)
  5     d (字节 5)
  6     d (字节 6)
  7     d (字节 7)
  ----------------------
  8     c
  ----------------------
  9     填充
 10     填充
 11     填充
  ----------------------
 12     i (字节 0)
 13     i (字节 1)
 14     i (字节 2)
 15     i (字节 3)
  ----------------------
struct s4
{
    char c1;        1
    struct s3 s3;   8
    char c2;        1
};

printf("%zu\n", sizeof(struct s4)); 32

s3的大小是16,默认对齐数是8,对齐数是较小值 = 8。从8的整数倍--8开始。

偏移量  内容
-----  -----
  0     c1
  1     填充
  2     填充
  3     填充
  4     填充
  5     填充
  6     填充
  7     填充
  ----------------------
  8     s3
  9     s3
 10     s3
 11     s3
.....
 23     s3
  ----------------------
 24     d
 25     d 
 26     d 
 27     d 
.....

 31     d

d的对齐数是8,从8的倍数开始,3*8=24,所以从24开始到31.

总大小为最大对齐数8的整数倍,4*8=32

为什么要内存对齐?

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

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

比如,一次访问4个字节,但是没有对齐,一个int可能上面存了2字节,下面存了两个字节,正好被访问从中间截断了,需要访问两次。

总体来说:
        结构体的内存对齐是拿空间换时间的做法。所以在涉及结构体时,要让占用小的成员尽量集中在一起,就像前面的s1和s2,成员一摸一样,但是占用空间不同。

2. 联合

2.1 联合的定义

        联合是一种特殊的自定义类型,这种类型定义的变量也包含一系列的成员,特征是这些成员共用一块空间(所以联合也叫共用体)

union Un
{
    char c;
    int i;
};

union Un un;

printf("%zu\n", sizeof(un)); 4

我们发现c和i共用了空间,大小为4。

2.2 联合的特点

成员共用一块内存空间,后存的会把先存的覆盖掉,一个联合变量的大小,至少是最大成员的大小(至少得有能力保存最大的那个成员),当最大成员大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍。

union Un2
{
    int i;
    char c;
};

union Un2 un2;

int main()
{
    printf("%zu\n", sizeof(un2));    4
    printf("%p\n", &(un2.i));    00007ff7626ff034
    printf("%p\n", &(un2.c));    00007ff7626ff034
    un2.i = 0x12345678;
    printf("%x\n", un2.i);    12345678
    un2.c = 0x00000012;
    printf("%x\n", un2.i);    12345612
}

由于联合体的成员共享同一块内存,因此 un2.iun2.c 的地址是相同的

  • 首先将 un2.i 赋值为 0x12345678

  • 然后将 un2.c 赋值为 0x00000012

由于联合体的成员共享内存,修改 un2.c 会覆盖 un2.i 的第一个字节(最低字节)

  • 由于 un2.c 修改了 un2.i 的第一个字节,un2.i 的值会发生变化。

  • 具体分析:

    • 初始值:un2.i = 0x12345678

    • 修改 un2.c 后,un2.i 的第一个字节被覆盖为 0x12

    • 最终值:un2.i = 0x12345612

  • 输出为 12345612

这里就可以引出大小端存储

面试题:判断当前计算机的大小端存储

可以用联合解决:

union Un
{
    char c;
    int i;
}un;

int main()
{
    un.i = 1;
    if(un.c ==1)
        printf("小端""\n");
    else
        printf("大端""\n");
    return 0;
}

小端序(Little Endian)

  • 低字节存储在低地址,高字节存储在高地址。

  • un.i = 1 的内存布局:

地址     值
----    ----
0x00    01  // 低字节
0x01    00
0x02    00
0x03    00  // 高字节

un.c 访问的是 0x00 地址的字节,值为 1,因此 un.c == 1 为真。

大端序(Big Endian)

  • 高字节存储在低地址,低字节存储在高地址。

  • un.i = 1 的内存布局:

地址     值
----    ----
0x00    00  // 高字节
0x01    00
0x02    00
0x03    01  // 低字节

un.c 访问的是 0x00 地址的字节,值为 0,因此 un.c == 1 为假。


http://www.kler.cn/a/582386.html

相关文章:

  • 批量给 Excel 表格添加文字和图片水印
  • Java程序开发之Spring Boot快速入门:5分钟搭建RESTful API
  • 以下是基于文章核心命题打造的15个标题方案,根据传播场景分类推荐
  • 在线商城服务器
  • c++介绍线程的屏障 八
  • C++入门——内联函数、auto关键字、基于范围的for循环
  • FastDDS中Utils定义的那些数据结构(二)
  • 【TMS570LC4357】之工程创建
  • UE5.5 Niagara 发射器粒子更新模块
  • 基于SpringBoot的租房管理系统实现与设计
  • 用C# Newtonsoft.Json库实现JSON数据中某个字段值的提取
  • LLM最新的模型微调技术有哪些
  • 爬虫一些基础知识的备忘录(需要自取)
  • 【Academy】SSRF ------ Server-side request forgery
  • 2025年是Matter智能家居至关重要的一年?
  • NetAssist 5.0.14网络助手基础使用及自动应答使用方案
  • 鸿蒙 Java 人工智能 嵌入式 测试运维简单分析
  • Android Retrofit 框架适配器模块深入源码分析(五)
  • windows版本的时序数据库TDengine安装以及可视化工具
  • 元宇宙:虚实融合中的消费新空间探析