Em C, por que algumas pessoas lançam o ponteiro antes de liberá-lo?


167

Estou trabalhando em uma base de código antiga e praticamente toda chamada de free () usa uma conversão em seu argumento. Por exemplo,

free((float *)velocity);
free((float *)acceleration);
free((char *)label);

onde cada ponteiro é do tipo correspondente (e correspondente). Não vejo sentido em fazer isso. É um código muito antigo, então fico me perguntando se é uma coisa de K&R. Nesse caso, desejo realmente oferecer suporte aos compiladores antigos que possam ter exigido isso, então não quero removê-los.

Existe uma razão técnica para usar esses modelos? Não vejo nem uma razão pragmática para usá-los. Qual é o sentido de nos lembrar do tipo de dados antes de liberá-lo?

Edição: Esta pergunta não é uma duplicata da outra pergunta. A outra pergunta é um caso especial desta pergunta, que eu acho óbvio se os eleitores próximos leiam todas as respostas.

Colophon: Estou dando a "resposta constante" a marca de seleção porque é uma verdadeira razão genuína pela qual isso pode precisar ser feito; no entanto, a resposta sobre ser um costume pré-ANSI C (pelo menos entre alguns programadores) parece ser o motivo pelo qual foi usada no meu caso. Muitos pontos positivos por muitas pessoas aqui. Obrigado por suas contribuições.


13
"Qual é o sentido de nos lembrar do tipo de dados antes de liberá-lo?" Talvez para saber quanta memória será liberada?
M0skit0

12
@Codor O compilador não faz a desalocação, o sistema operacional faz.
M0skit0

20
@ m0skit0 "Talvez para saber quanta memória será liberada?" Tipo não é necessário para saber quanto liberar. Transmitir apenas por esse motivo é uma codificação incorreta.
user694733

9
@ m0skit0 A transmissão para facilitar a leitura é sempre uma codificação ruim, porque a transmissão altera a maneira como os tipos são interpretados e pode ocultar erros sérios. Quando a legibilidade é necessária, os comentários são melhores.
user694733

66
Nos tempos antigos, quando os dinossauros percorriam a Terra e escreviam livros de programação, acredito que não existia void*no C pré-padrão, mas apenas char*. Portanto, se suas descobertas arqueológicas revelarem código que lança o parâmetro para free (), acredito que deve ser daquele período ou ser escrito por uma criatura daquela época. Não consigo encontrar nenhuma fonte para isso, então evitarei responder.
Lundin

Respostas:


171

A conversão pode ser necessária para resolver os avisos do compilador, se os ponteiros estiverem const. Aqui está um exemplo de código que causa um aviso sem lançar o argumento free:

const float* velocity = malloc(2*sizeof(float));
free(velocity);

E o compilador (gcc 4.8.3) diz:

main.c: In function main’:
main.c:9:5: warning: passing argument 1 of free discards const qualifier from pointer target type [enabled by default]
     free(velocity);
     ^
In file included from main.c:2:0:
/usr/include/stdlib.h:482:13: note: expected void *’ but argument is of type const float *’
 extern void free (void *__ptr) __THROW;

Se você usar free((float*) velocity);o compilador para de reclamar.


2
@ m0skit0 que não explica por que alguém lançaria float*antes da liberação. Eu tentei free((void *)velocity);com o gcc 4.8.3. Claro que não iria funcionar com um compilador antigo
Manos Nikolaidis

54
Mas por que você precisaria alocar dinamicamente a memória constante? Você nunca poderia usá-lo!
N15_M

33
@Nils_M, é um exemplo simplificado de argumentação. O que eu fiz no código real em uma função é alocar memória não-const, atribuir valores, converter em um ponteiro const e retorná-lo. Agora, há um ponteiro para a memória const pré-atribuída que alguém precisa liberar.
Manos Nikolaidis

2
Exemplo : “Essas sub-rotinas retornam a string na memória recém-alocada, apontada por * stringValueP, que você deve liberar eventualmente. Às vezes, a função do sistema operacional usada para liberar memória é declarada para levar um ponteiro para algo não constante como argumento, portanto, porque * stringValueP é um ponteiro para uma const. ”
Carsten S

3
Errôneo, se uma função usa const char *pcomo argumento e depois a libera, a coisa certa a fazer não é converter ppara char*antes de chamar de graça. É para não declarar que está recebendo const char *pem primeiro lugar, pois ele modifica *p e deve ser declarado da mesma forma. (E se ele leva um ponteiro const em vez de ponteiro para const, int *const p, você não precisa de elenco desde que é realmente legal e, portanto, funciona bem sem o elenco.)
Ray

59

O C pré-padrão não tinha void*mas apenas char*, então você tinha que converter todos os parâmetros passados. Se você encontrar o código C antigo, poderá encontrar esses elencos.

Pergunta semelhante com referências .

Quando o primeiro padrão C foi lançado, os protótipos para malloc e free mudaram de ter char*para o void*que eles ainda têm hoje.

E, é claro, no padrão C, esses modelos são supérfluos e prejudicam a legibilidade.


23
Mas por que você lançaria o argumento freepara o mesmo tipo que já é?
Jwodder

4
@chux O problema com o pré-padrão é apenas isso: não há obrigações para nada. As pessoas apenas apontaram o livro da K&R para o canon porque essa era a única coisa que eles tinham. E, como podemos ver em vários exemplos da K&R 2ª edição, a própria K&R está confusa sobre como as projeções do parâmetro freefuncionam no padrão C (você não precisa converter). Eu não li a 1ª edição, então não sei dizer se eles também estavam confusos nos tempos pré-padrão dos anos 80.
Lundin

7
O C pré-padrão não tinha void*, mas também não tinha protótipos de função, portanto, lançar o argumento de freeainda era desnecessário mesmo em K&R (supondo que todos os tipos de ponteiros de dados usassem a mesma representação).
Ian Abbott

6
Por várias razões já mencionadas nos comentários, não acho que essa resposta faça sentido.
R .. GitHub Pare de ajudar o gelo

4
Não vejo como essa resposta realmente responderia a algo relevante. A pergunta original envolve lançamentos para outros tipos, não apenas para char *. Que sentido faria em compiladores antigos sem void? O que esses elencos atingiriam?
AnT

34

Aqui está um exemplo em que o free falharia sem um elenco:

volatile int* p = (volatile int*)malloc(5 * sizeof(int));
free(p);        // fail: warning C4090: 'function' : different 'volatile' qualifiers
free((int*)p);  // success :)
free((void*)p); // success :)

Em C, você pode receber um aviso (recebi um no VS2012). Em C ++, você receberá um erro.

Casos raros à parte, o elenco apenas incha o código ...

Edit: Eu lancei para void*não int*demonstrar a falha. Funcionará da mesma forma int*que será convertido void*implicitamente. int*Código adicionado .


Observe que no código postado na pergunta, as conversões não são para void *, mas para float *e char *. Esses elencos não são apenas estranhos, eles estão errados.
Andrew Henle

1
A questão é realmente sobre o oposto.
M0skit0

1
Eu não entendo a resposta; em que sentido free(p)falharia? Isso daria um erro do compilador?
Codor 01/12/19

1
Estes são bons pontos. O mesmo vale para constponteiros qualificadores, obviamente.
Lundin

2
volatileexiste desde que C foi padronizado, se não mais. Foi não acrescentou em C99.
R .. GitHub Pare de ajudar o gelo

30

Motivo antigo: 1. Ao usar free((sometype*) ptr), o código é explícito sobre o tipo que o ponteiro deve ser considerado como parte da free()chamada. A conversão explícita é útil quando free()é substituída por um (faça você mesmo) DIY_free().

#define free(ptr) DIY_free(ptr, sizeof (*ptr))

A DIY_free()era (é) uma maneira, especialmente no modo de depuração, de fazer uma análise em tempo de execução do ponteiro sendo liberado. Isso geralmente é associado a um DIY_malloc()para adicionar sentenciais, contagens globais de uso de memória, etc. Meu grupo usou essa técnica por anos antes que ferramentas mais modernas fossem exibidas. Obrigava que o item que estava sendo liberado fosse lançado para o tipo que foi originalmente alocado.

  1. Dadas as muitas horas gastas rastreando problemas de memória etc., pequenos truques como liberar o tipo free'd ajudariam a pesquisar e restringir a depuração.

Moderno: Evitar conste volatileavisos, conforme abordado por Manos Nikolaidis @ e @egur . Pensamento gostaria de observar os efeitos dos 3 eliminatórias : const, volatile, e restrict.

Adicionado char * restrict *rp2por @R .. comentário

void free_test(const char *cp, volatile char *vp, char * restrict rp, 
    char * restrict *rp2) {
  free(cp);  // warning
  free(vp);  // warning
  free(rp);  // OK
  free(rp2);  // warning
}

int main(void) {
  free_test(0,0,0,0);
  return 0;
}

3
restrictnão é um problema devido ao local onde está colocado - afeta o objeto e rpnão o tipo apontado. Se você tivesse char *restrict *rp, então isso importaria.
R .. GitHub Pare de ajudar o gelo

16

Aqui está outra hipótese alternativa.

Fomos informados de que o programa foi escrito antes da C89, o que significa que não pode haver algum tipo de incompatibilidade com o protótipo de free, porque não apenas não havia tal coisa constnem void *antes da C89, não havia tal coisa como um protótipo de função anterior a C89. stdlib.hem si foi uma invenção do comitê. Se os cabeçalhos do sistema se preocupassem em declarar free, eles teriam feito assim:

extern free();  /* no `void` return type either! */

Agora, o ponto principal aqui é que a ausência de protótipos de função significava que o compilador não fazia verificação de tipo de argumento . Ele aplicou as promoções de argumentos padrão (as mesmas que ainda se aplicam a chamadas de funções variadas) e foi isso. A responsabilidade de alinhar os argumentos em cada local da chamada com as expectativas do receptor cabe inteiramente ao programador.

No entanto, isso ainda não significa que era necessário lançar o argumento freena maioria dos compiladores K&R. Uma função como

free_stuff(a, b, c)
    float *a;
    char *b;
    int *c;
{
    free(a);
    free(b);
    free(c);
}

deveria ter sido compilado corretamente. Então, acho que o que temos aqui é um programa escrito para lidar com um compilador de buggy para um ambiente incomum: por exemplo, um ambiente em que sizeof(float *) > sizeof(int)o compilador não usaria a convenção de chamada apropriada para ponteiros, a menos que você os projete no momento da chamada.

Não conheço esse ambiente, mas isso não significa que não exista. Os candidatos mais prováveis ​​que vêm à mente são os compiladores "minúsculos C" reduzidos para micros de 8 e 16 bits no início dos anos 80. Também não ficaria surpreso ao saber que os primeiros Crays tinham problemas como esse.


1
Concordo plenamente com a primeira metade. E a segunda metade é uma conjectura intrigante e plausível.
chux - Restabelece Monica

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.