En C, pourquoi certaines personnes lancent-elles le pointeur avant de le libérer?

167

Je travaille sur une ancienne base de code et à peu près chaque invocation de free () utilise un cast sur son argument. Par exemple,

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

où chaque pointeur est du type correspondant (et correspondant). Je ne vois aucun intérêt à faire cela. C'est un code très ancien, donc je me demande si c'est un truc K&R. Si tel est le cas, je souhaite en fait prendre en charge les anciens compilateurs qui peuvent avoir nécessité cela, donc je ne veux pas les supprimer.

Y a-t-il une raison technique d'utiliser ces moulages? Je ne vois même pas beaucoup de raison pragmatique de les utiliser. À quoi bon se rappeler le type de données juste avant de le libérer?

EDIT: Cette question n'est pas une duplication de l'autre question. L'autre question est un cas particulier de cette question, qui, je pense, est évidente si les électeurs proches liraient toutes les réponses.

Colophon: Je coche la "réponse const" parce que c'est une vraie raison pour laquelle cela pourrait devoir être fait; cependant, la réponse à ce sujet étant une coutume pré-ANSI C (du moins chez certains programmeurs) semble être la raison pour laquelle il a été utilisé dans mon cas. Beaucoup de bons points par beaucoup de gens ici. Merci pour vos contributions.

Dr Personne Personne II
la source
13
"A quoi bon se rappeler le type de données juste avant de le libérer?" Peut-être pour savoir combien de mémoire sera libérée?
m0skit0
12
@Codor Le compilateur ne fait pas la désallocation, le système d'exploitation le fait.
m0skit0
20
@ m0skit0 "Peut-être pour savoir combien de mémoire sera libérée?" Le type n'est pas nécessaire pour savoir combien libérer. Cast pour cette raison seulement est un mauvais codage.
user694733
9
@ m0skit0 La conversion par souci de lisibilité est toujours un mauvais codage, car la conversion change la façon dont les types sont interprétés et peut masquer de graves erreurs. Lorsque la lisibilité est nécessaire, les commentaires sont meilleurs.
user694733
66
Dans les temps anciens, lorsque les dinosaures parcouraient la terre et écrivaient des livres de programmation, je crois qu'il n'y avait pas void*de C pré-standard, mais seulement char*. Donc, si vos découvertes archéologiques révèlent un code convertissant le paramètre en free (), je pense qu'il doit être soit de cette période, soit écrit par une créature de cette époque. Cependant, je ne trouve aucune source à ce sujet, alors je m'abstiendrai de répondre.
Lundin

Réponses:

171

Le cast peut être nécessaire pour résoudre les avertissements du compilateur si les pointeurs le sont const. Voici un exemple de code qui provoque un avertissement sans lancer l'argument de free:

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

Et le compilateur (gcc 4.8.3) dit:

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;

Si vous utilisez free((float*) velocity);le compilateur cesse de vous plaindre.

Manos Nikolaidis
la source
2
@ m0skit0 qui n'explique pas pourquoi quelqu'un lancerait float*avant de libérer. J'ai essayé free((void *)velocity);avec gcc 4.8.3. Bien sûr, cela ne fonctionnerait pas avec un ancien compilateur
Manos Nikolaidis
54
Mais pourquoi auriez-vous besoin d'allouer dynamiquement de la mémoire constante? Vous ne pourrez jamais l'utiliser!
Nils_M
33
@Nils_M c'est un exemple simplifié pour faire valoir un point. Ce que j'ai fait dans le code réel d'une fonction est d'allouer de la mémoire non const, d'assigner des valeurs, de convertir un pointeur const et de le renvoyer. Maintenant, il y a un pointeur vers la mémoire const préaffectée que quelqu'un doit libérer.
Manos Nikolaidis
2
Exemple : «Ces sous-programmes renvoient la chaîne dans la mémoire nouvellement mallocée, pointée par * stringValueP, que vous devez éventuellement libérer. Parfois, la fonction OS que vous utilisez pour libérer de la mémoire est déclarée prendre un pointeur vers quelque chose de non constant comme argument, donc parce que * stringValueP est un pointeur vers un const. »
Carsten S
3
Errones, si une fonction prend const char *pcomme argument et libère alors il, la bonne chose à faire est de ne pas jeter pà char*avant d' appeler gratuitement. Il ne s'agit pas de le déclarer comme prenant const char *pen premier lieu, car il se modifie *p et doit être déclaré en conséquence. (Et s'il prend un pointeur const au lieu d'un pointeur vers const, int *const pvous n'avez pas besoin de lancer car il est en fait légal et fonctionne donc très bien sans le cast.)
Ray
59

Pré-standard C n'avait pas void*mais seulement char*, vous deviez donc convertir tous les paramètres passés. Si vous rencontrez un ancien code C, vous pourriez donc trouver de tels moulages.

Question similaire avec références .

Lorsque la première norme C a été publiée, les prototypes pour malloc et free sont passés de l'avoir char*à void*ce qu'ils ont encore aujourd'hui.

Et bien sûr, dans la norme C, de tels moulages sont superflus et nuisent à la lisibilité.

Lundin
la source
23
Mais pourquoi voudriez-vous convertir l'argument freedans le même type qu'il est déjà?
jwodder
4
@chux Le problème avec le pré-standard est juste que: il n'y a aucune obligation pour quoi que ce soit. Les gens ont juste pointé du doigt le livre K&R pour canon parce que c'était la seule chose qu'ils avaient. Et comme nous pouvons le voir à partir de plusieurs exemples de la 2ème édition de K&R, K&R eux-mêmes freene savent pas comment les transtypages du paramètre fonctionnent en C standard (vous n'avez pas besoin de lancer). Je n'ai pas lu la 1ère édition, donc je ne peux pas dire s'ils étaient également confus à l'époque pré-standard des années 80.
Lundin
7
Le C pré-standard n'avait pas void*, mais il n'avait pas non plus de prototypes de fonction, donc le cast de l'argument de freeétait toujours inutile même dans K&R (en supposant que tous les types de pointeurs de données utilisaient la même représentation).
Ian Abbott
6
Pour plusieurs raisons déjà énoncées dans les commentaires, je ne pense pas que cette réponse ait un sens.
R .. GitHub STOP HELPING ICE
4
Je ne vois pas comment cette réponse répondrait vraiment à quelque chose de pertinent. La question originale implique des lancers vers d'autres types, pas seulement vers char *. Quel sens cela aurait-il dans les vieux compilateurs sans void? Que permettraient de tels moulages?
Du
34

Voici un exemple où la gratuité échouerait sans un casting:

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 :)

En C, vous pouvez obtenir un avertissement (vous en avez un dans VS2012). En C ++, vous obtiendrez une erreur.

Hormis les rares cas, le casting ne fait que gonfler le code ...

Edit: J'ai casté pour void*ne int*pas démontrer l'échec. Il fonctionnera de la même manière que int*sera converti en void*implicitement. int*Code ajouté .

Egur
la source
Notez que dans le code publié dans la question, les transtypages ne sont pas vers void *, mais vers float *et char *. Ces moulages ne sont pas simplement étrangers, ils sont faux.
Andrew Henle
1
La question est en fait le contraire.
m0skit0
1
Je ne comprends pas la réponse; dans quel sens free(p)échouerait? Cela donnerait-il une erreur de compilation?
Codor
1
Ce sont de bons points. Il en va de même pour les constpointeurs de qualification, évidemment.
Lundin
2
volatileexiste depuis que C a été standardisé, sinon plus. Il n'a pas été ajouté dans C99.
R .. GitHub STOP HELPING ICE
30

Ancienne raison: 1. En utilisant free((sometype*) ptr), le code est explicite sur le type que le pointeur doit être considéré comme faisant partie de l' free()appel. Le cast explicite est utile lorsqu'il free()est remplacé par un (do-it-yourself) DIY_free().

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

A DIY_free()était (est) une certaine manière, en particulier en mode débogage, pour effectuer une analyse d' exécution du pointeur étant libéré. Ceci est souvent associé à un DIY_malloc()pour ajouter des identifiants, des comptes globaux d'utilisation de la mémoire, etc. Mon groupe a utilisé cette technique pendant des années avant que des outils plus modernes n'apparaissent. Il obligeait à ce que l'élément en cours de libération ait été lancé dans le type qui lui avait été attribué à l'origine.

  1. Étant donné les nombreuses heures passées à rechercher les problèmes de mémoire, etc., de petites astuces comme lancer le type free'd aideraient à rechercher et à réduire le débogage.

Moderne: éviter constet volatileavertissements tels que traités par Manos Nikolaidis @ et @egur . Je pensais noterais les effets des 3 qualificatifs : const, volatileetrestrict .

[modifier] Ajouté char * restrict *rp2par @R .. commentaire

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;
}
chux - Réintégrer Monica
la source
3
restrictn'est pas un problème en raison de l'endroit où il est placé - cela affecte l'objet et rpnon le type pointé. Si vous l'aviez à la place char *restrict *rp, alors ce serait important.
R .. GitHub STOP HELPING ICE
16

Voici une autre hypothèse alternative.

On nous dit que le programme a été écrit avant C89, ce qui signifie qu'il ne peut pas contourner une sorte de non-concordance avec le prototype de free, car non seulement il n'y avait rien de tel que constni void *avant C89, mais il n'existait pas de un prototype de fonction avant C89. stdlib.helle-même était une invention du comité. Si les en-têtes système avaient pris la peine de déclarer freedu tout, ils l'auraient fait comme ceci:

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

Maintenant, le point clé ici est que l'absence de prototypes de fonction signifiait que le compilateur n'a pas vérifié le type d'argument . Il a appliqué les promotions d'argument par défaut (les mêmes qui s'appliquent toujours aux appels de fonction variadiques) et c'était tout. La responsabilité de faire correspondre les arguments à chaque site d'appel avec les attentes de l'appelé incombait entièrement au programmeur.

Cependant, cela ne signifie toujours pas qu'il était nécessaire de lancer l'argument freesur la plupart des compilateurs K&R. Une fonction comme

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

aurait dû être compilé correctement. Je pense donc que nous avons ici un programme écrit pour faire face à un compilateur bogué pour un environnement inhabituel: par exemple, un environnement où sizeof(float *) > sizeof(int)et le compilateur ne le ferait pas la convention d'appel appropriée pour les pointeurs à moins que vous ne les castiez au point de l'appel.

Je ne suis pas au courant d'un tel environnement, mais cela ne veut pas dire qu'il n'y en avait pas. Les candidats les plus probables qui me viennent à l'esprit sont les compilateurs «minuscules C» réduits pour les micros 8 et 16 bits au début des années 80. Je ne serais pas non plus surpris d'apprendre que les premiers Crays avaient des problèmes comme celui-ci.

zwol
la source
1
Je suis entièrement d'accord avec la première moitié. Et la seconde moitié est une conjecture intrigante et plausible.
chux