C++调用posix_memalign分配内存访问触发段错误原因排查
问题描述
我编写了如下C++代码,尝试读取二进制文件,并将读取得到的32位值以十六进制格式打印输出:
// hello.cpp file #include <iostream> #include <fstream> #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> int main() { int file_size; // font file size in bytes int i; std::cout << "Hello World\n"; std::string binary_data_file("font.dat"); struct stat statbuff; stat(binary_data_file.c_str(), &statbuff); file_size = statbuff.st_size; void *data_buffer; posix_memalign(&data_buffer, 4096, file_size); std::ifstream data_input_file(binary_data_file.c_str(), std::ios::in | std::ios::binary); data_input_file.read((char *) data_buffer, file_size); data_input_file.close(); int * debug_buffer = (int * ) data_buffer; for (int j = 0; j< 148481; j++) { std::cout << "Data size: " << std::dec << file_size << std::endl; std::cout << "Element: " << j << " Value: " << std::hex << *(debug_buffer + j) << std::endl; } return 0; }
上述代码在循环变量j取值为148480时触发Segmentation Fault(段错误),运行输出如下:
Data size: 211200 Element: 148477 Value: 0 Data size: 211200 Element: 148478 Value: 0 Data size: 211200 Element: 148479 Value: 0 Data size: 211200 Segmentation fault (core dumped)
疑问:分配的缓冲区大小为211200字节,循环变量j理应可以遍历到211200的位置,为何会触发段错误?
根本原因
核心错误是混淆了字节偏移单位和指针运算的步长单位:
- 你分配的缓冲区
file_size = 211200的单位是字节,但将缓冲区强转为int*类型的debug_buffer后,C++的指针偏移运算规则是:偏移量的基本单位为指针指向类型的大小,也就是sizeof(int)。在绝大多数通用系统上int类型占4字节,也就是说j每加1,*(debug_buffer + j)访问的内存地址会向后跳跃4个字节,而非1个字节。 - 按这个规则计算,合法访问整个缓冲区的j上限应为
file_size / sizeof(int) = 211200 / 4 = 52800,也就是j最大只能取到52799,超过这个值就属于缓冲区越界访问。你硬编码的循环上限j < 148481远远超出了合法范围,当j取到148480时,实际访问的内存偏移量为148480 * 4 = 593920字节,超出分配的缓冲区大小38万多字节。 - 段错误没有在j刚越界时立刻触发,是因为内存权限以页(通常4096字节)为单位管理,越界后很长一段地址可能仍属于进程可访问的地址空间(比如内存分配器预留的空闲内存、其他段的映射内存),直到j=148480时刚好访问到内核标记为不可读写的未映射内存页,才触发段错误崩溃。j从52800到148479的所有访问都属于未定义行为,哪怕没有崩溃,读到的也都是不属于目标文件的无效数据。
修复方案
不要硬编码循环上限,根据缓冲区大小和元素类型动态计算合法的元素个数,同时补充必要的错误检查:
#include <iostream> #include <fstream> #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> int main() { std::cout << "Hello World\n"; std::string binary_data_file("font.dat"); struct stat statbuff; // 检查stat调用是否成功 if (stat(binary_data_file.c_str(), &statbuff) != 0) { perror("stat failed"); return 1; } int file_size = statbuff.st_size; void *data_buffer; // 检查内存分配是否成功 if (posix_memalign(&data_buffer, 4096, file_size) != 0) { perror("posix_memalign failed"); return 1; } std::ifstream data_input_file(binary_data_file.c_str(), std::ios::in | std::ios::binary); // 检查文件是否打开成功 if (!data_input_file.is_open()) { perror("open file failed"); free(data_buffer); return 1; } data_input_file.read((char *) data_buffer, file_size); // 检查文件是否读取完整 if (data_input_file.gcount() != file_size) { perror("read file failed"); data_input_file.close(); free(data_buffer); return 1; } data_input_file.close(); int * debug_buffer = (int * ) data_buffer; // 动态计算合法的int元素个数 int elem_count = file_size / sizeof(int); for (int j = 0; j < elem_count; j++) { std::cout << "Data size: " << std::dec << file_size << std::endl; std::cout << "Element: " << j << " Value: " << std::hex << *(debug_buffer + j) << std::endl; } // 释放分配的内存 free(data_buffer); return 0; }
额外注意
如果文件大小不是sizeof(int)的整数倍,最后剩余不足4字节的部分不能直接按int类型读取,否则仍会出现小范围越界,需要单独按字节处理。
内容的提问来源于stack exchange,提问作者Jonas
相关产品推荐
相关产品推荐

