(学习笔记)3.9 异质的数据结构(3.9.1 结构)

文章目录


线索栏

  1. C语言中 struct(结构体)是什么?它的主要目的是什么?
  2. 结构体的所有成员在内存中如何排列?指向结构体的指针值是什么?
  3. 如何通过结构体变量和结构体指针访问其成员?->操作符是什么的简写?
  4. 偏移量与编译:结构体成员的"偏移量"是什么?编译器如何利用它生成访问成员的代码?
  5. 如何生成一个指向结构体内部某个成员(或数组成员)的指针?
  6. 结构体可以包含其他结构体吗?其内存布局如何?
  7. 如何根据结构体声明计算各字段的偏移量和总大小?如何根据汇编代码反推结构体初始化语句?
  8. 给定一个遍历链表的汇编代码,如何反推出其操作的数据结构(链表)和函数逻辑(求和)?

笔记栏

1. 结构体基础

(1)定义:struct是一种将不同类型的对象聚合到一个单元中的数据类型。用于表示一个逻辑实体。

(2)声明示例:

c 复制代码
struct rect { // 表示一个长方形
    long llx;         // 左下角x坐标
    long lly;         // 左下角y坐标
    unsigned long width;  // 宽
    unsigned long height; // 高
    unsigned color;       // 颜色
};

(3)使用:

c 复制代码
struct rect r; // 声明变量
r.llx = 0;     // 通过 '.' 操作符访问成员
r.color = 0xFF00FF;
// 声明并初始化
struct rect r2 = {0, 0, 10, 20, 0xFF00FF};

2. 结构体指针与成员访问

1)指针传递

为避免复制开销,常传递结构体指针。

2)成员访问

(1)(*rp).width:先解引用指针,再访问成员。括号必需。

(2)rp->width:等价于上者,是更简洁的写法。

3)示例函数

c 复制代码
long area(struct rect *rp) { return rp->width * rp->height; }
void rotate_left(struct rect *rp) {
    long t = rp->height;
    rp->height = rp->width;
    rp->width = t;
    rp->llx -= t;
}

3. 内存布局、偏移量与代码生成

1)连续存储

所有成员在内存中连续存放。结构体指针指向第一个字节的地址。

2)偏移量

每个成员相对于结构体起始地址的字节距离。编译器在编译时计算并记录所有偏移量。

3)示例分析(struct rec)

c 复制代码
struct rec { int i; int j; int a[2]; int *p; };
成员 类型 大小 偏移量
i int 4 0
j int 4 4
a[0] int 4 8
a[1] int 4 12
p int* 8 16

4)总大小

24 字节(注意末尾可能有填充,但此处刚好对齐)。

5)汇编代码生成

访问成员 = 基址 + 偏移量。

(1)设 struct rec *r在 %rdi

(2)r->j = r->i;被编译为:

c 复制代码
movl (%rdi), %eax   # 从偏移0读取 r->i
movl %eax, 4(%rdi)  # 存入偏移4,即 r->j

4. 生成指向结构体内部成员的指针

1)原理

地址 = 结构体基址 + 成员偏移量。

2)示例

(1)&(r->a[1]):偏移 = 8 + 4 * 1 = 12。汇编:leaq 12(%rdi), %rax

(2)&(r->a[i]):偏移 = 8 + 4*i。汇编:leaq 8(%rdi, %rsi, 4), %rax(i在%rsi)

5. 嵌套结构体

(1)声明:结构体可以包含其他结构体作为成员。

(2)内存:内嵌结构体的成员按其自身规则连续存放,并作为整体嵌入到外层结构体中。

6. 练习题

练习题3.41

A. 偏移量计算:

c 复制代码
struct prob {
    int *p;         // 指针,8字节,偏移0
    struct {        // 内嵌结构体,含两个int
        int x;      // 4字节,偏移8
        int y;      // 4字节,偏移12
    } s;
    struct prob *next; // 指针,8字节,偏移16
};

(1)p: 0

(2)s.x: 8

(3)s.y: 12

(4)next: 16

B. 总字节数:最后一个成员 next偏移16,加自身大小8,总 24字节。

C. 反推 sp_init代码:

已知 sp在 %rdi,分析汇编:

(1)movl 12(%rdi), %eax:读取偏移12 -> sp->s.y到 %eax。

(2)movl %eax, 8(%rdi):将值存入偏移8 -> sp->s.x。所以 sp->s.x = sp->s.y;。

(3)leaq 8(%rdi), %rax:计算偏移8的地址 -> &(sp->s.x)。

(4)movq %rax, (%rdi):将该地址存入偏移0 -> sp->p。所以 sp->p = &(sp->s.x);。

(5)movq %rdi, 16(%rdi):将 sp(自身地址) 存入偏移16 -> sp->next。所以 sp->next = sp;。

补全的C代码:

c 复制代码
void sp_init(struct prob *sp) {
    sp->s.x = sp->s.y;
    sp->p = &(sp->s.x);
    sp->next = sp;
}

练习题3.42

给定:结构体 ELE包含 long v和 struct ELE *p。函数 fun的汇编如上。

A. 逆向C代码:

分析汇编控制流:这是一个循环,%rdi作为指针 ptr在移动(第6行 movq 8(%rdi), %rdi),每次将 ptr->v加到 %rax(第5行),直到 ptr为 NULL(第8行测试)。这是典型的链表遍历。

c 复制代码
long fun(struct ELE *ptr) {
    long result = 0;
    while (ptr != NULL) {
        result += ptr->v;
        ptr = ptr->p;
    }
    return result;
}

B. 数据结构与操作:

(1)数据结构:ELE结构实现了一个单链表。v是节点存储的值,p是指向下一个节点的指针。

(2)fun的操作:函数 fun遍历这个链表,将所有节点的 v值求和,并返回总和。


总结栏

本节深入剖析了C语言结构体的内存表示、访问机制及其在机器代码中的实现,是理解复杂数据组织的基石。

  1. 结构体是连续的内存块:编译器按照声明顺序,在连续内存中布局各字段,并计算每个字段的固定偏移量。所有访问都通过"基址+偏移"完成。
  2. 偏移量是编译时常数:这是结构体访问高效的关键。机器指令中直接编码偏移量,运行时无额外开销。编译器完全负责字段名到偏移量的映射。
  3. 指针访问是标准模式:传递结构体指针(而非副本)是通用做法。->操作符简化了通过指针的访问。生成指向内部成员的指针,是地址计算的直接应用。
  4. 理解汇编的钥匙:在涉及结构体的汇编代码中,识别出"基址寄存器+偏移量"的模式,就能立刻知道它在访问哪个字段。练习题3.41和3.42是绝佳的实践,将声明、偏移计算、汇编分析与高级逻辑还原完整串联。

核心启示:结构体将类型不同的数据逻辑绑定,而内存布局的确定性(偏移量)和连续性,使得硬件能高效访问。理解这种从抽象描述到具体内存布局的转换,是进行底层编程、调试内存错误和与硬件设备交互的基础。联合体(union)则提供了另一种"重叠"内存的视角,是下一节的延伸。

相关推荐
2301_803875618 小时前
PHP 中处理会话数组时的类型错误解析与修复指南
jvm·数据库·python
m0_743623928 小时前
c++如何批量修改文件后缀名_std--filesystem--replace_extension【实战】
jvm·数据库·python
MY_TEUCK9 小时前
Sealos 平台部署实战指南:结合 Cursor 与版本发布流程
java·人工智能·学习·aigc
桌面运维家9 小时前
IDV云桌面vDisk机房网络管控访问限制部署方案
运维·服务器·网络
2501_914245939 小时前
CSS如何处理CSS变量作用域冲突_利用特定类名重写变量值
jvm·数据库·python
charlie11451419110 小时前
嵌入式C++工程实践第16篇:第四次重构 —— LED模板,从通用GPIO到专用抽象
c语言·开发语言·c++·驱动开发·嵌入式硬件·重构
handler0111 小时前
Linux: 基本指令知识点(2)
linux·服务器·c语言·c++·笔记·学习
故事和你9111 小时前
洛谷-数据结构1-4-图的基本应用1
开发语言·数据结构·算法·深度优先·动态规划·图论
maqr_11011 小时前
MySQL数据库迁移到云端如何保障安全_数据加密与SSL连接配置
jvm·数据库·python
u01091476011 小时前
MySQL如何限制触发器递归调用的深度_防止触发器死循环方法
jvm·数据库·python