No início de 2018, uma série de artigos apareceu em nosso blog dedicado à sexta verificação do código-fonte do projeto Chromium. O ciclo inclui 8 artigos dedicados a erros e recomendações para sua prevenção. Dois artigos causaram discussões acaloradas e, até agora, raramente recebi comentários sobre os tópicos abordados neles. Talvez, algumas explicações adicionais devam ser dadas e, como se costuma dizer, pontue o i.
Um ano se passou desde a escrita de uma série de artigos dedicados à próxima verificação dos códigos-fonte do projeto Chromium:
- Chromium: sexta verificação do projeto e 250 bugs
- Cromo bonito e memset desajeitado
- quebra e avanço
- Cromo: vazamentos de memória
- Erros de cromo
- Crómio: usando dados imprecisos
- Por que é importante verificar o que a função malloc retornou
- Crómio: outros erros
Os artigos sobre
memset e
malloc causaram e continuam a causar discussões que me parecem estranhas. Aparentemente, houve algum mal-entendido devido ao fato de eu não formular claramente meus pensamentos. Decidi voltar a esses artigos e fazer alguns esclarecimentos.
memset
Vamos começar com o artigo sobre
memset , porque tudo é simples aqui. Houve controvérsia sobre a melhor forma de inicializar estruturas. Muitos programadores escreveram que é melhor dar uma recomendação para não escrever:
HDHITTESTINFO hhti = {};
E assim:
HDHITTESTINFO hhti = { 0 };
Argumentos:
- A construção {0} é mais fácil de notar ao ler código do que {}.
- A construção {0} é mais intuitiva que {}. Ou seja, 0 sugere que a estrutura é preenchida com zeros.
Por conseguinte, sou oferecido para alterar este exemplo de inicialização no artigo. Não concordo com os argumentos e não pretendo alterar o artigo. Agora justifico minha posição.
Sobre visibilidade. Eu acho que isso é uma questão de gosto e hábito. Eu não acho que a presença de 0 dentro de chaves cresça fundamentalmente a situação.
Mas com o segundo argumento, eu não concordo. Um registro como {0} fornece um motivo para interpretar mal o código. Por exemplo, você pode calcular que, se você substituir 0 por 1, todos os campos serão inicializados em unidades. Portanto, esse estilo de escrita é mais prejudicial do que útil.
O analisador PVS-Studio ainda tem um diagnóstico relacionado
V1009 sobre esse tópico, cuja descrição vou agora dar.
V1009. Verifique a inicialização do array. Somente o primeiro elemento é inicializado explicitamente.O analisador detectou um possível erro devido ao fato de que, ao declarar uma matriz, o valor é especificado para apenas um elemento. Assim, o restante dos elementos será inicializado implicitamente em zero ou no construtor padrão.
Considere um exemplo de código suspeito:
int arr[3] = {1};
Talvez o programador esperasse que
arr consistisse em uma unidade, mas não é assim. A matriz será composta pelos valores 1, 0, 0.
O código correto é:
int arr[3] = {1, 1, 1};
Essa confusão pode ocorrer devido à semelhança com a construção
arr = {0} , que inicializa toda a matriz em zeros.
Se projetos semelhantes forem usados ativamente em seu projeto, você poderá desativar esse diagnóstico.
Também não é recomendável negligenciar a visibilidade do código.
Por exemplo, o código para codificar valores de cores é escrito da seguinte maneira:
int White[3] = { 0xff, 0xff, 0xff }; int Black[3] = { 0x00 }; int Green[3] = { 0x00, 0xff };
Graças à inicialização implícita, todas as cores são definidas corretamente, mas é melhor reescrever o código mais claramente:
int White[3] = { 0xff, 0xff, 0xff }; int Black[3] = { 0x00, 0x00, 0x00 }; int Green[3] = { 0x00, 0xff, 0x00 };
malloc
Antes de ler mais, peço que você atualize o conteúdo do artigo "
Por que é importante verificar o que a função malloc retornou ". Este artigo gerou muita discussão e crítica. Aqui estão algumas das discussões:
reddit.com/r/cpp ,
reddit.com/r/C_Programming ,
habr.com . Ocasionalmente, eles me escrevem sobre este artigo no correio agora.
O artigo é criticado pelos leitores nos seguintes pontos:
1. Se malloc retornou NULL , é melhor sair imediatamente do programa do que escrever um monte de ifs e tentar lidar de alguma forma com a falta de memória, pelo que geralmente é impossível executar o programa de qualquer maneira.Eu não liguei até o último para lidar com as consequências de ficar sem memória, lançando o erro cada vez mais alto. Se for permitido que um aplicativo seja desligado sem aviso, que assim seja. Para fazer isso, é necessária apenas uma verificação imediatamente após o
malloc ou o uso do
xmalloc (consulte o próximo parágrafo).
Fiz objeções e adverti sobre a ausência de verificações, por causa das quais o programa continua funcionando "como se nada tivesse acontecido". Isto é completamente diferente. Isso é perigoso porque leva a um comportamento indefinido, corrupção de dados e assim por diante.
2. Não falamos sobre a solução, que consiste em escrever wrappers de funções para alocar memória com verificação subseqüente ou usar funções existentes, como xmalloc .Eu concordo, eu perdi esse momento. Ao escrever um artigo, simplesmente não pensei em como consertar a situação. Era mais importante para mim transmitir ao leitor qual é o perigo de uma falta de verificação. Como corrigir um erro já é uma questão de gosto e detalhes de implementação.
A função
xmalloc não faz parte da biblioteca padrão C (consulte "
Qual é a diferença entre xmalloc e malloc? "). No entanto, essa função pode ser declarada em outras bibliotecas, por exemplo, na biblioteca GNU utils (
GNU libiberty ).
A essência da função é que o programa termina se a alocação de memória falhar. Uma implementação desta função pode ser assim:
void* xmalloc(size_t s) { void* p = malloc(s); if (!p) { fprintf (stderr, "fatal: out of memory (xmalloc(%zu)).\n", s); exit(EXIT_FAILURE); } return p; }
Assim, chamando a função
xmalloc em qualquer lugar,
em vez de
malloc, você pode ter certeza de que o programa não terá um comportamento indefinido devido a qualquer uso do ponteiro nulo.
Infelizmente, o
xmalloc também não é uma panacéia para todos os males. Deve-se lembrar que o uso do
xmalloc é inaceitável quando se trata de escrever código de biblioteca. Eu vou falar sobre isso um pouco mais tarde.
3. A maioria dos comentários foi a seguinte: "na prática, malloc nunca retorna NULL ".Felizmente, eu não entendo sozinho que essa é a abordagem errada. Eu realmente gostei deste
comentário no meu suporte:
A partir da experiência de discutir esse tópico, sentimos que existem duas seitas na Internet. Os que aderiram ao primeiro estão firmemente convencidos de que, no Linux, o malloc nunca retorna NULL. Os que aderem ao segundo estão firmemente convencidos de que, se a memória do programa não puder ser alocada, mas nada puder ser feito em princípio, você só precisará cair. Você não pode convencê-los de nenhuma maneira. Especialmente quando essas duas seitas se cruzam. Você só pode tomar isso como garantido. E não importa em que recurso de perfil está a discussão.Pensei e decidi seguir o conselho e não tentarei persuadir :). Vamos torcer para que essas equipes de desenvolvimento escrevam apenas programas não críticos. Se, por exemplo, alguns dados se deteriorarem no jogo ou o jogo trava, não é grande coisa.
A única coisa importante é que os desenvolvedores de bibliotecas, bancos de dados etc. não fazem isso.
Uma chamada para a biblioteca e desenvolvedores de código responsáveis
Se você estiver desenvolvendo uma biblioteca ou outro código responsável, sempre verifique o valor do ponteiro retornado pela função
malloc / realloc e retorne o código de erro para fora se a memória não puder ser alocada.
Nas bibliotecas, você não pode chamar a função de
saída se não conseguir alocar memória. Pelo mesmo motivo, você não pode usar
xmalloc . Para muitos aplicativos, é inaceitável simplesmente levá-los e travá-los. Por esse motivo, por exemplo, o banco de dados pode estar corrompido. Os dados contados por muitas horas podem ser perdidos. Por esse motivo, o programa pode estar vulnerável a vulnerabilidades de negação de serviço, quando, em vez de manipular corretamente a carga crescente, o aplicativo multithread simplesmente termina.
Não se pode assumir como e em quais projetos a biblioteca será usada. Portanto, deve-se assumir que o aplicativo pode resolver tarefas muito importantes. E apenas "matá-lo" chamando de
saída não é bom. Provavelmente, esse programa foi escrito levando em consideração a possibilidade de ficar sem memória e pode fazer algo nesse caso. Por exemplo, algum sistema CAD, devido à forte fragmentação da memória, não pode alocar um buffer de memória suficiente para a próxima operação. Mas essa não é uma razão para ela terminar no modo de emergência com perda de dados. Um programa pode tornar possível salvar um projeto e reiniciar-se no modo normal.
Em nenhum caso você pode confiar no fato de que o
malloc sempre pode alocar memória. Não se sabe em qual plataforma e como a biblioteca será usada. Se em uma plataforma a falta de memória é exótica, em outra, pode ser uma situação muito comum.
Não se pode esperar que, se
malloc retornar
NULL , o programa falhará. Tudo pode acontecer. Como descrevi no
artigo , um programa pode gravar dados em um endereço diferente de zero. Como resultado, alguns dados podem estar corrompidos, o que leva a consequências imprevisíveis. Até o
memset é perigoso. Se os dados forem preenchidos na ordem inversa, primeiro alguns dados serão corrompidos e somente então o programa falhará. Mas o outono pode acontecer tarde demais. Se dados corrompidos forem usados em threads paralelos no momento da função
memset , as consequências poderão ser fatais. Você pode obter uma transação danificada no banco de dados ou enviar um comando para excluir arquivos "desnecessários". Tudo pode acontecer a tempo. Sugiro que o leitor imagine independentemente o que o uso do lixo na memória pode levar.
Portanto, a biblioteca possui apenas uma opção correta para trabalhar com funções
malloc . Você precisa verificar IMEDIATAMENTE se a função retornou e, se for NULL, retorne o status do erro.
Sitelinks
- Manuseio de OOM .
- Diversão com ponteiros NULL: parte 1 , parte 2 .
- O que todo programador C deve saber sobre comportamento indefinido: parte 1 , parte 2 , parte 3 .

Se você deseja compartilhar este artigo com um público que fala inglês, use o link para a tradução: Andrey Karpov.
Sexta verificação de cromo, posfácio .