c语言内存不能为written

99ANYc3cd6
预计阅读时长 16 分钟
位置: 首页 C语言 正文

这是一个运行时错误,而不是编译错误,当你看到这个错误弹窗时,意味着你的程序试图向一块它没有写入权限的内存地址写入数据,操作系统为了保护系统的稳定性,立即终止了你的程序。

c语言内存不能为written
(图片来源网络,侵删)

为什么会发生这个错误?(核心原因)

就是“越界写入”,你的程序拿到了一个内存地址的“钥匙”,但试图打开一扇它无权进入的“门”,强行开门导致了冲突。

这块“无权写入的内存”通常包括:

  • 程序代码段:存放你的代码和常量的地方,是只读的。
  • 操作系统内核空间:受系统严格保护。
  • 已经被释放的内存:你之前 free 掉的内存,虽然系统可能还没收回,但你已经失去了对它的控制权。
  • 未初始化的栈内存:比如一个局部数组,你声明了但没有初始化,就尝试写入其未分配的部分。

最常见的“罪魁祸首”及代码示例

下面是导致此错误的几种最常见情况,并附有 C 语言代码示例,你可以对照检查自己的代码。

野指针

场景:指针指向了一个无效的、随机的内存地址,然后你试图通过这个指针写入数据。

c语言内存不能为written
(图片来源网络,侵删)

原因

  • 指针没有被初始化(int *p;)。
  • 指针所指向的内存已经被 freedelete,但指针没有被置为 NULL
  • 指针超出了其作用域(返回了栈上局部变量的地址)。

错误示例:

#include <stdio.h>
#include <stdlib.h>
void function() {
    int *p; // p 是一个野指针,指向未知内存
    *p = 100; // 尝试向未知内存写入,极大概率导致 "内存不能为 written"
}
int main() {
    int *q = (int*)malloc(sizeof(int));
    *q = 10; // 这是合法的
    free(q); // 内存被释放了
    *q = 20; // 错误!q 现在是野指针,向已释放内存写入
    function(); // 调用上面的函数
    return 0;
}

数组越界

场景:访问数组元素时,使用了超出数组合法范围的索引。

原因:C 语言本身不做数组边界检查,如果你声明一个大小为 5 的数组,你却试图写入索引为 5 或更大的元素,就会写入到数组之外的内存,从而覆盖了其他数据或非法内存。

错误示例:

#include <stdio.h>
int main() {
    int arr[5] = {0}; // 合法索引是 0, 1, 2, 3, 4
    for (int i = 0; i <= 5; i++) { // 循环条件应该是 i < 5
        arr[i] = i; // 当 i = 5 时,arr[5] 越界写入!
    }
    return 0;
}

释放内存后继续使用

场景:调用了 free()delete 释放了内存,但之后仍然使用原来的指针去访问或修改该内存。

原因free() 只是告诉操作系统“这块内存我用完了,你可以收回了”,但指针变量本身仍然保存着那块内存的地址,操作系统可能已经将这块内存分配给了其他程序,或者标记为不可访问,你的程序再写入就会出错。

错误示例:

#include <stdio.h>
#include <stdlib.h>
int main() {
    int *p = (int*)malloc(sizeof(int));
    *p = 100;
    free(p); // p 指向的内存已被释放
    // p 成为了一个“悬挂指针”
    *p = 200; // 错误!向已释放的内存写入,导致 "内存不能为 written"
    // 正确的做法是:free(p); p = NULL;
    return 0;
}

指针运算错误

场景:通过指针的加减运算来遍历内存,但计算错误,导致指针指向了非法区域。

原因:指针运算的单位和它的数据类型有关。p+1 实际上是 p + sizeof(*p) 个字节,错误的计算很容易越界。

错误示例:

#include <stdio.h>
int main() {
    int arr[5] = {0};
    int *p = arr; // p 指向 arr[0]
    // 正确的遍历方式
    for (int i = 0; i < 5; i++) {
        printf("%d ", *(p + i));
    }
    // 错误的运算:p+5 指向了 arr[5] 的位置,越界
    *(p + 5) = 99; // 错误!
    return 0;
}

返回栈内存的地址

场景:在函数内部定义了一个局部变量(在栈上),然后返回了这个变量的指针。

原因:当函数执行完毕返回时,它所在的栈帧会被销毁,这个局部变量占用的内存被“回收”,之后可以被其他函数调用随意覆盖,返回指向它的指针,就相当于返回了一个随时会失效的地址。

错误示例:

#include <stdio.h>
int* get_bad_pointer() {
    int x = 10; // x 在栈上
    return &x;   // 危险!返回了栈变量的地址
}
int main() {
    int *p = get_bad_pointer();
    // p 指向的内存可能已经被其他操作修改了
    *p = 20; // 极大概率导致 "内存不能为 written",因为 x 的栈空间已不复存在
    printf("%d\n", *p);
    return 0;
}

如何调试和解决?

遇到这种错误,不要慌张,按照以下步骤系统地排查:

使用调试器 - 最有效的方法!

不要直接运行程序,而是使用调试器,GDB (Linux/macOS) 或 Visual Studio Debugger (Windows)。

  • 设置断点:在你怀疑出错的代码行(如 free() 之后、循环内部)设置断点。
  • 单步执行:逐行执行代码,观察变量的值和内存地址的变化。
  • 观察指针:重点检查指针的值,它是不是 NULL?它指向的地址是不是合法的?当你进行 p++arr[i] 操作时,它是否超出了预期范围?
  • 查看内存:调试器通常可以让你查看特定内存地址的内容,看看是否被意外修改。

代码审查和静态分析

  • 检查 free 之后:确保 free(p) 之后,立即将 p 置为 NULL
  • 检查数组循环:所有 for 循环访问数组时,确认边界条件是 i < size 而不是 i <= size
  • 初始化所有指针:声明指针时,如果暂时不赋值,就初始化为 NULLint *p = NULL;
  • 使用工具:使用像 Valgrind (Linux/macOS) 这样的工具,它可以在运行时检测内存泄漏、野指针、越界访问等问题,非常强大。

编译器警告

开启编译器的警告选项,让编译器帮你发现潜在问题。

  • GCC/Clang: 使用 -Wall -Wextra 编译选项。
    gcc -Wall -Wextra your_program.c -o your_program

    编译器可能会提示你“未初始化的变量”或“有返回局部变量地址”等问题。


一个综合性的调试案例

假设我们有下面这段有问题的代码:

#include <stdio.h>
#include <stdlib.h>
// 错误的函数:返回栈地址
int* create_array(int size) {
    int arr[size]; // VLA (变长数组),仍在栈上
    for (int i = 0; i < size; i++) {
        arr[i] = i;
    }
    return arr; // 错误!返回了栈地址
}
int main() {
    int *p = create_array(5);
    // p 是一个悬挂指针
    // 尝试修改,极大概率崩溃
    p[0] = 100; // "内存不能为 written" 或 "段错误"
    // 尝试释放,也可能有问题
    free(p); // p 指向的不是堆内存,free 可能会报错或导致未定义行为
    return 0;
}

调试步骤:

  1. 使用 GDB
    gdb ./your_program
  2. main 函数的 p[0] = 100; 这一行设置断点
    break main.c:17
  3. 运行程序
    run
  4. 程序停在断点处,现在检查变量 p
    print p

    你可能会看到一个看起来很奇怪的地址,这个地址很可能在栈的范围内,而不是堆的范围。

  5. 查看 p 指向的内存
    x/5w $p  # 以32位整数形式打印从p地址开始的5个word

    你可能会看到一些垃圾值,因为 create_array 函数的栈帧已经被销毁了。

  6. 单步执行 p[0] = 100;
    next

    程序很可能在这里崩溃,并提示 "memory not writable"。

通过这个过程,你就能清楚地定位到问题出在 create_array 函数返回了栈地址。

“内存不能为 written” 是 C/C++ 程序员必须跨越的一道坎,它的本质是对内存的错误访问

核心解决思路

  1. 永远清楚你的指针指向哪里:是栈、堆、全局区还是代码区?
  2. 确保指针在有效范围内操作:特别是数组和指针运算。
  3. 遵循 RAII 原则:谁分配,谁释放;释放后立即置空。
  4. 善用工具:调试器 和静态分析工具 是你最好的朋友。

养成良好的编程习惯,多思考内存的生命周期,就能有效避免这类问题。

-- 展开阅读全文 --
头像
dede list limit
« 上一篇 01-08
织梦cms如何搭建网站
下一篇 » 01-08

相关文章

取消
微信二维码
支付宝二维码

目录[+]