0Pricing
C Academy · 课时

无效访问

越界访问与未初始化数据

无效访问 是 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 4
Address 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 反馈 — 无需本地设置。

此课程中的所有课时

  1. 为什么使用 Valgrind
  2. 检测内存泄漏
  3. 无效访问
  4. 阅读报告
← 返回 C Academy