(学习笔记)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)则提供了另一种"重叠"内存的视角,是下一节的延伸。

相关推荐
墨^O^2 小时前
进程与线程的核心区别及 Linux 启动全过程解析
linux·c++·笔记·学习
福楠2 小时前
现代C++ | C++14甜点特性
linux·c语言·开发语言·c++
charlie1145141912 小时前
嵌入式C++教程实战之Linux下的单片机编程:从零搭建 STM32 开发工具链(4)从零构建 STM32 构建系统
linux·开发语言·c++·stm32·单片机·学习·嵌入式
AI成长日志2 小时前
【笔面试算法学习专栏】双指针专题:简单难度三题精讲(167.两数之和II、283.移动零、344.反转字符串)
学习·算法·面试
猹叉叉(学习版)2 小时前
【系统分析师_知识点整理】 10.软件需求工程
笔记·需求分析·软考·系统分析师
Book思议-2 小时前
【数据结构】数组与特殊矩阵
数据结构·算法·矩阵
mcooiedo2 小时前
mybatisPlus打印sql配置
数据库·sql
LuminousCPP2 小时前
C语言自定义类型全解析
c语言·笔记·枚举·结构体·联合体
wudl55662 小时前
MySQL 8.0.42 Docker 开发部署手册
数据库·mysql·docker