Estou aprendendo C e rapidamente encontrei meu primeiro problema neste tutorial . Aqui está o código:
#include <stdio.h>
#include <stdbool.h>
static char buffer[10];
int main(int argc, char **argv) {
puts("Lispy VERSION 0.0.0.0.1");
puts("Press Ctrl+c to exit\n");
while (true) {
fputs("Lispy> ", stdout);
fgets(buffer, 2048, stdin);
printf("No you are a %s", buffer);
}
return 0;
}
Neste código, eu queria testar o que acontece se o segundo parâmetro max_count
da fgets
função exceder o tamanho do buffer
. Para minha surpresa, o programa rodou perfeitamente, sem erros, e o compilador não me avisou sobre possíveis problemas.
Usei o recurso de depuração no meu editor e descobri que se eu inserir uma string como HELLLOOOOOOOOOOOOOOOOOOOOOOO
, a situação da memória fica assim:
Como mostrado na captura de tela acima, parece que toda a string de entrada está armazenada corretamente na memória! Também parece que o tamanho de buffer
não é exatamente o que eu especifiquei (10 bytes). Se fossem 10 bytes, a linha printf("No you are a %s", buffer);
deveria imprimir apenas 10 bytes, mas imprimiu a string inteira "HELLLOOOOOOOOOOOOOOOOOOOOOOOOO".
Isso significa que eu realmente acessei memória além do tamanho alocado do buffer
?
Em geral, a linguagem C é um mecanismo para fazer o que o programador diz e não para fornecer verificações de que está sendo feito corretamente.
Um motivo para isso é que, quando C estava sendo desenvolvido, as linguagens de programação eram relativamente novas, e cada recurso implementado exigia uma quantidade considerável de trabalho. Alguém precisava ter as ideias, planejar a solução, implementar algoritmos e escrever o código. Portanto, pouco trabalho era feito para garantir que os programas não fossem escritos além dos limites dos arrays. Esse era o trabalho do programador.
Outro motivo é que uma característica desejável de C é sua rapidez: ela praticamente não realiza nenhum trabalho além do absolutamente necessário para o funcionamento de um programa. Adicionar verificações em tempo de execução para coisas que um programador deveria ter feito ao escrever o programa é um desperdício, pois executa muitas vezes o que deveria ter sido feito apenas uma vez.
Portanto, o padrão C define o que acontece quando um programa usa
fgets
métodos que não transbordam o buffer passado a ele. Ele não define o que acontece quando um programa transborda o buffer. Na terminologia do padrão C, esse é um comportamento indefinido: o padrão não especifica nada sobre o que deve acontecer.Quando
fgets
usado como você demonstrou, a consequência mais comum é que ele escreve além do array. O que acontece então depende do programa. Escrever além do array sobrescreve outros dados no programa. Em um programa simples como o que você tentou, com pouco conteúdo, nada mais era imediatamente afetado. Em um programa com mais componentes, escrever além do array pode ter interrompido outras coisas, e o programa travará ou apresentará mau funcionamento.Nada indica
printf
o tamanhobuffer
. Quandoprintf
vê%s
, ele detecta o fim da string procurando por um byte nulo. Como vocêfgets
escreveu uma string que excede o tamanho do array,printf
seguiu os bytes, imprimindo cada um até encontrar um byte nulo. Havia um byte nulo após a string que você inseriu, poisfgets
coloca um byte nulo após os caracteres que lê.Esta é uma simples manifestação de comportamento indefinido. Você deve estar ciente de que os compiladores se tornaram sofisticados e, quando a otimização é ativada, eles podem transformar programas de maneiras que podem ser surpreendentes a princípio, portanto, o comportamento indefinido pode se manifestar de maneiras surpreendentes.
Alguns compiladores detectam esse problema específico . No entanto, não é possível que os compiladores detectem todos esses erros. Muitas vezes, o erro
fgets
não seria executado diretamente no código onde a declaração do buffer é visível. Frequentemente, o buffer seria passado para alguma outra subrotina, e essa subrotina chamariafgets
. No código visível ao compilador, não seria aparente que o comprimento usado era maior que o comprimento do buffer.