文件如何在用空终止字符串(即C)语言编写的操作系统中包含空字节?
例如,如果我运行此shell代码:
$ printf "Hello 0, 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 0, 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[] = "Hellox0World"; // 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, "%sn", 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个元素大小的int
,所以数组中的所有值都出现在文件中。 您可以从输出中看到所有值都已写入,值为32位,并且每个值都以little-endian字节顺序写入。 我们还可以看到数组的第二个和第四个元素每个都包含一个空字节,而第三个值为0则有4个空字节,并且都出现在文件中。
我们还在第二个数组上使用fwrite
,它包含char
类型的元素,我们再次看到所有数组元素都出现在文件中。 特别是,数组中的第三个值为0,它由一个也出现在文件中的空字节组成。
第一个数组首先用fprintf
函数编写,使用%s
格式说明符,它需要一个字符串。 它在遇到空字节之前将此数组的前5个字节写入文件,之后它将停止读取数组。 然后根据格式打印换行符( 0x0a
)。
它再次写入文件的第三个数组,这次使用fwrite
。 字符串常量"Hellox0World"
包含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
是一个字符数组,但它不是以null结尾的字符串。
因为文件只是一个字节流,包括空字节在内的任何字节。 有些文件只包含所有可能字节的子集时称为文本文件:可打印的文件(大致字母数字,空格,标点符号)。
C字符串是由空字节终止的字节序列,只是常规问题。 它们往往是混乱的根源; 只是一个以null结尾的序列,意味着任何以null结尾的非空字节都是正确的C字符串! 甚至包含不可打印字节或控件字符的字符。 要小心,因为你的例子不是C的! 在C printf("dummy 00foo");
永远不会打印foo
因为printf
会考虑从d
开始到中间空字节结束的C字符串。 一些编译器抱怨这样的C字符串文字。
现在C字符串(通常也只包含可打印的字符)和文本文件之间没有直接链接。 将C字符串打印到文件中通常只包括存储非空字节的子序列。
虽然null-bytes用于终止字符串并且需要字符串操作函数(因此它们知道字符串结束的位置),但在二进制文件中