C语言课程作业:WAV文件反转的运行异常与内存问题
C语言WAV文件反转程序问题解答
问题1:短音频反转正常,长音频失效的原因
你的程序处理长音频失效,核心原因有三个:
1. 文件指针初始定位错误
你用fseek(input, n, SEEK_END);将指针定位到文件末尾之后n字节的位置,这是完全错误的。对于短文件,系统可能允许指针超出文件范围,后续操作碰巧能凑出正确结果;但长文件会导致fread读取无效数据,甚至触发读写错误。
正确的做法是将指针定位到最后一个音频块的起始位置,即从文件末尾往前偏移n字节:
fseek(input, -n, SEEK_END);
2. 循环条件不严谨且存在大文件溢出风险
while (ftell(input) != sizeof(WAVHEADER))的判断逻辑有两个问题:
- 当处理第一个音频块时,
fread后指针会移动到sizeof(WAVHEADER),此时执行fseek(input, -2 * n, SEEK_CUR)会跳到sizeof(WAVHEADER) - n,循环条件不满足,会漏掉第一个块的写入。 ftell返回的long类型在32位系统下最多只能表示2GB文件,长音频文件超出这个范围会导致返回值溢出,循环条件判断完全失效。
正确的循环条件应该是判断当前指针位置大于等于数据区起始位置(即sizeof(WAVHEADER)):
while (ftell(input) >= sizeof(WAVHEADER))
3. 音频块缓冲区溢出
你用WORD temp;存储音频块,但WORD是16位(2字节)类型,而音频块大小n可能是4字节(比如16位双声道)。fread(&temp, n, 1, input)会强行向2字节的变量写入4字节数据,导致栈缓冲区溢出。短文件可能因为操作次数少没触发崩溃,长文件多次溢出后会破坏栈结构,导致程序失效。
正确的做法是根据n动态分配缓冲区:
BYTE *temp = malloc(n); if (temp == NULL) { // 内存分配失败处理 fclose(input); fclose(output); return 1; } // 后续用temp读取和写入 free(temp);
问题2:删除get_data_size函数时而出现段错误的原因
核心原因:未检查文件操作的返回值,且指针定位错误导致无效读写
get_data_size函数的作用是将文件指针移到文件末尾,但它本身没有任何实际作用。当你保留这个函数时,文件指针已经在末尾,后续fseek(input, n, SEEK_END)会把指针移到末尾+n的位置;删除该函数后,指针停在WAV头之后的位置,同样会被移到末尾+n的位置——两种情况的指针位置是一样的。
出现段错误的差异是因为:
- 当保留
get_data_size时,栈上多了该函数的局部变量,改变了栈布局,缓冲区溢出(问题1中的第3点)没有破坏到关键栈数据(如返回地址),所以程序没崩溃。 - 删除该函数后,栈布局变化,缓冲区溢出直接覆盖了关键栈数据,触发段错误。
- 后续再次删除没有出现段错误,可能是编译器优化、测试文件不同或栈布局随机变化导致的巧合,但潜在的缓冲区溢出问题依然存在。
修复后的完整代码示例
#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include "wav.h" int check_format(WAVHEADER header); int get_block_size(WAVHEADER header); int main(int argc, char *argv[]) { // Ensure proper usage if (argc != 3) { printf("Usage: %s <input.wav> <output.wav>\n", argv[0]); return 2; } // Open input file for reading char *infile = argv[1]; char *outfile = argv[2]; FILE *input = fopen(infile, "rb"); if (input == NULL) { printf("Could not open input file: %s\n", infile); return 1; } // Read header WAVHEADER inwav; if (fread(&inwav, sizeof(WAVHEADER), 1, input) != 1) { printf("Failed to read WAV header\n"); fclose(input); return 1; } // Validate WAV format if (check_format(inwav) != 0) { fclose(input); printf("Input file is not a valid WAV file\n"); return 3; } // Open output file for writing FILE *output = fopen(outfile, "wb"); if (output == NULL) { printf("Could not open output file: %s\n", outfile); fclose(input); return 1; } // Write header to output if (fwrite(&inwav, sizeof(WAVHEADER), 1, output) != 1) { printf("Failed to write WAV header\n"); fclose(input); fclose(output); return 1; } // Calculate block size int n = get_block_size(inwav); if (n == 0) { fclose(input); fclose(output); return 1; } // Allocate buffer for audio block BYTE *temp = malloc(n); if (temp == NULL) { printf("Memory allocation failed\n"); fclose(input); fclose(output); return 1; } // Position pointer to the last audio block if (fseek(input, -n, SEEK_END) != 0) { printf("Failed to seek input file\n"); free(temp); fclose(input); fclose(output); return 1; } // Write audio blocks in reverse order while (ftell(input) >= sizeof(WAVHEADER)) { // Read one block if (fread(temp, n, 1, input) != 1) { printf("Failed to read audio block\n"); break; } // Write the block to output if (fwrite(temp, n, 1, output) != 1) { printf("Failed to write audio block\n"); break; } // Seek back two blocks to read the previous one if (fseek(input, -2 * n, SEEK_CUR) != 0) { // If seeking fails, we've reached the start of data break; } } // Cleanup free(temp); fclose(input); fclose(output); return 0; } int check_format(WAVHEADER header) { return (header.format[0] == 'W' && header.format[1] == 'A' && header.format[2] == 'V' && header.format[3] == 'E') ? 0 : 1; } int get_block_size(WAVHEADER header) { if (header.numChannels != 1 && header.numChannels != 2) { printf("Unsupported number of channels: %d\n", header.numChannels); return 0; } if (header.bitsPerSample != 8 && header.bitsPerSample != 16) { printf("Unsupported bits per sample: %d\n", header.bitsPerSample); return 0; } return (header.bitsPerSample / 8) * header.numChannels; }
额外修复说明:
- 打开文件时使用
rb和wb模式,确保二进制文件读写的兼容性(Windows系统下必须)。 - 增加了所有文件操作的返回值检查,避免无效操作导致的未知错误。
- 完善了错误提示信息,便于调试。
- 优化了
check_format和get_block_size的逻辑,增加了对采样位数的合法性检查。
内容的提问来源于stack exchange,提问作者NRevan
相关产品推荐
相关产品推荐

