Как файл может содержать нулевые байты?
как возможно, что файлы могут содержать нулевые байты в операционных системах, написанных на языке с нулевыми строками (а именно, C)?
например, если я запускаю этот шелл код:
$ printf "Hello, 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, World!
? Есть ли фундаментальное разница между файлами и строками?
6 ответов
строки с нулевым завершением-это конструкция C, используемая для определения конца последовательности символов, предназначенных для использования в качестве строки. Функции управления строками, такие как strcmp
, strcpy
, strchr
, и другие используют эту конструкцию для выполнения своих обязанностей.
но вы все равно можете читать и записывать двоичные данные, которые содержат нулевые байты в вашей программе, а также В и из файлов. Ты просто не можешь относиться к ним как к струнам.
вот пример того, как это работает:
#include <stdio.h>
#include <stdlib.h>
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 элемента размером int
, все значения в массиве отображаются в файле. Из выходных данных видно, что все значения записаны, значения 32-разрядные, и каждое значение записывается в порядке байтов. Мы также видим, что второй и четвертый элементы массива содержат по одному нулевому байту, а третье значение значение 0 имеет 4 байта null, и все они отображаются в файле.
мы также использовать fwrite
на втором массиве, который содержит элементы типа char
и мы снова видим, что все элементы массива появляются в файле. В частности, третье значение в массиве-0, которое состоит из одного байта null, который также появляется в файле.
третий массив записывается с
null-завершенной строки конечно не единственное, что вы можете поместить в файл. Код операционной системы не рассматривает файл как средство хранения строк с нулевым завершением: операционная система представляет файл как набор произвольных байтов.
Что касается C, 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 printf("dummy0foo");
никогда не будет печатать foo
as printf
рассмотрит строку C, начинающуюся с d
и заканчивается нулевым байтом посередине. Некоторые компиляторы жалуются на такой строковый литерал C.
теперь нет прямой связи между строками C (которые обычно также содержат только печатаемый символ) и текстовым файлом. При печати строки C в файл обычно состоит в сохранении только его подпоследовательности ненулевых байтов.
в то время как нулевые байты используются для завершения строк и необходимы для функций манипуляции строками (чтобы они знали, где заканчивается строка), в двоичных файлах байты могут быть везде.
рассмотрим двоичный файл с 32-разрядными числами, например, все они будут содержать нулевые байты, если их значения меньше 2^24 (например: 0x001a00c7, или 64-разрядный 0x0000000а00001a4d).
Idem для Unicode-16 где все символы ASCII имеют начальный или конечный в зависимости от их endianness, а строки должны заканчиваться на
.
многие файлы даже имеют блоки, проложенные (до 4kB или даже 64kB) с байты, чтобы иметь быстрый доступ к нужным блокам.
для еще большего количества нулевых байтов в файле взгляните на разреженные файлы, где все байты по умолчанию, и блоки, полные нулевых байтов, даже не сохраняются на диске для сохранения пространство.
рассмотрим обычные вызовы функции C для записи данных в файлы -write(2)
:
ssize_t
write(int fildes, const void *buf, size_t nbyte);
и fwrite(3)
:
size_t
fwrite(const void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);
ни одна из этих функций принимает const char *
строка с нулевым завершением. Скорее, они берут массив байтов (a const void *
) с явным размером. Эти функции обрабатывают нулевые байты так же, как и любое другое значение байта.
прежде чем ответить на что-либо, обратите внимание, что
(Примечание: согласно n.м. (см. комментарий в OP) " a байт наименьшее количество доступное для записи на диск со стандартной библиотекой C, нестандартные библиотеки вполне могут иметь дело с битами или чем-либо еще."Итак, то, что я сказал ниже о размерах слов, являющихся наименьшим количеством, вероятно, не очень верно, но все же дает представление однако.)
NULL всегда 0_decimal (практически)
dec: 0
hex: 0x00000000
bin: 00000000 00000000 00000000 00000000
хотя это фактическое значение определяется спецификацией языка программирования, поэтому используйте определенную константу NULL
вместо того, чтобы хардкодить 0
везде (в случае его изменения, когда ад замерзнет).
ASCII кодировка для символа ' 0 ' - 48_decimal
dec: 48
hex: 0x00000030
bin: 00000000 00000000 00000000 00110000
концепция NULL
не существует в файле,но в языке программирования генерирующего приложения. Просто числовая кодировка / значение .
как возможно, что файлы могут содержать нулевые байты при работе системы, написанные на языке с нулевыми строками (а именно, В)?
с вышеизложенным этот вопрос становится,как файл может содержать 0? ответ теперь тривиален.
например, если я запускаю этот шелл код:
$ printf "Hello, World!" test.txt $ xxd test.txt 0000000: 4865 6c6c 6f00 2c20 576f 726c 6421 Hello., World!
я вижу нулевой байт в тест.txt (по крайней мере, в OS X). Если c использует нулевые завершающие строки, А OS X написана на C, то как получилось, что файл не завершается на нулевом байте, в результате чего файл содержащий
Hello
вместоHello, World!
?есть принципиальная разница между файлами и строками?
предполагая ASCII кодировка символов (1-байтовые/8-битные символы в десятичном диапазоне 0 и 127):
- строки являются буферами / char-массивами 1 байтовых символов (где NULL = 0_decimal и '0' = 48_decimal)).
- файлы являются последовательностями 32-разрядных или 64-разрядных"слова "(зависит от ОС и оборудования, т. е. x86 или x64 соответственно).
таким образом, 32-разрядный файл ОС который содержит только ASCII строки будут последовательностью 32-разрядных (4-байтовых) слов, которые варьируются между десятичными значениями 0 и 127, по существу, используя только первый байт 4-байтового слова (b2: base-2, decimal is base-10 и hex base-16, fyi)
0_b2: 00000000 00000000 00000000 00000000
32_b2: 00000000 00000000 00000000 00100000
64_b2: 00000000 00000000 00000000 01000000
96_b2: 00000000 00000000 00000000 01100000
127_b2: 00000000 00000000 00000000 11111111
128_b2: 00000000 00000000 00000001 00000000
погода этот байт левый или правый-больше всего зависит от ОС endianness.
но чтобы ответить на ваш вопрос о пропавших без вести NULL
после Hello, World!
Я предположить, что он был заменен EOL / EOF (конец файла) значение, которое, скорее всего, не печатается и поэтому вы не видите его в окне вывода.
Примечание: я уверен, что современные ОС (и классические системы на основе Unix) оптимизируют хранение ASCII символы, так что 1 Слово (4 байта) может упаковываться в 4 символа. Все меняется с UTF однако, поскольку эти кодировки используют больше бит для хранения символов, так как они имеют большие алфавиты/наборы символов для представления (например, 50k Кандзи/японские символы). Я думаю UTF-8 это analogus к ASCII и переименован для единообразия (с UTF-16 и UTF-32).
Примечание: C / C++ фактически "упаковывает" 4 символа в одно 4-байтовое слово, используя символьные массивы (т. е. строки). Поскольку каждый символ равен 1 байту, компилятор выделит и обработает его как 1 байт, арифметически, в стеке или куче. Поэтому, если вы объявляете массив в функции (т. е. автоматической переменной), например
char[] str1[7] = {'H','e','l','l','o','!',''};
где стек функций начинается с адреса 1000_b10( base-10 / decimal), то у вас есть:
072 101 108 108 111 033
addr char binary decimal
---- ----------- -------- -------
1000: str1[0] 'H' 01001000 (072)
1001: str1[1] 'e' 01100101 (101)
1002: str1[2] 'l' 01101100 (108)
1003: str1[3] 'l' 01101100 (108)
1004: str1[4] 'o' 01101111 (111)
1005: str1[5] '!' 00100001 (033)
1006: str1[6] '0' 00000000 (000)
поскольку ОЗУ является байт-адресуемым, каждый адрес ссылается на один байт.