实现数据链路协议时fclose()触发段错误问题求助
段错原因分析:
fclose()触发崩溃与文件打开时机的关联 问题背景
实现数据链路协议时,通过虚拟串口传输文件,调用fclose()触发段错误。输出文件已创建但无内容,即使改为先将文件缓冲到内存再写入仍未解决;将文件打开时机从函数开头移至写入前,问题解决。
相关代码
int app_rx(const char* outputFile){ file_data_t fileData; if(!receive_ctrl_pckt(&fileData)){ printf("Could not receive control packet\n"); return FAILURE; } printf("Receiving file [%s]\nFile size: %d\n", fileData.fileName, fileData.fileSize); unsigned char fileBuffer[fileData.fileSize]; int fileIndex = 0; int totalBytes = 0; int stop = 0; size_t sqNo = 0; int bytes; FILE* out = fopen(outputFile, "w"); do{ activeBuffer = (activeBuffer + 1) % 2; printf("Receiving packet %lu\n", sqNo); bytes = llread(BUFFERS[TMP_BUFFER]); if(bytes == DUP_ERR){ printf("Duplicate data. Discarding packet\n"); } else if (bytes == WH_ERR){ printf("Invalid header. Discarding packet\n"); } else if (bytes == WD_ERR){ printf("Corrupted data. Awaiting retransmission\n"); } else{ printf("Packet %lu successfully received\n", sqNo); int retrieveRes = retrieve_payload(sqNo); switch(retrieveRes){ case CTRL_END: { stop = 1; break; } case FAILURE: { printf("Unknown error\nExiting..."); exit(1); } case SQ_ERR:{ printf("Unsynchronized packets\nExiting"); exit(1); } default:{ for(int i = 0; i < retrieveRes; i++){ fileBuffer[fileIndex] = BUFFERS[activeBuffer][i]; fileIndex++; } sqNo = (sqNo + 1) % 255; totalBytes += bytes; break; } } } }while(!stop); printf("file index: %d\n", fileIndex); fwrite(fileBuffer, sizeof(unsigned char), fileIndex, out); printf("here\n"); fclose(out); if(totalBytes == fileData.fileSize){ return SUCCESS; } else{ return FAILURE; } }
标准输出
Packet 9 successfully received Receiving packet 10 Asserting data integrity Packet 10 successfully received Receiving packet 11 Asserting data integrity Packet 11 successfully received file index: 10968 here make: *** [Makefile:35: run_rx] Segmentation fault (core dumped)
核心原因分析
1. 栈溢出破坏FILE指针
代码开头定义了栈上的大数组:
unsigned char fileBuffer[fileData.fileSize];
系统栈的默认空间有限(Linux下通常为8MB,Windows下约1MB),当fileData.fileSize较大时,这个数组会占用大量栈空间,触发栈溢出。
栈的分配顺序是从高地址向低地址延伸:先定义的fileBuffer占据高地址区域,后续的FILE* out指针位于低地址区域。如果fileBuffer的大小超过栈剩余可用空间,会直接覆盖out指针的内存,使其变成野指针。后续调用fclose(out)时,访问野指针就会触发段错误。
将fopen移至写入前,out指针的赋值发生在文件接收完成后,栈上的溢出风险已经过去,指针不会被破坏。
2. 长时间持有句柄导致的内存污染
在文件接收的循环中,你调用了llread、retrieve_payload等底层串口操作函数。如果这些函数存在内存越界、指针错误等问题,可能会意外修改out指向的FILE结构体内容。
文件句柄从函数开头到接收完成被长时间持有,暴露在内存污染风险下的窗口更长。而将fopen移至写入前,句柄仅在写入和关闭时被持有,污染概率大幅降低。
3. 虚拟串口驱动的资源冲突
部分虚拟串口驱动可能与标准C库的文件IO共享底层资源(如文件描述符表、内存缓冲区)。长时间打开文件句柄可能引发驱动与C库的资源竞争,破坏FILE结构体的内部状态,最终导致fclose崩溃。
验证与修复建议
- 检查栈大小:Linux下用
ulimit -s查看当前栈大小,若fileData.fileSize接近或超过该值,说明栈溢出是主因。 - 改用堆分配:将
fileBuffer改为malloc分配,避免栈溢出:unsigned char* fileBuffer = malloc(fileData.fileSize); if (!fileBuffer) { /* 处理内存分配失败 */ } // 使用完成后调用free(fileBuffer); - 调试指针完整性:在
fopen后和fclose前打印out的地址,若地址不一致,说明指针被破坏。
内容的提问来源于stack exchange,提问作者Pedro Barbeira
相关产品推荐
相关产品推荐

