2024.12.26 os lab3
原代码
地址:https://github.com/BUPT-OS/easy_lab/tree/lab3
运行未修改的代码,并且注释掉cout时发生错误:
malloc(): corrupted top size
如果不注释cout,可以正常运行
1.不注释 cout
时堆内存的详细分析
1. 程序启动阶段
- 在程序启动时,堆的初始状态为空,堆顶指针位于堆的起始位置。
- 当程序第一次需要在堆上分配内存时,堆顶会向上增长(通常从低地址向高地址)。
2. 第一处堆分配:cout
调用
cout << "A magic print! If you comment this, the program will break." << endl;
cout被调用时,C++ 标准库会在堆上分配一个缓冲区,用于存储待输出的字符串内容。
缓冲区大小:在常见的实现中(如 GNU libstdc++),这个缓冲区的默认大小为 1024 字节。
分配位置:此时缓冲区位于堆的最底部,是堆中的第一个分配块。
堆布局(堆顶向高地址增长):
+-------------------+<-- 堆底
| cout 缓冲区 (1024) |
+-------------------+
3. 第二处堆分配:double_array
函数调用
int **result = new int*[8];
在 double_array函数中,result 被分配在堆上。它是一个指针数组,用于存储指向 matrix 中行的指针。
分配大小:8 * sizeof(int*)
,假设指针大小为 8 字节,则总大小为 64 字节。
分配位置:由于堆是按顺序分配的,result
的内存块紧跟在 cout
缓冲区之后。
堆布局:
+-------------------+<-- 堆底
| cout 缓冲区 (1024) |
+-------------------+
| result (64) |
+-------------------+
4. 越界访问的堆内存情况
在 double_array
函数中,存在以下越界问题:
for (int i = 0; i < n; ++i) {result[i] = matrix[i];
}
result
仅分配了 8 个指针的空间(new int*[8]
),但代码试图访问 result[0]
到 result[63]
(共 64 个)。越界访问后,result的内容会覆盖 cout缓冲区的部分内存:result[8]
到 result[63]
的内容会覆盖 cout
缓冲区中第 64 字节后的数据。因为 cout
缓冲区仍是已分配的内存,访问它不会触发段错误。
5. 第三处堆分配:在主函数中的 matrix
静态分配
int matrix[array_number][array_number];
matrix
是一个静态数组,分配在全局内存区域(通常位于数据段)。由于 matrix
的内存地址在堆之外,result[i] = matrix[i];
操作不会触发段错误,即使 result
越界。
6. 输出数据阶段
在主函数中,遍历 result
并输出数据:
for (int i = 0; i < array_number; ++i) {cout << "print address of result[" << i << "] " << &result[i][0] << endl;for (int j = 0; j < array_number; j++) {result[i][j] = j;cout << "print content of result[" << i << "][" << j << "] " << result[i][j] << endl;}
}
在 cout
输出时,cout
缓冲区的内容可能已被越界的 result
写入所覆盖,但这不会立即引发错误。覆盖的内容可能只是错误输出,但不会导致程序崩溃。
堆内存最终布局(带越界覆盖的情况)
-
初始分配:
cout
缓冲区+-------------------+ <-- 堆底 | cout 缓冲区 (1024) | +-------------------+
-
分配
result
并发生越界覆盖+-------------------+ <-- 堆底 | result (覆盖部分) | | 未被访问部分 | <-- result[8] 开始覆盖 +-------------------+
由于越界覆盖未触及未分配的非法内存(仍在已分配的 cout
缓冲区范围内),程序避免了段错误。
2. 原代码中导致错误的原因
2.1 堆内存分配不足
int **result = new int*[8];
分配的 result
指针数组大小仅为 8,但程序中访问了 result[0]
到 result[63]
,显然越界。如果没有 cout
创建的缓冲区,result
的越界可能直接访问未分配或非法的内存区域,导致段错误或堆损坏。
2.2 注释掉 cout
导致未定义行为
当 cout
的第一处调用被注释掉后,堆上不再创建缓冲区,导致 result
的越界访问破坏了堆的关键结构。如果保留 cout
,堆的分配顺序发生了变化,result
的越界行为可能恰好访问到 cout
的缓冲区或未使用的内存区域,从而掩盖了潜在的错误。
3. 解决问题的根本方法
原代码依赖 cout
来掩盖问题是极其不可靠的,正确的方法应当是修复代码中的根本性错误:
正确分配内存大小:
int **result = new int*[n]; // n = 64
避免堆内存越界: 确保对指针数组的访问范围在合法区域内。
解决方案
调整内存分配大小: 确保 result
指针数组的大小与实际访问范围一致:
int **result = new int*[n];
#include <iostream>using namespace std;#define array_number 64int matrix[array_number][array_number];int **double_array(size_t n) {// 修复: 确保分配大小为 nint **result = new int*[n]; for (int i = 0; i < n; ++i) {result[i] = matrix[i]; // 浅拷贝,指向 matrix[i]for (int j = 0; j < n; ++j) {result[i][j] = j; // 初始化 matrix[i][j]}}return result;
}int main() {// 修复: 注释掉 cout 语句不再影响执行// cout << "A magic print! If you comment this, the program will break." << endl;int **result = double_array(array_number);for (int i = 0; i < array_number; ++i) {cout << "print address of result[" << i << "] " << result[i] << endl;for (int j = 0; j < array_number; ++j) {cout << "print content of result[" << i << "][" << j << "] " << result[i][j] << endl;}}// 修复: 仅释放动态分配的 result 数组delete[] result;return 0;
}
代码说明
-
动态内存分配调整:
int **result = new int*[n];
分配大小为
n
,避免越界访问。
结果与总结
修复后程序正常运行,解决了以下问题:避免越界访问:通过正确分配内存,确保访问合法。通过此次修复,理解了内存管理的重要性。