Is char Nice ? : ¿Código que no se incrementa con un tipo char problemático en C?
Hola, soy incompetente.
Tenía curiosidad, así que lo probé.
https://blog.httrack.com/blog/2014/05/30/c-corner-cases-and-funny-things
Primero, intentemos compilar y ejecutarlo en el estado anterior.
#include <stdio.h>
/** Return the next character within a \0-terminated string, or EOF. **/
int my_read_char(const char *buffer, size_t *offs) {
if (buffer[*offs] != '\0') {
return buffer[*offs++]; /* here's the trap */
} else {
return EOF;
}
int main() {
const char *buffer = "Hello, World!";
size_t offs = 0;
printf("Reading characters from buffer:\n");
while (1) {
int ch = my_read_char(buffer, &offs);
if (ch == EOF) {
break;
}
printf("Character read: %c, offs: %zu\n", ch, offs);
}
return 0;
}
Ahora, intentemos compilar y ejecutar esto.
gcc -o bugcode bugcode.c
./bugcode
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Charact^C
alleycat:[haturatu]:~/clang$
¡¡¡Aaaah!!!
Así que es peligroso, pero voy a hacer que imprima los logs en un archivo llamado 'log' y ver la ejecución inicial.
De hecho, a pesar de que rápidamente hice ps -ef | grep bugcode para verificar el ID del proceso y matarlo...
alleycat:[haturatu]:~/clang$ ls -la
合計 1256824
drwxr-xr-x 2 haturatu haturatu 4096 6月 14 23:38 .
drwx------ 39 haturatu haturatu 4096 6月 14 23:38 ..
~
-rw-r--r-- 1 haturatu haturatu 1286922240 6月 14 23:39 log
El tamaño del archivo se volvió enorme, aproximadamente 1.2 GB... ¡Es un código tan peligroso que podría colapsar el sistema...! No pude verificarlo con view, pero pude abrirlo con vim.
alleycat:[haturatu]:~/clang$ head log
Reading characters from buffer:
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
No se está actualizando (incrementando) desde el principio.
Quiero entender qué significa esto con la ayuda de ChatGPT.
Diferencia entre EOF y \0
EOF (End Of File) es un valor especial que indica el final de un archivo, y normalmente es -1. Por otro lado, '\0' es el carácter nulo que indica el final de una cadena en C, y su valor es 0.
Parecía que lo entendía aquí, pero ahora mi cabeza se está confundiendo.
Para calmarme, preguntaré sobre el flujo que ocurre con este código en este punto. Por favor, ignoren que ha cambiado a my_read_char_with_cast.
Detalles del flujo
Estado inicial:
offs es 0.
El búfer no tiene terminación nula (no hay '\0').
Inicio del bucle:
Se llama a la función my_read_char_with_cast.
buffer[*offs] es 'H' (el primer carácter), por lo que no es '\0'.
Se devuelve 'H', y offs se convierte en 1.
Continuación del bucle:
En la siguiente llamada, se lee buffer[1] ('e') y se devuelve.
offs se convierte en 2.
Repetición:
Este proceso continúa.
Finalmente, offs se convierte en 11, se lee buffer[11] ('!') y se devuelve.
offs se convierte en 12.
Bucle infinito:
El valor de buffer[12] es indefinido (se refiere a un área de memoria fuera del búfer) y no es '\0'.
Dado que buffer[*offs] nunca es '\0', se produce un bucle infinito sin alcanzar EOF.
De alguna manera, ahora estoy aún más confundido. Los resultados de la prueba real muestran un bucle infinito desde el principio, por lo que offs ni siquiera llega a 12. Al depurar:
Returning character: H
Character read: H, offs: 0
my_read_char called with offs: 0
Returning character: H
Character read: H, offs: 0
my_read_char called with offs: 0
Returning character: H
Character read: H, o^C
alleycat:[haturatu]:~/clang$
Como era de esperar, offs permanece en 0.
Ahora, volvamos al blog de HTTrack.
Esta función devuelve un valor negativo para ASCII > 127. En particular, para ASCII 255 (0xFF), devuelve el valor -1. Este es también el valor de EOF.
¿Entiendo? Entonces, ¿por qué devuelve -1? Le preguntaré a la IA.
Particularidad del tipo char
Entero con signo de 8 bits: El tipo char en C se trata normalmente como un entero con signo de 8 bits. Esto puede representar un rango de -128 a 127.
Desbordamiento (Wrap-around): Cuando el valor de un tipo char alcanza su valor máximo (127), la siguiente operación de incremento lo "envuelve" al valor mínimo (-128). Esto se debe a la representación en complemento a dos, donde incrementar 127 resulta en -128.
Consideraciones sobre ASCII 255 (0xFF)
Fuera del rango ASCII: En la tabla ASCII, el valor 255 (0xFF) está fuera de rango y normalmente se trata como un carácter de control.
Relación con EOF: En C, EOF es un valor especial que indica el fin de un archivo, y normalmente se trata como -1. EOF se usa para indicar el final de un archivo, pero aquí se usa como valor de retorno para el valor ASCII 255 (0xFF).
Significado del comportamiento de la función
La razón por la que esta función devuelve -1 para ASCII 255 (0xFF) es la siguiente:
Valor fuera de rango: ASCII 255 está fuera del rango del tipo char y normalmente se trata como un valor inválido.
Uso de EOF: En esta función, EOF (-1) se utiliza para indicar el final del búfer. El hecho de que el valor de retorno para ASCII 255 sea -1 se convierte en una especie de convención implícita para indicar EOF.
Este diseño permite que la función detecte el final del búfer y devuelva EOF, actuando como una señal para que el llamador detenga la lectura de datos. Sin embargo, en el contexto de esta función, se debe tener precaución si se requiere un manejo específico para ASCII 255 o si se desea tratar ASCII 255 correctamente.
Ya veo, entonces el tipo char no puede manejarlo en primer lugar. Se origina en la diferencia entre ASCII 127 y ASCII 255, y debido a eso, EOF se devuelve como -1, lo que lleva a un bucle infinito.
El código corregido del sitio de HTTrack es el siguiente. Sin embargo, cuando lo compilé con gcc, todavía ocurrió un bucle infinito, así que esto tampoco parece funcionar.
/** Devuelve el siguiente carácter dentro de una cadena terminada en \0, o EOF. **/
int my_read_char(const char *buffer, size_t *offs) {
if (buffer[*offs] != '\0') {
return (unsigned char) buffer[*offs++];
} else {
return EOF;
}
La IA me presentó esto:
int my_read_char(const char *buffer, size_t *offs) {
if (buffer[*offs] != '\0') {
return buffer[(*offs)++];
} else {
return EOF;
}
}
Al final, incluso si se hace un cast, parece que es necesario incrementarlo explícitamente como un tipo char.
Volví a preguntar sobre la diferencia.
Detalles de la diferencia
1. Función que incluye un cast a unsigned char
Razón del cast:
buffer es un array de char, y buffer[*offs] devuelve un valor de tipo char.
El tipo char suele ser un entero con signo de 8 bits, pero esto depende de la implementación (puede ser sin signo en algunos compiladores o plataformas).
Si un valor en la cadena es negativo, sin el cast, el signo podría conservarse cuando el valor negativo se extiende a un tipo int.
Por ejemplo, si el valor de buffer[*offs] es -1, sin el cast se devuelve -1, pero con el cast se devuelve 255 (0xFF).
2. Función sin cast
Manejo de caracteres con signo:
Si buffer[*offs] puede tener un valor negativo, sin el cast, el valor negativo se devuelve tal cual.
Esto puede causar un comportamiento inesperado en algunos casos.
Entonces, ¿cuál es la conclusión?
¿Cuál debería usarse?
Función que incluye el cast:
Es común hacer un cast a unsigned char cuando se tratan datos de caracteres como binarios o cuando se desea tratarlos como valores sin signo.
Esto es especialmente importante cuando se manejan caracteres no ASCII (por ejemplo, datos con codificación UTF-8).
Función sin cast:
Si solo se manejan caracteres ASCII o si se pretende tratarlos con signo, puede que no haya problema sin el cast.
Este es un caso bastante especial, ¿verdad?
En el segundo caso, donde no hay cast, al especificar explícitamente **buffer[(*offs)++]** para tratarlo como un tipo char, ¿significa que se tratará implícitamente como sin signo por defecto para char y, por lo tanto, se incrementará correctamente como un tipo char?
Las diferencias en la codificación de caracteres son a menudo difíciles de visualizar, y hay muchas trampas comunes, ¿no es así? ¡Fue realmente interesante!