关于printf处理二进制文件字符串的两类技术疑问
先看这段将文件读取到内存的代码:
FILE *fileptr; char *buffer; long filelen; fileptr = fopen("myfile.txt", "rb"); // 以二进制模式打开文件 fseek(fileptr, 0, SEEK_END); // 跳转至文件末尾 filelen = ftell(fileptr); // 获取当前文件字节偏移量 rewind(fileptr); // 跳转回文件开头 buffer = (char *)malloc((filelen+1)*sizeof(char)); // 分配足够存文件内容+终止符的内存 fread(buffer, filelen, 1, fileptr); // 读取整个文件内容 fclose(fileptr); // 关闭文件
这段代码能把文件内容读取成字节数组,本质可以当作字符串使用。如果再添加以下两行代码:
buffer[filelen] = '\0';
和
printf("%s" , buffer);
理论上就能像打印普通字符串一样输出整个文件内容——但这个操作只在普通文本文件里有效,碰到二进制文件就失效了。
有人尝试编写了这样一个遍历函数来处理二进制文件:
void traverse(char *string ,size_t size){ for(size_t i=0;i<size;i++) printf("%c",string[i]); }
结果运行后屏幕输出全是乱码,哪怕把字符串类型改成unsigned char,结果还是一样。
下面来解答这两个核心疑问:
疑问1:为何printf在处理二进制文件时不将buffer视为字符串?
这得从printf("%s")的工作逻辑说起:它会从传入的指针位置开始逐个输出字符,直到碰到**\0(空字符,ASCII码为0)**才会停止。但二进制文件里经常会包含ASCII码为0的字节——比如可执行文件、图片文件中,这类字节是文件结构的必要组成部分,并非字符串终止符。所以printf("%s")读到第一个\0就直接停止输出,根本无法打印完整的二进制文件内容。
而文本文件里的内容都是可打印字符(或换行、制表这类常见控制字符),不会随意出现\0,所以用%s格式打印完全没问题。
疑问2:为何traverse函数中的printf输出乱码而非正常字符?
二进制文件里的字节可不都是可打印字符!比如图片的像素数据、可执行文件的机器指令,很多字节对应的ASCII码属于非打印控制字符(比如ASCII码0-31、127),这些字符输出到终端时,要么显示成乱码、方块,要么会触发终端的特殊行为(比如响铃、光标跳转)。
哪怕把char改成unsigned char,也只是改变了字节的符号解读方式(不会把负数当作符号位),但这些字节对应的字符本身就是不可打印的,终端无法将它们转换成人类能看懂的正常字符,自然就显示成乱码了。
内容的提问来源于stack exchange,提问作者anjanik012

