tutorial - para que se usa elastic search
¿Cómo puede un archivo contener bytes nulos? (6)
¿Cómo es posible que los archivos puedan contener bytes nulos en sistemas operativos escritos en un lenguaje con cadenas de terminación nula (es decir, C)?
Por ejemplo, si ejecuto este código de shell:
$ printf "Hello/00, World!" > test.txt
$ xxd test.txt
0000000: 4865 6c6c 6f00 2c20 576f 726c 6421 Hello., World!
Veo un byte nulo en test.txt
(al menos en OS X). Si C usa cadenas que terminan en nulo y OS X está escrito en C, entonces, ¿cómo es que el archivo no termina en el byte nulo, lo que da como resultado que el archivo contenga Hello
lugar de Hello/00, World!
? ¿Hay una diferencia fundamental entre archivos y cadenas?
Antes de responder nada, por favor tenga en cuenta que
( nota: de acuerdo con nm (vea los comentarios en OP) "un byte es la cantidad más pequeña disponible para escribir en el disco con la biblioteca estándar de C, las bibliotecas no estándar pueden tratar con bits o cualquier otra cosa". sobre el tamaño de la PALABRA que es la cantidad más pequeña probablemente no sea muy cierta, pero aún así proporciona una visión).
NULL siempre es 0_decimal ( prácticamente )
dec: 0
hex: 0x00000000
bin: 00000000 00000000 00000000 00000000
aunque su valor real está definido por la especificación de un lenguaje de programación, por lo tanto, use la constante NULL
definida en lugar de 0
cualquier lugar (en caso de que cambie, cuando el infierno se congele).
ASCII codificación ASCII para el carácter ''0'' es 48_decimal
dec: 48
hex: 0x00000030
bin: 00000000 00000000 00000000 00110000
El concepto de NULL
no existe en un archivo, pero dentro del lenguaje de programación de la aplicación de generación. Solo la codificación / valor numérico de NULL
existe en un archivo.
¿Cómo es posible que los archivos puedan contener bytes nulos en sistemas operativos escritos en un lenguaje con cadenas de terminación nula (es decir, C)?
Con lo mencionado anteriormente, esta pregunta se convierte en: ¿cómo puede un archivo contener 0? La respuesta es ahora trivial.
Por ejemplo, si ejecuto este código de shell:
$ printf "Hello/00, World!" test.txt $ xxd test.txt 0000000: 4865 6c6c 6f00 2c20 576f 726c 6421 Hello., World!
Veo un byte nulo en test.txt (al menos en OS X). Si C usa cadenas que terminan en nulo y OS X está escrito en C, entonces, ¿cómo es que el archivo no termina en el byte nulo, lo que da como resultado que el archivo contenga
Hello
lugar deHello/00, World!
?¿Hay una diferencia fundamental entre archivos y cadenas?
Suponiendo una codificación de caracteres ASCII caracteres de 1 byte / 8 bits en el rango decimal de 0 y 127):
- Las cadenas son buffers / char-arrays de 1 byte caracteres (donde NULL = 0_decimal y ''0'' = 48_decimal)).
- Los archivos son secuencias de " palabras " de 32 bits o de 64 bits (depende del sistema operativo y del hardware, es decir, x86 o x64 respectivamente).
Por lo tanto, un archivo de sistema operativo de 32 bits que contiene solo cadenas ASCII será una secuencia de palabras de 32 bits (4 bytes) que oscilan entre los valores decimales 0 y 127, esencialmente utilizando solo el primer byte de la palabra de 4 bytes ( b2: base-2, decimal es base-10 y hexadecimal 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
El clima de este byte está más a la izquierda o más a la derecha depende de la endianness del sistema operativo.
Pero para responder a su pregunta sobre el NULL
falta después de Hello/00, World!
Voy a suponer que fue sustituido por el valor EOL / EOF (final del archivo), que probablemente no sea imprimible y por eso no lo ve en la ventana de resultados.
Nota: Estoy seguro de que el sistema operativo moderno (y los sistemas basados en Unix clásico) optimizan el almacenamiento de caracteres ASCII , de modo que una palabra (4 bytes) puede empaquetar en 4 caracteres. Sin embargo, las cosas cambian con UTF , ya que estas codificaciones utilizan más bits para almacenar caracteres, ya que tienen alfabetos / juegos de caracteres más grandes para representar (como 50 kanji / caracteres japoneses). Creo que UTF es análogo a ASCII , y se le cambió el nombre por uniformidad (con UTF y UTF ).
Nota: C / C ++ de hecho "empaqueta" 4 caracteres en una sola palabra de 4 bytes usando matrices de caracteres (es decir, cadenas). Como cada caracter es de 1 byte, el compilador lo asignará y lo tratará como 1 byte, aritméticamente, en la pila o el montón. Entonces, si declara una matriz en una función (es decir, una auto-variable), así
char[] str1[7] = {''H'',''e'',''l'',''l'',''o'',''!'',''/0''};
donde la pila de funciones comienza en la dirección 1000_b10 (base-10 / decimal), entonces ya tiene:
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)
Dado que la RAM es direccionable por bytes, cada dirección hace referencia a un solo byte.
Considere las llamadas de función C habituales para escribir datos en archivos - write(2)
:
ssize_t
write(int fildes, const void *buf, size_t nbyte);
... y fwrite(3)
:
size_t
fwrite(const void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);
Ninguna de estas funciones acepta una cadena const char *
terminada en NUL. Más bien, toman una matriz de bytes (un const void *
) con un tamaño explícito. Estas funciones tratan los bytes NUL como cualquier otro valor de byte.
Debido a que un archivo es solo un flujo de bytes, de cualquier byte que incluya un byte nulo. Algunos archivos se denominan archivos de texto cuando solo contienen un subconjunto de todos los bytes posibles: los imprimibles (aproximadamente alfanuméricos, espacios, puntuación).
Las cadenas C son una secuencia de bytes terminados por un byte nulo, solo una cuestión de convención. Con demasiada frecuencia son fuente de confusión; solo una secuencia terminada por nulo, significa que cualquier byte no nulo terminado por nulo es una cadena C correcta! Incluso uno que contiene un byte no imprimible, o un carácter de control. ¡Ten cuidado porque tu ejemplo no es uno C! En C printf("dummy/000foo");
nunca imprimirá foo
ya que printf
considerará que la cadena C comienza en d
y termina en el byte nulo en el medio. Algunos compiladores se quejan de tal literal en C.
Ahora no hay un enlace directo entre las cadenas en C (que generalmente también contiene solo caracteres imprimibles) y el archivo de texto. Mientras que la impresión de una cadena C en un archivo generalmente consiste en almacenar solo su subsecuencia de bytes no nulos.
Las cadenas terminadas en nulo no son ciertamente lo único que puede poner en un archivo. El código del sistema operativo no considera que un archivo sea un vehículo para almacenar cadenas terminadas en nulo: un sistema operativo presenta un archivo como una colección de bytes arbitrarios.
En lo que respecta a C, existen API de E / S para escribir archivos en modo binario. Aquí hay un ejemplo:
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);
Este código C crea un archivo llamado "data.bin", y escribe diez bytes en él. Tenga en cuenta que aunque el buffer
es una matriz de caracteres, no es una cadena terminada en cero.
Las cadenas terminadas en nulo son una construcción C utilizada para determinar el final de una secuencia de caracteres que se pretende utilizar como una cadena. Las funciones de manipulación de cadenas como strcmp
, strcpy
, strchr
y otras utilizan este constructo para realizar sus tareas.
Pero aún puede leer y escribir datos binarios que contengan bytes nulos dentro de su programa, así como hacia y desde archivos. Simplemente no puedes tratarlos como cuerdas.
Aquí hay un ejemplo de cómo funciona esto:
#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;
}
Contenido de 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.
Para la primera matriz, porque usamos la función fwrite
y le decimos que escriba 4 elementos del tamaño de un int
, todos los valores de la matriz aparecen en el archivo. Puede ver en la salida que todos los valores se escriben, los valores son de 32 bits y cada valor se escribe en orden de bytes little-endian. También podemos ver que el segundo y cuarto elementos de la matriz contienen cada uno un byte nulo, mientras que el tercer valor que es 0 tiene 4 bytes nulos, y todos aparecen en el archivo.
También usamos fwrite
en la segunda matriz, que contiene elementos de tipo char
, y nuevamente vemos que todos los elementos de la matriz aparecen en el archivo. En particular, el tercer valor en la matriz es 0, que consiste en un solo byte nulo que también aparece en el archivo.
La tercera matriz se escribe primero con la función fprintf
usando un especificador de formato %s
que espera una cadena. Escribe los primeros 5 bytes de esta matriz en el archivo antes de encontrar el byte nulo, después de lo cual deja de leer la matriz. Luego imprime un carácter de nueva línea ( 0x0a
) según el formato.
El tercer array lo escribió nuevamente en el archivo, esta vez usando fwrite
. La constante de cadena "Hello/x0World"
contiene 12 bytes: 5 para "Hello", uno para el byte nulo explícito, 5 para "World" y uno para el byte nulo que termina implícitamente la constante de cadena. Como a fwrite
se le da el tamaño completo de la matriz (12), escribe todos esos bytes. De hecho, mirando el contenido del archivo, vemos cada uno de esos bytes.
Como nota al margen, en cada una de las llamadas a fwrite
, he codificado el tamaño de la matriz para el tercer parámetro en lugar de usar una expresión más dinámica como sizeof(a1)/sizeof(a1[0])
para hacerlo más Borre exactamente cuántos bytes se están escribiendo en cada caso.
Si bien los bytes nulos se usan para terminar cadenas y se necesitan para las funciones de manipulación de cadenas (para que sepan dónde termina la cadena), los archivos binarios /0
bytes pueden estar en todas partes.
Considere un archivo binario con números de 32 bits, por ejemplo, todos contendrán bytes nulos si sus valores son menores que 2 ^ 24 (por ejemplo: 0x 00 1a 00 c7, o 64 bits 0x 000000 0a 0000 1a4d).
Idem para Unicode-16, donde todos los caracteres ASCII tienen un /0
inicial o final, según su carácter endianness , y las cadenas deben terminar con /0/0
.
Muchos archivos incluso tienen bloques rellenados (de 4 kB o incluso de 64 kB) con /0
bytes, para tener un acceso rápido a los bloques deseados.
Para incluso más bytes nulos en un archivo, eche un vistazo a los archivos dispersos , donde todos los bytes son /0
por defecto, y los bloques llenos de bytes nulos ni siquiera se almacenan en el disco para ahorrar espacio.