无效访问
越界访问与未初始化数据
无效访问 是 CoddyKit 上的免费 C Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C Academy 课程共包含 4 节课。
超越内存泄漏
泄漏会浪费内存,但无效访问错误会破坏内存。memcheck 可以捕获多种问题:
- 读取或写入超出缓冲区末尾的位置
- 在
free后继续使用内存 - 读取未初始化的值
- 通过错误的指针进行读取或写入
这些问题会导致最危险、也最难复现的崩溃。
越界写入
这段代码为 5 个 int 分配了空间,却写入了索引 5,也就是第六个位置。
索引 5 位于末尾之后一个元素的位置。memcheck 会在这一行报告 Invalid write of size 4。
#include <stdlib.h>
int main(void) {
int *a = malloc(5 * sizeof(int));
a[5] = 99; /* valid indices are 0..4 */
free(a);
return 0;
}阅读错误报告
报告会指出操作类型、大小以及该操作与内存块之间的关系:
Invalid write of size 4Address 0x... is 0 bytes after a block of size 20 alloc'd
“位于 20 字节内存块之后 0 个字节”表示这次访问恰好落在 20 字节(5 个 int)分配区域的紧接末尾处。
越界读取
读取操作同样会被捕获。在这里,循环多运行了一个元素。
memcheck 会在最后一次迭代中标记 Invalid read of size 4,即使程序可能因为侥幸而“正常工作”。
#include <stdlib.h>
#include <stdio.h>
int main(void) {
int *a = calloc(3, sizeof(int));
for (int i = 0; i <= 3; i++) /* should be i < 3 */
printf("%d\n", a[i]);
free(a);
return 0;
}未初始化的值
malloc 不会将内存清零。在赋值前使用这块内存会产生不可预测的值。
当此类值影响控制流或输出时,memcheck 会报告 Conditional jump or move depends on uninitialised value(s)。
#include <stdlib.h>
#include <stdio.h>
int main(void) {
int *p = malloc(sizeof(int));
if (*p == 0) /* *p was never set */
printf("zero\n");
free(p);
return 0;
}为什么未初始化值的报告会延迟
memcheck 不会在您读取未初始化内存的瞬间就发出警告。它会跟踪“已定义性”,只有当未定义的值确实产生影响时才报告,例如影响分支、输出或系统调用。
这样可以避免在您无害地复制未初始化字节时产生误报。
释放后使用
释放内存后继续访问它属于未定义行为。memcheck 可以精确检测到这种问题。
它会报告 Invalid read of size 4,并附带说明 Address ... is 0 bytes inside a block of size 4 free'd,甚至还会显示执行 free 的位置。
#include <stdlib.h>
#include <stdio.h>
int main(void) {
int *p = malloc(sizeof(int));
*p = 7;
free(p);
printf("%d\n", *p); /* read after free */
return 0;
}重复释放
两次释放同一个指针会破坏分配器的簿记信息。
memcheck 会报告无效释放操作,并显示当前释放和原始分配的位置,让这个错误一目了然。
#include <stdlib.h>
int main(void) {
int *p = malloc(sizeof(int));
free(p);
free(p); /* freed twice */
return 0;
}追踪来源
对于未初始化值错误,请添加 --track-origins=yes。
这样,memcheck 会报告错误值是在哪里创建的,而不只是在哪里使用的,从而把模糊的警告转化为明确的修复方向。
valgrind --track-origins=yes ./prog栈和全局变量
memcheck 擅长检测堆错误。但对于栈或全局数组中的溢出,其覆盖能力较弱。
在这种情况下,AddressSanitizer(gcc -fsanitize=address)通常是更好的选择。这两个工具可以互相补充。
差一错误:空终止符
一种常见的堆溢出是忘记字符串终止符。strlen 对 'hello' 返回 5,但该字符串需要 6 个字节来存储末尾的 '\0'。
当 strcpy 将最后一个字节写到末尾之外时,memcheck 会报告无效写入。
#include <stdlib.h>
#include <string.h>
int main(void) {
char *s = malloc(strlen("hello")); /* needs +1 */
strcpy(s, "hello"); /* writes the '\0' past end */
free(s);
return 0;
}快速检查
请确定 memcheck 会针对这段代码报告什么。
回顾
现在,您已经能够识别无效访问错误:
- 越界读取/写入会显示为“无效读取/写入”,并附带相对于内存块的偏移量
- 未初始化值在影响决策或输出时会被标记
- 释放后使用和重复释放都会通过两个调用位置被检测出来
--track-origins=yes可以定位未定义值;检测栈错误时请配合 ASan 使用
下一步:阅读完整报告。
常见问题解答
「无效访问」课时是免费的吗?
是的 — 「无效访问」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C Academy 课程的其余内容,请升级到 CoddyKit PRO。 C Academy 课程共包含 4 节课。
「无效访问」这节课中我会学到什么?
越界访问与未初始化数据 你通过在浏览器中直接运行的动手代码来练习 C Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 C Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 C Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「无效访问」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 C Academy 课中编写并运行代码吗?
能。每节 C Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。