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

为什么会发生这个错误?(核心原因)
就是“越界写入”,你的程序拿到了一个内存地址的“钥匙”,但试图打开一扇它无权进入的“门”,强行开门导致了冲突。
这块“无权写入的内存”通常包括:
- 程序代码段:存放你的代码和常量的地方,是只读的。
- 操作系统内核空间:受系统严格保护。
- 已经被释放的内存:你之前
free掉的内存,虽然系统可能还没收回,但你已经失去了对它的控制权。 - 未初始化的栈内存:比如一个局部数组,你声明了但没有初始化,就尝试写入其未分配的部分。
最常见的“罪魁祸首”及代码示例
下面是导致此错误的几种最常见情况,并附有 C 语言代码示例,你可以对照检查自己的代码。
野指针
场景:指针指向了一个无效的、随机的内存地址,然后你试图通过这个指针写入数据。

原因:
- 指针没有被初始化(
int *p;)。 - 指针所指向的内存已经被
free或delete,但指针没有被置为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。 - 初始化所有指针:声明指针时,如果暂时不赋值,就初始化为
NULL:int *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;
}
调试步骤:
- 使用 GDB:
gdb ./your_program
- 在
main函数的p[0] = 100;这一行设置断点:break main.c:17
- 运行程序:
run
- 程序停在断点处,现在检查变量
p:print p
你可能会看到一个看起来很奇怪的地址,这个地址很可能在栈的范围内,而不是堆的范围。
- 查看
p指向的内存:x/5w $p # 以32位整数形式打印从p地址开始的5个word
你可能会看到一些垃圾值,因为
create_array函数的栈帧已经被销毁了。 - 单步执行
p[0] = 100;:next
程序很可能在这里崩溃,并提示 "memory not writable"。
通过这个过程,你就能清楚地定位到问题出在 create_array 函数返回了栈地址。
“内存不能为 written” 是 C/C++ 程序员必须跨越的一道坎,它的本质是对内存的错误访问。
核心解决思路:
- 永远清楚你的指针指向哪里:是栈、堆、全局区还是代码区?
- 确保指针在有效范围内操作:特别是数组和指针运算。
- 遵循 RAII 原则:谁分配,谁释放;释放后立即置空。
- 善用工具:调试器 和静态分析工具 是你最好的朋友。
养成良好的编程习惯,多思考内存的生命周期,就能有效避免这类问题。
