文件如何在用空终止字符串(即C)的语言编写的操作系统中包含空字节?
例如,如果我运行此shell代码:
$ printf "Hello\00, World!" > test.txt $ xxd test.txt 0000000: 4865 6c6c 6f00 2c20 576f 726c 6421 Hello., World!
我看到一个空字节test.txt
(至少在OS X中).如果C使用空终止字符串,并且OS X是用C语言编写的,那么为什么文件不会在空字节处终止,从而导致文件包含Hello
而不是Hello\00, World!
?文件和字符串之间是否存在根本区别?
以空值终止的字符串是一种C结构,用于确定要用作字符串的字符序列的结尾.字符串操作功能,例如strcmp
,strcpy
,strchr
,和其他人使用这个结构来履行职责.
但您仍然可以在程序中以及文件中读取和写入包含空字节的二进制数据.你不能把它们视为字符串.
这是一个如何工作的例子:
#include#include int main() { FILE *fp = fopen("out1","w"); if (fp == NULL) { perror("fopen failed"); exit(1); } int a1[] = { 0x12345678, 0x33220011, 0x0, 0x445566 }; char a2[] = { 0x22, 0x33, 0x0, 0x66 }; char a3[] = "Hello\x0World"; // this writes the whole array fwrite(a1, sizeof(a1[0]), 4, fp); // so does this fwrite(a2, sizeof(a2[0]), 4, fp); // this does not write the whole array -- only "Hello" is written fprintf(fp, "%s\n", a3); // but this does fwrite(a3, sizeof(a3[0]), 12, fp); fclose(fp); return 0; }
out1的内容:
[dbush@db-centos tmp]$ xxd out1 0000000: 7856 3412 1100 2233 0000 0000 6655 4400 xV4..."3....fUD. 0000010: 2233 0066 4865 6c6c 6f0a 4865 6c6c 6f00 "3.fHello.Hello. 0000020: 576f 726c 6400 World.
对于第一个数组,因为我们使用该fwrite
函数并告诉它写入4个大小为a的元素int
,所以数组中的所有值都出现在文件中.您可以从输出中看到所有值都已写入,值为32位,并且每个值都以little-endian字节顺序写入.我们还可以看到数组的第二个和第四个元素每个都包含一个空字节,而第三个值为0则有4个空字节,并且都出现在文件中.
我们还在fwrite
包含类型元素的第二个数组上使用char
,我们再次看到所有数组元素都出现在文件中.特别是,数组中的第三个值为0,它由一个也出现在文件中的空字节组成.
第一个数组首先用fprintf
函数编写,使用%s
格式说明符,它需要一个字符串.它在遇到空字节之前将此数组的前5个字节写入文件,之后它将停止读取数组.然后0x0a
按照格式打印换行符(\n ).
它再次写入文件的第三个数组,这次使用fwrite
.字符串常量"Hello\x0World"
包含12个字节:5表示"Hello",1表示显式空字节,5表示"World",1表示空字节隐式结束字符串常量.由于fwrite
给定了数组(12)的完整大小,因此它会写入所有这些字节.实际上,查看文件内容,我们会看到每个字节.
作为旁注,在每个fwrite
调用中,我已经为第三个参数硬编码数组的大小,而不是使用更动态的表达式,sizeof(a1)/sizeof(a1[0])
以便更清楚地确定在每种情况下写入多少字节.
以空字符结尾的字符串肯定不是您可以放入文件的唯一内容.操作系统代码不认为文件是存储以空字符结尾的字符串的工具:操作系统将文件呈现为任意字节的集合.
就C而言,存在用于以二进制模式写入文件的I/O API.这是一个例子:
char buffer[] = {0, 1, 0, 2, 0, 3, 0, 4, 0, 5}; FILE *f = fopen("data.bin","wb"); // "w" is for write, "b" is for binary fwrite(buffer, 1, sizeof(buffer), f);
此C代码创建一个名为"data.bin"的文件,并将十个字节写入其中.请注意,虽然buffer
是字符数组,但它不是以空字符结尾的字符串.
因为文件只是一个字节流,包括空字节在内的任何字节.有些文件只包含所有可能字节的子集,称为文本文件:可打印的文件(大致字母数字,空格,标点符号).
C字符串是由空字节终止的字节序列,只是常规问题.它们往往是混乱的根源; 只是一个以null结尾的序列,意味着任何以null结尾的非空字节都是正确的C字符串!甚至包含不可打印字节或控件字符的字符.要小心,因为你的例子不是C的!在C printf("dummy\000foo");
中将永远不会打印,foo
因为printf
将考虑从d
中间的空字节开始并结束的C字符串.一些编译器抱怨这样的C字符串文字.
现在C字符串(通常也只包含可打印的字符)和文本文件之间没有直接链接.将C字符串打印到文件中通常只包括存储非空字节的子序列.
虽然null-bytes用于终止字符串并且需要字符串操作函数(因此它们知道字符串结束的位置),但在二进制文件中,\0
字节可以在任何地方.
例如,考虑具有32位数字的二进制文件,如果它们的值小于2 ^ 24,则它们都将包含空字节(例如:0x 00 1a 00 c7或64位0x 000000 0a 0000 1a4d).
同义词为Unicode-16,其中所有ASCII字符都有前导或尾随\0
,具体取决于它们的字节顺序,字符串需要以\0\0
.
许多文件甚至具有用\0
字节填充(到4kB甚至64kB)的块,以便快速访问所需的块.
对于文件中甚至更多的空字节,请查看稀疏文件,其中\0
默认情况下所有字节都是,并且甚至没有将完整的空字节块存储在磁盘上以节省空间.