顺序表完成:任意位置插入 / 删除,统一用 end 表示元素个数
新增功能: - q_addpro 任意位置插入,含位置合法性检查(0 <= num <= end) - q_reducepro 任意位置删除,含位置合法性检查(0 <= num <= end-1) - q_reduce 出队,一行复用 q_reducepro(q, 0, x) 修复: - q_add 的扩容判断改用 end。原先用未初始化的 length, 导致 "length == max" 永远不成立,扩容代码从未执行过,是潜在的堆越界 结构调整: - 去掉 length 字段 —— 它与 end 表示同一件事,并存必然产生不一致 - 1.c 改名为 seqlist.c(顺序表 sequential list) 调试记录.md 改名为 seqlist-debug-notes.md 验收: - gcc -Wall -Wextra 零 warning 零 error - 18 个用例全绿(含 0 号位 / 末尾 / 末尾+1 / 负数 / 空表 / 自动扩容) 详见 seqlist-debug-notes.md 中记录的 8 个坑及沉淀的原则。
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
# seqlist —— 顺序表开发记录
|
||||
|
||||
用 C 手写顺序表(顺序存储的线性表):动态数组 + 任意位置插入/删除。
|
||||
|
||||
---
|
||||
|
||||
## 状态
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| 源文件 | `seqlist.c` |
|
||||
| 编译 | `gcc -Wall -Wextra seqlist.c -o seqlist.exe` —— **零 warning 零 error** |
|
||||
| 进度 | 增、删、查全部完成 ✅ |
|
||||
|
||||
## 接口一览
|
||||
|
||||
| 函数 | 作用 | 返回值 |
|
||||
|---|---|---|
|
||||
| `q_init(q, len)` | 建表,容量 len | 0 |
|
||||
| `q_add(q, x)` | 尾部追加(容量不够自动扩容) | 0 |
|
||||
| `q_addpro(q, num, x)` | 在 num 号位插入 x | 0 成功 / −1 位置非法 |
|
||||
| `q_reducepro(q, num, &x)` | 取走 num 号位的元素,值写给 x | 0 成功 / −1 位置非法 |
|
||||
| `q_reduce(q, &x)` | 取走队头元素 | 同 `q_reducepro` |
|
||||
| `printall(q)` | 打印全表 | 0 |
|
||||
|
||||
## 结构体
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
int max; // 容量
|
||||
int *start; // 内存起始地址
|
||||
int front; // 预留(循环队列要用)
|
||||
int end; // 元素个数,同时也是"下一个空位"的下标
|
||||
} queue;
|
||||
```
|
||||
|
||||
**决定:不要 `length` 字段。** 曾经有过一个 `length`,和 `end` 表示同一件事。
|
||||
两个变量记同一件事,就必然有"两个数不一样"的那天。现在统一用 `end`。
|
||||
|
||||
## 约定
|
||||
|
||||
- **`end` 是"下一个空位"的下标**,数值上等于元素个数。
|
||||
- 合法下标范围:`0 .. end-1`
|
||||
- 合法**插入**位置:`0 <= num <= end`(可以插在末尾,等于追加)
|
||||
- 合法**删除**位置:`0 <= num <= end-1`(不能删"末尾之后",那儿没东西)
|
||||
|
||||
---
|
||||
|
||||
# 踩过的坑
|
||||
|
||||
按发现顺序排列。每条都记下"当时是怎么识别出来的"——这个比结论更值钱。
|
||||
|
||||
## 1. `realloc` 尺寸算错 → 堆越界
|
||||
|
||||
```c
|
||||
q->start = realloc(q->start, sizeof(q->max)); // ❌
|
||||
q->start = realloc(q->start, q->max * sizeof(int)); // ✅
|
||||
```
|
||||
|
||||
`sizeof(q->max)` 是"一个 int 占几字节"(4),不是"总共要几字节"。
|
||||
|
||||
**现象**:打印出来一串每次都不同的随机数(`490`→`467`、`1494745424`→`-960036528`)。
|
||||
|
||||
> 💡 **识别技巧:数据每次运行都不一样 → 读到的不是自己的数据,是别人的内存。**
|
||||
|
||||
因为 realloc 只申请了 4 字节,后面的格子被堆的管理数据占用了。
|
||||
|
||||
## 2. 忘了把 `x` 真正写进去
|
||||
|
||||
平移做完了,但没执行 `q->start[num] = x;`,参数 `x` 从头到尾没被用过。
|
||||
|
||||
> 💡 编译器的 warning 里点了名:`unused parameter 'x'`。
|
||||
> **编译器说某个参数"没被用过",八成是漏写了什么。**
|
||||
|
||||
## 3. 平移循环:起点、方向、类型,三个错
|
||||
|
||||
```c
|
||||
for (size_t i = q->end; i < num-1; i--) // ❌ 三个错全占了
|
||||
for (int i = q->end - 1; i >= num; i--) // ✅
|
||||
```
|
||||
|
||||
| 错 | 表现 | 原因 |
|
||||
|---|---|---|
|
||||
| **起点** `end` | 多搬一次"空气",垃圾值混进表 | `end` 是空位,真数据只到 `end-1` |
|
||||
| **方向** `i--` 配 `i <` | 条件第一秒就为假,**循环一次都不跑** | 往上数的条件配了往下数的循环 |
|
||||
| **类型** `size_t` | 往下数会"翻车" | 无符号类型减到 0 以下会变成天文数字 |
|
||||
|
||||
**方向为什么必须从后往前**:如果从前往后搬,`start[1] = start[0]` 会先把 1 号位
|
||||
原来等着被搬的东西**踩掉**。从后往前,每一格的目标位都是刚刚腾出来的。
|
||||
|
||||
> 💡 **识别技巧:在循环里插一行 `printf("i=%d\n", i)`。**
|
||||
> 空的就是一次没跑 → 怀疑条件;有值但值不对 → 怀疑起点。一次插桩把问题一劈两半。
|
||||
|
||||
> 💡 **写循环之前先在注释里填三个空:起点、停手线、方向。**
|
||||
> 填不完,说明还没想清楚,先别写。
|
||||
|
||||
## 4. `size_t` 到底是什么
|
||||
|
||||
**无符号整数**,标准库用来表示"大小/个数"。`sizeof`、`malloc`、`strlen` 都用它。
|
||||
本机是 8 字节,最大 18446744073709551615。
|
||||
|
||||
**为什么危险**:拿一个"可能为负"的数和它比较时,**负数会被转成天文数字**。
|
||||
|
||||
```c
|
||||
int num = 0;
|
||||
size_t i = 5;
|
||||
i > num - 1 // 心里想的是 5 > -1 → 成立
|
||||
// 实际: -1 被转成 18446744073709551615
|
||||
// 5 > 18446744073709551615 → 不成立!
|
||||
```
|
||||
|
||||
**口诀:`size_t` 是"往上数"的类型;要往下数,用 `int`。**
|
||||
|
||||
> 📌 别盲信编辑器补全。`for (size_t i = 0; i < n; i++)` 是最标准的"往上数"写法,
|
||||
> 但它不知道你的循环是倒着走的。**循环头必须自己想清楚再按 Tab。**
|
||||
|
||||
## 5. `end + 1` 这个 off-by-one
|
||||
|
||||
```c
|
||||
for (int i = num; i < q->end+1; i++) // ❌ 删多了一次
|
||||
for (int i = num; i < q->end-1; i++) // ✅
|
||||
```
|
||||
|
||||
`+1` 让 `i` 跑到了 `end`,把"空位"当成了"数据",读写了数据之外的内存。
|
||||
|
||||
> 📌 **规律:`end` 是空位、不是数据。凡是循环想碰"最后一个元素",上界就得是 `end-1`。**
|
||||
|
||||
## 6. 没有参数校验 → 静默堆越界
|
||||
|
||||
`q_addpro(&q, 11, x)` 在表长只有 5 时被接受,数据写到了下标 11。
|
||||
程序不报错、不崩溃、静默跑完。
|
||||
|
||||
**教训**:
|
||||
- **越界 ≠ 崩溃。** Windows 上页是按 4KB 给的,多写几个字节可能还在同一页里。
|
||||
最危险的就是这种"静默写坏"。
|
||||
- **容量不够可以扩;位置不存在,只能拒绝。** 用扩容去接非法位置,
|
||||
只会在表中间造出"洞",那就不是顺序表了。
|
||||
- **做"安全检查"的人也要防**:如果非法位置能触发任意大的 `realloc`,
|
||||
别人传个 `num = 100000000` 就能把你的程序搞垮。
|
||||
|
||||
## 7. `q->length` 从未初始化 → 扩容逻辑变成死代码
|
||||
|
||||
`q_init` 设了 `max`、`front`、`end`,唯独没设 `length`。
|
||||
而 `q_add` 的扩容判断是 `if (q->length == q->max)` —— 拿一个**栈上的随机数**做判断。
|
||||
|
||||
实测:重新初始化后 `length` 还留着上一次的 151,于是 `151 == 3` 永远不成立,
|
||||
**扩容那段代码一次都没跑过**。往容量 3 的表里放 6 个数,后 3 个直接写到缓冲区外面。
|
||||
|
||||
> 📌 **`malloc` 出来的是"别人用过的",栈上的变量是"上个人留下的"。**
|
||||
> **任何没被你亲自赋过值的字段,都是在读别人的垃圾。**
|
||||
> **初始化时,结构体的每个字段都应该是确定的。**
|
||||
|
||||
## 8. 删 `length` 字段时,多留了一行 `q->end++`
|
||||
|
||||
把 `q->length++;` 换成 `q->end++;` —— 但 `q->end++` 那行本来就在上面:
|
||||
|
||||
```c
|
||||
q->start[q->end] = num;
|
||||
q->end++; // 本来就有
|
||||
q->end++; // 新加的 ← 每加一个数,end 往前走两格
|
||||
```
|
||||
|
||||
**现象**:加了 `1 2 3`,得到 `end=6`、表 `[1 0 2 0 3 0]`(元素之间夹着洞)。
|
||||
|
||||
**注意:编译器一条 warning 都没报。** 两句 `q->end++;` 完全合法 ——
|
||||
语法没错、类型没错,**它错在"意图"上,而编译器读不懂意图**。
|
||||
|
||||
> 📌 **教训一:改代码不是"找一行相似的替换掉"。**
|
||||
> 替换之前先问:"**我要换掉的那个东西,是不是真的在这儿?**"
|
||||
> (和之前那个复制粘贴没改的 `if (num>q->max)` 是同一类毛病:
|
||||
> 靠"形状相似"改代码,而不是靠"理解这块地方在干什么"。)
|
||||
|
||||
> 📌 **教训二:`零 warning` ≠ `正确`。**
|
||||
> 编译器能挡住语法错、类型错、明显笔误,**挡不住逻辑错**。
|
||||
> 这半边的责任只能靠测试来扛。
|
||||
>
|
||||
> | 手段 | 抓什么 | 抓不到什么 |
|
||||
> |---|---|---|
|
||||
> | `gcc -Wall -Wextra` | 语法错、类型错、可疑写法 | **逻辑错** |
|
||||
> | **跑测试** | 逻辑错 | 编译期问题 |
|
||||
|
||||
---
|
||||
|
||||
# 沉淀下来的原则
|
||||
|
||||
1. **初始化要把所有字段都设上。** 没设的字段 = 别人的垃圾。
|
||||
2. **校验放在最前面。** 碰内存的动作,一个都不能在校验之前发生。
|
||||
3. **改原因,不要在下游拦症状。** 用 `if` 挡住错的循环,循环还是错的。
|
||||
4. **能用一个路径解决的,别开两个。** 两个分支 = 两份要维护的逻辑。
|
||||
5. **错误要一路传上去,不能半路被吞。** `return q_reducepro(q,0,x);` 就是全部。
|
||||
6. **越界有两种**:逻辑越界(读自己的垃圾)不崩;物理越界(写别人的内存)危险。
|
||||
7. **先看函数签名要什么类型**(编译器会念 `expected ...`),再检查手上的变量。
|
||||
`&q` 在 `q` 已是指针时多加一层;`*x` 在 `x` 已是指针时剥掉一层。
|
||||
8. **边界测试**:测"0 号位""末尾""末尾+1""负数""空表"。
|
||||
**只测"正常情况"等于没测。**
|
||||
9. **先写期望,再跑。** 不然"看起来对"会骗你。
|
||||
(曾经看到 `666` 出现在 0 号位就以为插入对了,其实原来的 `1` 被覆盖了。)
|
||||
10. **检查返回值**,也要检查**表本身有没有被动过**。报错了但数据被改坏,等于白报。
|
||||
|
||||
---
|
||||
|
||||
# 测试清单(当前全绿)
|
||||
|
||||
**插入**(表 `1 2 3 4 5`)
|
||||
|
||||
| 调用 | 期望 |
|
||||
|---|---|
|
||||
| `addpro(0, 666)` | `666 1 2 3 4 5` |
|
||||
| `addpro(2, 666)` | `1 2 666 3 4 5` |
|
||||
| `addpro(end, 666)` | `1 2 3 4 5 666` |
|
||||
| `addpro(end+1, 666)` | −1,表不动 |
|
||||
| `addpro(-1, 666)` | −1,表不动 |
|
||||
|
||||
**删除**(表 `1 2 3 4 5`)
|
||||
|
||||
| 调用 | 期望 |
|
||||
|---|---|
|
||||
| `reducepro(0)` | x=1,表变 `2 3 4 5` |
|
||||
| `reducepro(2)` | x=3,表变 `1 2 4 5` |
|
||||
| `reducepro(end-1)` | x=5,表变 `1 2 3 4` |
|
||||
| `reducepro(end)` | −1,表不动 |
|
||||
| `reducepro(-1)` | −1,表不动 |
|
||||
| `reducepro(0)`(空表) | −1,表不动 |
|
||||
|
||||
**队头出队**
|
||||
|
||||
| 调用 | 期望 |
|
||||
|---|---|
|
||||
| 连续 `q_reduce` 五次 | 依次拿到 1 2 3 4 5,表变空 |
|
||||
| 空表再 `q_reduce` | −1 |
|
||||
|
||||
**扩容**
|
||||
|
||||
- [x] 往容量 3 的表里加 6 个 → `max` 涨到 8,表 `[1 2 3 4 5 6]` ✅
|
||||
|
||||
---
|
||||
|
||||
# 环境与工具
|
||||
|
||||
- **编码**:源文件是 GBK,PowerShell 里中文正常显示。
|
||||
**不要**加 `-fexec-charset=GBK`(会转两次,反而乱码)。
|
||||
- **编译**:`gcc -Wall -Wextra seqlist.c -o seqlist.exe`
|
||||
- **检查警告要用完整编译**:`-fsyntax-only` 会漏掉 `-Wreturn-type` 这类后阶段警告。
|
||||
- **本机没装 ASan**(AddressSanitizer)。想抓堆越界可以自己埋"地雷":
|
||||
多申请一格、放一个哨兵值、事后检查它有没有被动过。
|
||||
(ASan 的原理就是这个,只是自动化的:在每块内存前后埋一圈"红区"并标记为禁止访问。)
|
||||
- **VSCode**:`.`vscode/tasks.json` 里配了构建任务,`Ctrl+Shift+B` 编译,
|
||||
结果进「问题」面板。**但记住:编辑器的波浪线是参考,编译器才是判决。**
|
||||
- **看循环变量**:`gdb` 可用 `watch` 命令给内存地址下监视点,值一变就中断。
|
||||
|
||||
---
|
||||
|
||||
# 下一步:循环队列
|
||||
|
||||
顺序表做队列用时,暴露一个痛点:
|
||||
|
||||
> **`q_reduce` 每次从队头取元素,都要把后面所有元素往前搬一遍。表越长越慢。**
|
||||
|
||||
`q_reduce(q, &x)` 内部调的是 `q_reducepro(q, 0, x)` —— 删 0 号位,
|
||||
意味着 `end-1` 个元素全都要挪窝。取 N 次就是 O(N²)。
|
||||
|
||||
**循环队列要解决的就是这个**:不搬数据,改成让"队头"和"队尾"两个下标
|
||||
在数组里**绕圈走**,走到末尾就回到开头。
|
||||
|
||||
预告:到时候会遇到一个新问题 —— **队空和队满,怎么区分?**
|
||||
(`front == rear` 既可能是空、也可能是满。)
|
||||
+23
-11
@@ -4,10 +4,10 @@
|
||||
|
||||
typedef struct {
|
||||
int max; //列表最大长度
|
||||
int length; //列表现在的长度
|
||||
//int length; //列表现在的长度
|
||||
int *start; //内存地址起始位置
|
||||
int front; //列表起始索引
|
||||
int end; //列表结束索引
|
||||
int end; //列表下一个空位置的索引
|
||||
|
||||
}queue;
|
||||
|
||||
@@ -23,23 +23,30 @@ q->end=0;
|
||||
|
||||
|
||||
int q_add(queue *q,int num){ //最后边加上元素
|
||||
if(q->length==q->max){ //容量不够了的自动扩容
|
||||
if(q->end==q->max){ //容量不够了的自动扩容
|
||||
q->max+=5;
|
||||
q->start=realloc(q->start,q->max*sizeof(int));
|
||||
}
|
||||
q->start[q->end]=num;
|
||||
q->end++;
|
||||
q->length++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int q_addpro(queue *q,int num,int x){ //num是插入位置,x是插入的元素。任意地方加上元素,要进去一个索引编号和数值
|
||||
//先判断够不够位置加上去先
|
||||
//根据进一步检查,这里要加一个非法参数拦截--索引过大直接报错。如果只是最后一位在追加,那也接受,但是就调用q_add就行了似乎。
|
||||
if (num>q->end||num<0){
|
||||
return -1; //超出了直接报错
|
||||
}
|
||||
// if (num==q->max){
|
||||
// q_add(q,x);
|
||||
// } 这里下面的循环已经可以处理了,不需要
|
||||
if(q->end>=q->max){
|
||||
q->max=q->max+5;
|
||||
q->start=realloc(q->start,q->max*sizeof(int));//这里就是扩容代码,把max也一起增加了
|
||||
}
|
||||
//然后写平移的代码段,把加入的位置的东西到end的所有东西往后平移一个
|
||||
for (size_t i = q->end; i > num-1; i--)
|
||||
for (int i = q->end-1; i >= num; i--)
|
||||
{
|
||||
/* code */
|
||||
q->start[i+1]=q->start[i];
|
||||
@@ -50,29 +57,33 @@ q->end++;
|
||||
q->start[num]=x;
|
||||
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int q_reduce(){ //最前面拿走元素
|
||||
|
||||
|
||||
}
|
||||
|
||||
int q_reducepro(queue *q,int num ,int *x){ //任意地方拿走元素,也是索引号+数值
|
||||
if(num>=q->end||num<0){return -1;}
|
||||
*x=q->start[num];
|
||||
for (size_t i = num; i < q->end+1; i++)
|
||||
|
||||
for (int i = num; i < q->end-1; i++)
|
||||
{
|
||||
/* code */
|
||||
q->start[i]=q->start[i+1];
|
||||
|
||||
|
||||
}
|
||||
q->end--;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int q_reduce(queue *q,int *x){ //最前面拿走元素
|
||||
return q_reducepro(q,0,x);
|
||||
}
|
||||
|
||||
int printall(queue *q){ //打印全部东西
|
||||
printf("下面是依次打印");
|
||||
for (size_t i = 0; i < q->end; i++)
|
||||
for (int i = 0; i < q->end; i++)
|
||||
{
|
||||
/* code */
|
||||
|
||||
@@ -100,6 +111,7 @@ q_add(&q,b);
|
||||
}
|
||||
printall(&q);
|
||||
printf("\n接下来测试中间插入: 3号位置插入666,5号位置插入-77,11号位置插入1919810");
|
||||
|
||||
q_addpro(&q,3,666);
|
||||
q_addpro(&q,5,-77);
|
||||
q_addpro(&q,11,1919810);
|
||||
@@ -1,121 +0,0 @@
|
||||
# 调试记录
|
||||
|
||||
记录顺表练习过程中遇到的问题和当时的想法。
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-17 当前进度
|
||||
|
||||
| 函数 | 状态 |
|
||||
|---|---|
|
||||
| `q_init` | 可用(但见问题 3) |
|
||||
| `q_add` | 可用 |
|
||||
| `printall` | 可用 |
|
||||
| `q_addpro` | 能跑,但有 bug(见问题 1、2) |
|
||||
| `q_reduce` | 空函数,未写 |
|
||||
| `q_reducepro` | 写了,未测试(见问题 4) |
|
||||
|
||||
---
|
||||
|
||||
## 问题 1:`q_addpro` 平移时多搬了一次,把垃圾值带进了表
|
||||
|
||||
**现象**:插入之后,数据末尾会多出一个来源不明的值。
|
||||
|
||||
**复现**:表长 3、元素 `1 2 3`,执行 `q_addpro(&q, 3, 666)`,
|
||||
打印结果为 `1 2 3 666 3801155 ...` —— `3801155` 不是任何一次输入的数据。
|
||||
|
||||
**待查**:循环的**起点**取的应该是哪一个下标?
|
||||
|
||||
> 补充:同样的问题在表长 6 时也存在,只是那次垃圾值落到了打印范围之外,
|
||||
> 所以没被发现。**同一个 bug,换个尺寸才现形。**
|
||||
|
||||
---
|
||||
|
||||
## 问题 2:`q_addpro` 不检查插入位置是否合法
|
||||
|
||||
**现象**:`q_addpro(&q, 11, 1919810)` 在表长只有 5 时被接受,
|
||||
程序不报错、不崩溃,静默跑完。
|
||||
|
||||
**实际后果**:扩容后 `max = 8`(合法下标 0~7),但数据被写到了 **下标 11**。
|
||||
也就是往不属于自己的内存里写了 4 格 —— **堆越界**。
|
||||
|
||||
**记住的教训**:
|
||||
|
||||
- 越界 ≠ 崩溃。Windows 上这类写坏通常无声无息,只在特定边界才炸。
|
||||
- 防御性编程:函数应该先检查参数,发现非法就**立刻返回**,
|
||||
并用返回值告诉调用方"我失败了"。
|
||||
- 这几个函数都声明成 `int`,但从来没 `return` 过 —— 那个 `int` 就是留给这件事的。
|
||||
|
||||
**待定**:合法插入位置的范围是什么?(`num` 能不能等于表长?能不能负数?)
|
||||
检测到非法之后,函数该怎么返回?调用方该怎么处理?
|
||||
|
||||
---
|
||||
|
||||
## 问题 3:`q->length` 从未被初始化
|
||||
|
||||
`q_init` 里只设了 `max`、`front`、`end`,**没有给 `length` 赋值**。
|
||||
但 `q_add` 的扩容判断用的是 `q->length == q->max` ——
|
||||
等于拿一个栈上的随机数来决定要不要扩容。
|
||||
|
||||
目前测试没踩到,是因为运气。换个输入就会出问题。
|
||||
|
||||
**待定**:`length` 和 `end` 到底是不是同一个东西?如果循环队列要用 `length`,
|
||||
那它得先被管起来。
|
||||
|
||||
---
|
||||
|
||||
## 问题 4:`q_reducepro` 的平移方向
|
||||
|
||||
删除和插入的平移**方向是相反的**,但原理相通。
|
||||
|
||||
`q_reducepro` 目前写的是 `for (size_t i = num; i < q->end + 1; i++)`,
|
||||
还没测过。另外注意 `size_t` 是无符号类型。
|
||||
|
||||
**待测**:
|
||||
|
||||
- [ ] 删 0 号位
|
||||
- [ ] 删最后一个
|
||||
- [ ] 删中间
|
||||
- [ ] `*x` 里拿到的是不是被删掉的那个值
|
||||
- [ ] 删除后表长有没有正确减少
|
||||
|
||||
---
|
||||
|
||||
## 待办的测试用例
|
||||
|
||||
- [ ] 插入到 0 号位
|
||||
- [ ] 插入到末尾(`num == end`)
|
||||
- [ ] 表"差一格满"时(`end == max - 1`)插入
|
||||
- [ ] 非法位置:负数、大于表长
|
||||
- [ ] 往空表里插入
|
||||
|
||||
---
|
||||
|
||||
## 环境和工具备注
|
||||
|
||||
- 源文件目前是 **GBK** 编码,PowerShell 里中文能正常显示。
|
||||
(注意:不要再加 `-fexec-charset=GBK`,会转两次反而乱码)
|
||||
- 编译命令:`gcc -Wall 1.c -o 1.exe`
|
||||
- 本机 **没有装 ASan**(AddressSanitizer),想抓内存越界得用别的办法。
|
||||
- `printall` 用的边界是 `q->end` —— 如果 `end` 本身不可信,打印出来的就是假象。
|
||||
|
||||
---
|
||||
|
||||
## 已经解决的(记录一下思路,别忘了)
|
||||
|
||||
**扩容时 `realloc` 的大小算错了**
|
||||
|
||||
写成了 `realloc(q->start, sizeof(q->max))`,而 `sizeof(q->max)` 是
|
||||
"一个 `int` 占几字节"(4),不是"总共要几字节"。
|
||||
|
||||
正确写法是 `个数 × 每个的大小`。当时误申请到了 4 字节,
|
||||
后面的数据全被堆的元数据覆盖,打印出来是一堆每次都不同的随机数。
|
||||
|
||||
> **识别技巧**:数据每次运行都不一样 → 读到的不是自己的数据,是别人的内存。
|
||||
|
||||
**忘了把 `x` 真正写进去**
|
||||
|
||||
平移做完了,但没执行插入动作,参数 `x` 从头到尾没被用过。
|
||||
编译器的 warning 里其实点了名(`unused parameter 'x'`)。
|
||||
|
||||
> **识别技巧**:编译器说某个参数"没被用过",八成是漏写了什么。
|
||||
Reference in New Issue
Block a user