Is char Nice ? : Código problemático com o tipo char em C que não incrementa?

12 min

language: ja bn en es hi pt ru zh-cn zh-tw

Olá, sou um incompetente.

Fiquei curioso e tentei.

https://blog.httrack.com/blog/2014/05/30/c-corner-cases-and-funny-things

Primeiro, vou compilar e executar no estado acima.

#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;
}

Vou compilar e executar isso.

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$ 

Aaaaaah!!!

É perigoso, mas vou fazer com que o log seja gravado em um arquivo chamado log e ver a execução inicial.
Apesar de ter feito ps -ef | grep bugcode rapidamente para verificar o ID do processo e matá-lo...

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

O tamanho do arquivo ficou enorme, cerca de 1.2GB... É um código tão perigoso que pode até travar o sistema... Não consegui verificar com view, mas consegui abrir com 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

Não foi atualizado (incrementado) desde o início.

Gostaria de entender o que isso significa com a ajuda do ChatGPT.

Diferença entre EOF e \0
EOF (End Of File) é um valor especial que indica o fim de um arquivo, geralmente -1. Por outro lado, '\0' é o caractere nulo que indica o fim de uma string em C, e seu valor é 0.

Parece que entendi aqui, mas minha cabeça está confusa.
Para me acalmar, vou perguntar sobre o fluxo que ocorre com este código neste ponto. Não se preocupe que mudou para my_read_char_with_cast.

Detalhes do fluxo
Estado inicial:

offs é 0.
O buffer não tem terminação nula (não há '\0').
Início do loop:

A função my_read_char_with_cast é chamada.
buffer[*offs] é 'H' (o primeiro caractere), então não é '\0'.
'H' é retornado, e offs se torna 1.
Continuação do loop:

Na próxima chamada, buffer[1] ('e') é lido e retornado.
offs se torna 2.
Repetição:

Este processo continua.
Eventualmente, offs se torna 11, buffer[11] ('!') é lido e retornado.
offs se torna 12.
Loop infinito:

O valor de buffer[12] é indefinido (refere-se a uma área de memória fora do buffer) e não é '\0'.
Como buffer[*offs] nunca é '\0', um loop infinito ocorre sem atingir EOF.

De alguma forma, fiquei ainda mais confuso. O resultado do teste real é um loop infinito desde o início, então offs nem sequer chegou a 12. Ao 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 esperado, offs permanece 0.

Agora, vamos voltar ao blog da HTTrack.

Esta função retorna um valor negativo para ASCII > 127. Em particular, para ASCII 255 (0xFF), retorna o valor -1. Este também é o valor de EOF.

Entendi? Mas por que ele retorna -1? Vou perguntar à IA.

Especificidade do tipo char
Inteiro de 8 bits com sinal: O tipo char na linguagem C é geralmente tratado como um inteiro de 8 bits com sinal. Isso pode representar um intervalo de -128 a 127.
Wrap-around: Quando o valor de um tipo char atinge o valor máximo (127), a próxima operação de incremento faz um "wrap-around" para o valor mínimo (-128). Isso se deve à representação em complemento de dois, onde incrementar 127 resulta em -128.

Considerações sobre ASCII 255 (0xFF)
Fora do intervalo ASCII: Na tabela ASCII, o valor 255 (0xFF) está fora do intervalo e é geralmente tratado como um caractere de controle.
Relação com EOF: Em C, EOF é um valor especial que indica o fim de um arquivo, geralmente tratado como -1. EOF é usado para indicar o fim de um arquivo, mas aqui é usado como o valor de retorno para o valor ASCII 255 (0xFF).
Significado do comportamento da função
A razão pela qual esta função retorna -1 para ASCII 255 (0xFF) é a seguinte:

Valor fora do intervalo: ASCII 255 está fora do intervalo do tipo char e é geralmente tratado como um valor inválido.
Uso de EOF: Nesta função, EOF (-1) é usado para indicar o fim do buffer. O fato de o valor de retorno para ASCII 255 ser -1 é uma espécie de convenção implícita que indica EOF.
Tal design permite que a função detecte o fim do buffer e retorne EOF, funcionando como um sinal para o chamador parar de ler dados. No entanto, no contexto desta função, é preciso ter cuidado se for necessário um tratamento específico para ASCII 255 ou se você quiser lidar com ASCII 255 corretamente.

Entendi, então o tipo char não consegue lidar com isso. Basicamente, a diferença entre ASCII 127 e ASCII 255 faz com que EOF seja retornado como -1, resultando em um loop infinito.

O código corrigido no site da HTTrack é o seguinte. No entanto, mesmo com este, compilei com gcc e ocorreu um loop infinito, então parece que este também não funciona.

/** 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 (unsigned char) buffer[*offs++];
  } else {
    return EOF;
  }

A IA me apresentou isso.

int my_read_char(const char *buffer, size_t *offs) {
  if (buffer[*offs] != '\0') {
    return buffer[(*offs)++];
  } else {
    return EOF;
  }
}

No final das contas, mesmo que você faça um cast, parece que é preciso incrementar explicitamente como um tipo char.

Perguntei novamente sobre a diferença entre eles.

Detalhes das diferenças
1. Função que inclui um cast para unsigned char
Razão para o cast:
buffer é um array de char, e buffer[*offs] retorna um valor do tipo char.
O tipo char é frequentemente um inteiro de 8 bits com sinal, mas isso depende da implementação (pode ser sem sinal em alguns compiladores ou plataformas).
Se um valor na string for negativo, e não houver cast, o sinal pode ser preservado quando o valor negativo é estendido para o tipo int.
Por exemplo, se o valor de buffer[*offs] for -1, sem cast, -1 é retornado; mas com cast, 255 (0xFF) é retornado.
2. Função sem cast
Tratamento de caracteres com sinal:
Se buffer[*offs] puder ter um valor negativo, sem cast, o valor negativo é retornado como está.
Isso pode, em alguns casos, levar a um comportamento inesperado.

Então, qual é o resultado?

Qual usar?
Função com cast:

É comum fazer um cast para unsigned char ao tratar dados de caracteres como binários ou quando se deseja tratá-los como valores sem sinal.
Isso é especialmente importante ao lidar com caracteres não-ASCII (por exemplo, dados com codificação UTF-8).
Função sem cast:

Se você estiver lidando apenas com caracteres ASCII ou se a intenção for tratá-los como valores com sinal, pode não haver problema em não fazer o cast.

Então, este é um caso bastante especial, não é?
No segundo caso, onde não há cast, ao especificar explicitamente buffer[(*offs)++] para tratá-lo como um tipo char, isso significa que o char é tratado como sem sinal por padrão, e assim incrementa corretamente como um tipo char?

As diferenças de codificação de caracteres são difíceis de visualizar, e há muitas armadilhas comuns, não é?... Ah, foi interessante!

Related Posts