Dois-je lancer le résultat de malloc?

2409

Dans cette question , quelqu'un a suggéré dans un commentaire que je ne jeter le résultat de malloc, à savoir

int *sieve = malloc(sizeof(int) * length);

plutôt que:

int *sieve = (int *) malloc(sizeof(int) * length);

Pourquoi en serait-il ainsi?

Patrick McDonald
la source
222
De plus, il est plus facile d'écrire sieve = malloc (sizeof * sieve * length);
William Pursell
3
Les réponses ici sont un champ de mines à sens unique. La réponse est "cela dépend". Ce n'est pas une chose nécessaire pour faire fonctionner le programme. Cependant certaines normes de codage l'exigent ... Par exemple, voir CERT C Coding Standard
Dr. Person Person II
Étrangement, tout le monde convient que vous ne lancez pas NULL. (c'est probablement pourquoi C ++ a introduit nullptr: C ++ n'autorise aucune conversion de pointeur implicite)
Sapphire_Brick
Vous pouvez, mais vous n'en avez pas besoin. Mais vous devez en C ++.
user12211554

Réponses:

2218

Non ; vous ne lancez pas le résultat, car:

  • Il est inutile, car void * est automatiquement et en toute sécurité promu vers tout autre type de pointeur dans ce cas.
  • Il ajoute de l'encombrement au code, les transtypages ne sont pas très faciles à lire (surtout si le type de pointeur est long).
  • Cela vous fait vous répéter, ce qui est généralement mauvais.
  • Il peut masquer une erreur si vous avez oublié de l'inclure <stdlib.h>. Cela peut provoquer des plantages (ou pire, ne pas provoquer de plantages jusqu'à bien plus tard dans une partie totalement différente du code). Considérez ce qui se passe si les pointeurs et les entiers sont de tailles différentes; alors vous cachez un avertissement en castant et risquez de perdre des morceaux de votre adresse retournée. Remarque: à partir de C99, les fonctions implicites ont disparu de C, et ce point n'est plus pertinent car il n'y a pas d'hypothèse automatique que les fonctions non déclarées reviennent int.

À titre de clarification, notez que j'ai dit "vous ne lancez pas", pas "vous n'avez pas besoin de lancer". À mon avis, c'est un échec à inclure le casting, même si vous avez bien compris. Il n'y a tout simplement aucun avantage à le faire, mais un tas de risques potentiels, et l'inclusion de la distribution indique que vous ne connaissez pas les risques.

Notez également, comme le soulignent les commentateurs, que ce qui précède parle de C droit, pas de C ++. Je crois très fermement au C et au C ++ en tant que langages séparés.

Pour ajouter plus, votre code répète inutilement les informations de type ( int) qui peuvent provoquer des erreurs. Il vaut mieux dé-référencer le pointeur utilisé pour stocker la valeur de retour, pour "verrouiller" les deux ensemble:

int *sieve = malloc(length * sizeof *sieve);

Cela déplace également lengthl'avant vers l'avant pour une visibilité accrue et supprime les parenthèses redondantes avec sizeof; ils ne sont nécessaires que lorsque l'argument est un nom de type. Beaucoup de gens semblent ne pas le savoir (ou l'ignorer), ce qui rend leur code plus verbeux. N'oubliez pas: ce sizeofn'est pas une fonction! :)


Bien que le déplacement lengthvers l'avant puisse augmenter la visibilité dans certains cas rares, il convient également de faire attention au fait que dans le cas général, il devrait être préférable d'écrire l'expression comme suit:

int *sieve = malloc(sizeof *sieve * length);

Depuis garder le sizeofpremier, dans ce cas, assure la multiplication se fait avec au moins les size_tmathématiques.

Comparer: malloc(sizeof *sieve * length * width)vs malloc(length * width * sizeof *sieve)le second peut déborder le length * widthquand widthet lengthsont de plus petits types que size_t.

se détendre
la source
22
Veuillez envisager de mettre à jour la réponse. Le casting n'est plus dangereux et se répéter n'est pas nécessairement une mauvaise chose (la redondance peut aider à détecter les erreurs).
n. «pronoms» m.
12
Les compilateurs ont changé. Un compilateur à jour vous avertira d'une déclaration manquante de malloc.
n. «pronoms» m.
55
@nm Ok. Je pense qu'il est mauvais de supposer que quiconque lit ici a un compilateur particulier. De plus, depuis C11, tout le concept de «fonction implicite» a disparu, je ne le savais pas. Pourtant, je ne vois pas l'intérêt d'ajouter une distribution inutile. Faites-vous aussi int x = (int) 12;juste pour clarifier les choses?
détendre
22
@nm si la conversion explicite d'un pointeur vide "a aidé" à résoudre un bogue, vous avez probablement rencontré un comportement indéfini, ce qui signifierait que le programme en question a probablement un bogue bien pire et non découvert que vous n'avez pas encore rencontré. Et un jour, par une froide soirée d'hiver, vous rentrerez du travail pour trouver votre page GitHub inondée de rapports de problèmes se plaignant de démons volant hors du nez des utilisateurs
Braden Best
12
@unwind Même si je suis d'accord avec vous, ce (int)12n'est pas comparable.12 est un int, le casting ne fait tout simplement rien. La valeur de retour de malloc()est void *, pas le type de pointeur converti en. (Si ce n'est pas le cas, void *alors l'analogie (int)12serait (void*)malloc(…)ce dont personne ne discute.)
Amin Negm-Awad
376

En C, vous n'avez pas besoin de transtyper la valeur de retour de malloc. Le pointeur vers void retourné par mallocest automatiquement converti au type correct. Cependant, si vous voulez que votre code compile avec un compilateur C ++, une conversion est nécessaire. Une alternative préférée au sein de la communauté est d'utiliser les éléments suivants:

int *sieve = malloc(sizeof *sieve * length);

ce qui vous évite également d'avoir à vous soucier de changer le côté droit de l'expression si jamais vous changez le type de sieve.

Les lancers sont mauvais, comme les gens l'ont souligné. Surtout les lancers de pointeurs

dirkgently
la source
71
@MAKZ Je dirais que malloc(length * sizeof *sieve)cela donne l'impression que sizeofc'est une variable - donc je pense que malloc(length * sizeof(*sieve))c'est plus lisible.
Michael Anderson
21
Et encore malloc(length * (sizeof *sieve))plus lisible. A MON HUMBLE AVIS.
Toby Speight
19
Mis à ()part le problème de Michael Anderson , notez que votre style suggéré a changé l'ordre., Considérez quand le nombre d'éléments est calculé comme length*width, en gardant lesizeof première dans ce cas la multiplication se fait Assure avec au moins size_tmathématiques. Comparer malloc(sizeof( *ptr) * length * width)vs malloc(length * width * sizeof (*ptr))- le 2e peut déborder length*widthquand il width,lengthy a des types plus petits size_t.
chux
3
@chux ce n'est pas évident, mais la réponse a été modifiée pour que mon commentaire soit moins pertinent - la suggestion originale étaitmalloc(sizeof *sieve * length)
Michael Anderson
12
C n'est pas C ++. Faire semblant de le faire mènera finalement à la confusion et à la tristesse. Si vous utilisez C ++, une conversion de style C est également mauvaise (sauf si vous utilisez un très ancien compilateur C ++). Et static_cast>()(oureinterpret_cast<>() ) n'est compatible avec aucun dialecte de C.
David C.
349

Vous avez jeté, parce que:

  • Il rend votre code plus portable entre C et C ++, et comme le montre l'expérience SO, un grand nombre de programmeurs affirment qu'ils écrivent en C lorsqu'ils écrivent vraiment en C ++ (ou C plus des extensions de compilateur local).
  • Ne pas le faire peut masquer une erreur : notez tous les exemples SO de confusion quand écrire type *par rapport àtype ** .
  • L'idée qu'il vous empêche de remarquer que vous avez échoué dans #includeun fichier d'en-tête approprié manque la forêt pour les arbres . C'est la même chose que de dire "ne vous inquiétez pas du fait que vous n'ayez pas demandé au compilateur de se plaindre de ne pas voir de prototypes - ce stdlib.h embêtant est la VRAIE chose importante à retenir!"
  • Il oblige à une contre-vérification cognitive supplémentaire . Il place le type souhaité (présumé) juste à côté de l'arithmétique que vous faites pour la taille brute de cette variable. Je parie que vous pourriez faire une étude SO qui montre que les malloc()bugs sont détectés beaucoup plus rapidement quand il y a un casting. Comme pour les assertions, les annotations qui révèlent une intention réduisent les bogues.
  • Se répéter d'une manière que la machine peut vérifier est souvent un excellente idée. En fait, c'est ce qu'est une assertion, et cette utilisation de cast est une assertion. Les assertions sont toujours la technique la plus générale que nous ayons pour obtenir un code correct, depuis que Turing a eu l'idée il y a tant d'années.
Ron Burk
la source
39
@ulidtko Si vous ne le saviez pas, il est possible d'écrire du code qui se compile à la fois en C et en C ++. En fait, la plupart des fichiers d'en-tête sont comme ça, et ils contiennent souvent du code (macros et fonctions en ligne). Avoir un fichier .c/ .cppà compiler comme les deux n'est pas très souvent utile, mais un cas ajoute le throwsupport C ++ lors de la compilation avec le compilateur C ++ (mais return -1;lorsqu'il est compilé avec le compilateur C, ou autre).
hyde
37
Si quelqu'un avait des appels malloc en ligne dans un en-tête, je ne serais pas impressionné, #ifdef __cplusplus et extern "C" {} sont pour ce travail, sans ajouter de transtypages supplémentaires.
paulm
15
Eh bien, le point 1 n'est pas pertinent, car C! = C ++, les autres points sont également triviaux, si vous utilisez la variable dans votre mallocappel: char **foo = malloc(3*sizeof(*foo));si tout à fait à l'épreuve: 3 pointeurs vers des pointeurs char. puis boucle, et fais foo[i] = calloc(101, sizeof(*(foo[i])));. Allouez un tableau de 101 caractères, soigneusement initialisés à des zéros. Aucun plâtre nécessaire. changez la déclaration en unsigned charou tout autre type, d'ailleurs, et vous êtes toujours bon
Elias Van Ootegem
34
Quand j'ai pensé l'avoir, ça y est! Réponse fantastique. C'est la première fois ici dans StackOverflow que je +1 à deux réponses opposées! +1 Non, vous ne lancez pas, et +1 Oui, vous lancez! LOL. Vous êtes formidables. Et pour moi et mes étudiants, je me suis décidé: je jette. Les erreurs commises par les élèves sont plus faciles à repérer lors du casting.
Dr Beco
15
@Leushenko: Se répéter d'une manière qui ne peut pas être validée par la machine ni par une inspection locale est mauvais. Se répéter d'une manière qui peut être validée par de tels moyens est moins mauvais. Étant donné struct Zebra *p; ... p=malloc(sizeof struct Zebra);, le malloc ne peut pas éviter de dupliquer des informations sur le type de p, mais ni le compilateur ni l'inspection du code local ne détecteraient de problème si un type changeait, mais pas l'autre. Changez le code en p=(struct Zebra*)malloc(sizeof struct Zebra);et le compilateur squawk si le type de cast ne correspond pas p, et l' inspection locale révélera ...
supercat
170

Comme d'autres l'ont dit, ce n'est pas nécessaire pour C, mais nécessaire pour C ++. Si vous pensez que vous allez compiler votre code C avec un compilateur C ++, pour quelque raison que ce soit, vous pouvez utiliser une macro à la place, comme:

#ifdef __cplusplus
# define NEW(type, count) ((type *)calloc(count, sizeof(type)))
#else
# define NEW(type, count) (calloc(count, sizeof(type)))
#endif

De cette façon, vous pouvez toujours l'écrire de manière très compacte:

int *sieve = NEW(int, 1);

et il compilera pour C et C ++.

quinmars
la source
17
Puisque vous utilisez de toute façon une macro, pourquoi ne l'utilisez-vous pas newdans la définition de C ++?
Hosam Aly
63
Parce qu'il n'y a aucune raison de le faire. C'est principalement pour les programmes C qui sont compilés avec un compilateur C ++. Si vous allez utiliser «nouveau», la seule chose que vous obtenez est des problèmes. Vous avez alors besoin également d'une macro gratuitement. Et vous avez besoin d'une macro pour libérer un tableau, une différenciation qui n'existe pas en C.
quinmars
8
Sans oublier que ce n'est pas vous qui libérez la mémoire mais peut-être une bibliothèque C que vous utilisez, etc. Beaucoup de problèmes possibles sans aucun gain.
quinmars
86
@Hosam: Oui, certainement. Si vous utilisez, newvous devez utiliser deleteet si vous utilisez, malloc()vous devez vous free(). Ne les mélangez jamais.
Graeme Perrow
17
Si l'on veut adopter cette approche, appeler la macro NEWest probablement une mauvaise idée car la ressource n'est jamais retournée en utilisant delete(ou DELETE), donc vous mélangez votre vocabulaire. Au lieu de cela, le nommer MALLOC, ou plutôt CALLOCdans ce cas, aurait plus de sens.
mah
139

De Wikipédia :

Avantages de la coulée

  • L'inclusion de la distribution peut permettre à un programme ou une fonction C de se compiler en C ++.

  • La distribution permet des versions antérieures à 1989 de malloc qui renvoyaient à l'origine un caractère *.

  • La conversion peut aider le développeur à identifier les incohérences de dimensionnement de type si le type de pointeur de destination change, en particulier si le pointeur est déclaré loin de l'appel malloc () (bien que les compilateurs modernes et les analyseurs statiques puissent avertir d'un tel comportement sans nécessiter la conversion).

Inconvénients du casting

  • Selon la norme ANSI C, la distribution est redondante.

  • L'ajout de la distribution peut masquer l'échec de l'inclusion de l'en-tête stdlib.h, dans lequel se trouve le prototype de malloc. En l'absence de prototype pour malloc, la norme exige que le compilateur C suppose que malloc renvoie un int. S'il n'y a pas de transtypage, un avertissement est émis lorsque cet entier est affecté au pointeur; cependant, avec le casting, cet avertissement n'est pas produit, cachant un bug. Sur certaines architectures et modèles de données (tels que LP64 sur les systèmes 64 bits, où long et les pointeurs sont 64 bits et int 32 bits), cette erreur peut en fait entraîner un comportement indéfini, car le malloc implicitement déclaré renvoie un 32- valeur binaire alors que la fonction réellement définie renvoie une valeur 64 bits. Selon les conventions d'appel et la disposition de la mémoire, cela peut entraîner un écrasement de la pile. Ce problème est moins susceptible de passer inaperçu dans les compilateurs modernes, car ils produisent uniformément des avertissements qu'une fonction non déclarée a été utilisée, donc un avertissement apparaîtra toujours. Par exemple, le comportement par défaut de GCC consiste à afficher un avertissement qui lit "déclaration implicite incompatible de fonction intégrée", que le transtypage soit présent ou non.

  • Si le type du pointeur est modifié lors de sa déclaration, on peut également avoir besoin de changer toutes les lignes où malloc est appelé et cast.

Bien que malloc sans casting soit la méthode préférée et la plupart des programmeurs expérimentés la choisissent , vous devez utiliser celui que vous aimez avoir connaissance des problèmes.

ie: Si vous avez besoin de compiler un programme C en C ++ (bien qu'il s'agisse d'un langage séparé), vous devez transtyper le résultat de l'utilisation malloc.

ashiquzzaman33
la source
1
Que signifie « Casting peut aider le développeur à identifier les incohérences dans la taille des types si le type de pointeur de destination change, en particulier si le pointeur est déclaré loin de l' malloc()appel »? Pouvez-vous donner un exemple?
Spikatrix
3
@CoolGuy: Voir un commentaire précédent sur une autre réponse . Mais notez que l' p = malloc(sizeof(*p) * count)idiome détecte automatiquement les modifications de type, vous n'avez donc pas à recevoir d'avertissement ni à changer quoi que ce soit. Ce n'est donc pas un réel avantage par rapport à la meilleure alternative pour le non-casting.
Peter Cordes
7
C'est la bonne réponse: il y a des avantages et des inconvénients, et cela se résume à une question de goût (sauf si le code doit être compilé en C ++ - alors la conversion est obligatoire).
Peter - Rétablir Monica
3
Le point 3 est théorique, car si le type du pointeur est modifié lors de sa déclaration, il convient de vérifier chaque instance de malloc, realloc et free inolving ce type. Le casting vous obligera à faire exactement cela.
Michaël Roy
104

En C, vous pouvez implicitement convertir un voidpointeur en tout autre type de pointeur, donc une conversion n'est pas nécessaire. En utiliser un peut suggérer à l'observateur occasionnel qu'il y a une raison pour laquelle il en faut un, ce qui peut être trompeur.

PaulJWilliams
la source
100

Vous ne lancez pas le résultat de malloc, car cela ajoute un encombrement inutile à votre code.

La raison la plus courante pour laquelle les gens lancent le résultat de malloc est qu'ils ne sont pas sûrs du fonctionnement du langage C. C'est un signe d'avertissement: si vous ne savez pas comment fonctionne un mécanisme de langage particulier, alors ne devinez pas. Cherchez ou demandez sur Stack Overflow.

Certains commentaires:

  • Un pointeur vide peut être converti en / à partir de tout autre type de pointeur sans transtypage explicite (C11 6.3.2.3 et 6.5.16.1).

  • C ++ n'autorisera cependant pas une conversion implicite entre void*et un autre type de pointeur. Donc, en C ++, la conversion aurait été correcte. Mais si vous programmez en C ++, vous devez utiliser newet non malloc (). Et vous ne devez jamais compiler de code C à l'aide d'un compilateur C ++.

    Si vous devez prendre en charge C et C ++ avec le même code source, utilisez les commutateurs du compilateur pour marquer les différences. N'essayez pas de rassasier les deux normes linguistiques avec le même code, car elles ne sont pas compatibles.

  • Si un compilateur C ne peut pas trouver une fonction parce que vous avez oublié d'inclure l'en-tête, vous obtiendrez une erreur de compilateur / éditeur de liens à ce sujet. Donc, si vous avez oublié d'inclure<stdlib.h> ce n'est pas grave, vous ne pourrez pas créer votre programme.

  • Sur les anciens compilateurs qui suivent une version de la norme qui a plus de 25 ans, en oubliant d'inclure <stdlib.h> entraînerait un comportement dangereux. Parce que dans cette ancienne norme, les fonctions sans prototype visible ont implicitement converti le type de retour en int. Le cast explicite du résultat de malloc cacherait alors ce bug.

    Mais ce n'est vraiment pas un problème. Vous n'utilisez pas un ordinateur de 25 ans, alors pourquoi utiliser un compilateur de 25 ans?

Lundin
la source
9
"l'encombrement inutile" est une hyperbole dédaigneuse qui a tendance à faire dérailler toute possibilité de convaincre quiconque n'est pas déjà d'accord avec vous. Un casting n'est certainement pas inutile; Les réponses de Ron Burk et Kaz avancent des arguments en faveur du casting avec lesquels je suis tout à fait d'accord. Que ces préoccupations pèsent plus que celles que vous mentionnez est une question raisonnable à poser. Pour moi, vos préoccupations semblent relativement mineures par rapport aux leurs.
Don Hatch
"Un pointeur vide peut être converti vers / depuis tout autre type de pointeur sans transtypage explicite" n'est pas pris en charge par 6.3.2.3. Peut-être pensez-vous à "pointer vers n'importe quel type d'objet"? "pointeur vide" et "pointeur vers une fonction" ne sont pas si facilement convertibles.
chux
En effet, la référence était incomplète. La partie pertinente pour l '"implicitement" est la règle de la simple assignation 6.5.16.1. "un opérande est un pointeur vers un type d'objet, et l'autre est un pointeur vers une version qualifiée ou non qualifiée de void". J'ai ajouté cette référence à la réponse pour être complet.
Lundin
91

En C, vous obtenez une conversion implicite de void *vers tout autre pointeur (de données).

EFraim
la source
6
@Jens: OK, la formulation la plus appropriée est peut-être "conversion implicite". Comme l'utilisation de la variable intégrale dans l'expression en virgule flottante.
EFraim
@EFraim Cela entraînerait en fait un casting, et un implicite à cela.
Mad Physicist
71

Cast de la valeur retournée par malloc() n'est plus nécessaire maintenant, mais je voudrais ajouter un point qui semble n'avoir été signalé par personne:

Dans les temps anciens, c'est-à-dire avant que l' ANSI C fournisse le void *type générique de pointeurs, char *c'est le type pour une telle utilisation. Dans ce cas, le cast peut fermer les avertissements du compilateur.

Référence: C FAQ

Yu Hao
la source
2
Fermer les avertissements du compilateur est une mauvaise idée.
Albert van der Horst
8
@AlbertvanderHorst Pas si vous le faites en résolvant le problème exact, l'avertissement est là pour vous avertir.
Dan Bechard
@Dan. Si, en résolvant le problème exact, on entend une réécriture d'un sous-programme pour retourner des types C ANSI modernes au lieu de char *, je suis d'accord. Je n'appellerais pas cela fermer le compilateur. Ne cédez pas aux gestionnaires qui insistent sur le fait qu'il n'y a aucun avertissement du compilateur, au lieu de les utiliser à chaque recompilation pour trouver d'éventuels problèmes. Groetjes Albert
Albert van der Horst
53

En ajoutant simplement mon expérience, en étudiant l'ingénierie informatique, je vois que les deux ou trois professeurs que j'ai vus écrire en C ont toujours fait du malloc, mais celui que j'ai demandé (avec un immense CV et une compréhension de C) m'a dit que c'était absolument inutile mais seulement utilisé pour être absolument spécifique, et pour amener les étudiants dans la mentalité d'être absolument spécifique. Fondamentalement, la conversion ne changera rien à son fonctionnement, elle fait exactement ce qu'elle dit, alloue de la mémoire et la diffusion ne l'affecte pas, vous obtenez la même mémoire, et même si vous la convertissez en autre chose par erreur (et en quelque sorte éludez le compilateur erreurs) C y accédera de la même manière.

Edit: Casting a un certain point. Lorsque vous utilisez la notation de tableau, le code généré doit savoir combien de mémoire il doit avancer pour atteindre le début de l'élément suivant, ceci est réalisé grâce à la conversion. De cette façon, vous savez que pour un double, vous avancez de 8 octets tandis que pour un int, vous passez 4, et ainsi de suite. Ainsi, cela n'a aucun effet si vous utilisez la notation de pointeur, en notation de tableau cela devient nécessaire.

user3079666
la source
3
Sauf comme déjà mentionné, le cast peut cacher des bogues et rendre le code plus difficile à analyser pour le compilateur ou l'analyseur statique.
Lundin
2
"Essentiellement, le casting ne changera rien à son fonctionnement". La conversion vers le type correspondant ne devrait rien changer, mais le type de var doit-il changer et la distribution ne correspond plus, des problèmes pourraient-ils survenir? IWO, les types cast et var doivent être synchronisés - deux fois les travaux de maintenance.
chux
Je peux voir pourquoi les profs préfèrent le casting. La diffusion peut être utile d'un point de vue pédagogique où elle transmet aux informations de l'instructeur et le code étudiant n'a pas besoin d'être conservé - son code jetable. Pourtant, du point de vue du codage, de l'examen par les pairs et de la maintenance , p = malloc(sizeof *p * n);c'est tellement simple et meilleur.
chux
53

Il n'est pas obligatoire de transtyper les résultats de malloc, car il renvoie void*, et a void*peut être pointé vers n'importe quel type de données.

user968000
la source
34

Un pointeur vide est un pointeur d'objet générique et C prend en charge la conversion implicite d'un type de pointeur vide vers d'autres types, il n'est donc pas nécessaire de le transtyper explicitement.

Cependant, si vous voulez que le même code fonctionne parfaitement compatible sur une plate-forme C ++, qui ne prend pas en charge la conversion implicite, vous devez effectuer le transtypage, donc tout dépend de la convivialité.

Effort
la source
2
Ce n'est pas un cas d'utilisation normal pour compiler une seule source en C et C ++ (par opposition, par exemple, à l'utilisation d'un fichier d'en-tête contenant des déclarations pour lier le code C et C ++ ensemble). L'utilisation de mallocet d'amis en C ++ est un bon signe d'avertissement qui mérite une attention particulière (ou une réécriture en C).
Toby Speight
1
"Un pointeur vide est un pointeur générique" -> "Un pointeur vide est un pointeur objet générique ". Les tailles des pointeurs de fonction peuvent dépasser void *, ce qui void *est donc insuffisant pour bien stocker un pointeur de fonction.
chux
mon intention de cette ligne était la même, mais de toute façon merci @chux pour la suggestion.
Endeavour
33

Voici ce que dit le manuel de référence de la bibliothèque GNU C :

Vous pouvez stocker le résultat de mallocdans n'importe quelle variable de pointeur sans transtypage, car ISO C convertit automatiquement le type void *en un autre type de pointeur si nécessaire. Mais la conversion est nécessaire dans des contextes autres que les opérateurs d'affectation ou si vous souhaitez que votre code s'exécute en C. traditionnel

Et en effet, la norme ISO C11 (p347) le dit:

Le pointeur renvoyé si l'allocation réussit est correctement aligné de sorte qu'il peut être affecté à un pointeur sur n'importe quel type d'objet avec une exigence d'alignement fondamentale, puis utilisé pour accéder à un tel objet ou à un tableau de ces objets dans l'espace alloué (jusqu'à ce que le l'espace est explicitement désalloué)

Slothworks
la source
31

Le type renvoyé est void *, qui peut être converti en le type de pointeur de données souhaité afin d'être déréférencable.

swaps
la source
1
void* peut être converti dans le type souhaité, mais il n'est pas nécessaire de le faire car il sera automatiquement converti. Donc, le casting n'est pas nécessaire, et en fait indésirable pour les raisons mentionnées dans les réponses à haut score.
Toby Speight
mais uniquement si vous devez la déréférencer "à la volée", si vous créez une variable à la place, elle sera convertie en toute sécurité et automatiquement en type effectif de la variable, sans transtypage (en C).
Ferrarezi
28

Dans le langage C, un pointeur void peut être affecté à n'importe quel pointeur, c'est pourquoi vous ne devez pas utiliser de transtypage de type. Si vous voulez une allocation "type sûr", je peux recommander les fonctions de macro suivantes, que j'utilise toujours dans mes projets C:

#include <stdlib.h>
#define NEW_ARRAY(ptr, n) (ptr) = malloc((n) * sizeof *(ptr))
#define NEW(ptr) NEW_ARRAY((ptr), 1)

Avec ceux-ci en place, vous pouvez simplement dire

NEW_ARRAY(sieve, length);

Pour les tableaux non dynamiques, la troisième macro de fonction indispensable est

#define LEN(arr) (sizeof (arr) / sizeof (arr)[0])

ce qui rend les boucles de tableau plus sûres et plus pratiques:

int i, a[100];

for (i = 0; i < LEN(a); i++) {
   ...
}
August Karlstrom
la source
"un pointeur vide peut être assigné à n'importe quel pointeur d' objet " Les pointeurs de fonction sont un autre problème, mais pas un malloc().
chux
L'affectation d'un void*à / à partir d'un pointeur de fonction peut perdre des informations, donc "un pointeur vide peut être affecté à n'importe quel pointeur", est un problème dans ces cas. L'affectation d'un pointeur void*de malloc() à n'importe quel pointeur d' objet n'est pas un problème.
chux
Le docommentaire de boucle concerne les macros impliquant une boucle mais qui s'éloigne de la question du titre. Supprimer ce commentaire. Va prendre celui-ci plus tard aussi.
chux
27

Cela dépend du langage de programmation et du compilateur. Si vous utilisez mallocen C, il n'est pas nécessaire de taper cast, car il saisira automatiquement cast. Cependant, si vous utilisez C ++, vous devez taper cast car mallocretournera un void*type.

Jeyamaran
la source
1
La fonction malloc retourne également un pointeur vide en C mais les règles du langage sont différentes de C ++.
août Karlstrom
16

Les gens habitués à GCC et Clang sont gâtés. Ce n'est pas si bon que ça.

J'ai été assez horrifié au fil des ans par les compilateurs incroyablement âgés que je devais utiliser. Souvent, les entreprises et les gestionnaires adoptent une approche ultra-conservatrice pour changer les compilateurs et ne testent même pas si un nouveau compilateur (avec une meilleure conformité aux normes et une optimisation du code) fonctionnera dans leur système. La réalité pratique pour les développeurs qui travaillent est que lorsque vous codez, vous devez couvrir vos bases et, malheureusement, la diffusion de mallocs est une bonne habitude si vous ne pouvez pas contrôler quel compilateur peut être appliqué à votre code.

Je suggérerais également que de nombreuses organisations appliquent leur propre norme de codage et que ce devrait être la méthode que les gens suivent si elle est définie. En l'absence de directives explicites, j'ai tendance à opter pour la compilation la plus probable partout, plutôt que l'adhésion servile à une norme.

L'argument selon lequel ce n'est pas nécessaire en vertu des normes actuelles est tout à fait valable. Mais cet argument omet les aspects pratiques du monde réel. Nous ne codons pas dans un monde régi exclusivement par la norme du jour, mais par les aspects pratiques de ce que j'aime appeler "le champ de réalité de la gestion locale". Et c'est plus courbé et tordu que l'espace-temps ne l'a jamais été. :-)

YMMV.

J'ai tendance à penser à lancer du malloc comme une opération défensive. Pas joli, pas parfait, mais généralement sûr. (Honnêtement, si vous n'avez pas inclus stdlib.h, vous avez bien plus de problèmes que de lancer malloc!).

StephenG
la source
15

J'ai mis le casting simplement pour montrer la désapprobation du trou laid dans le système de type, ce qui permet à du code tel que l'extrait suivant de compiler sans diagnostic, même si aucune conversion n'est utilisée pour provoquer la mauvaise conversion:

double d;
void *p = &d;
int *q = p;

Je souhaite que cela n'existe pas (et ce n'est pas le cas en C ++) et j'ai donc casté. Il représente mon goût et ma politique de programmation. Non seulement je jette un pointeur, mais effectivement, je vote et je chasse les démons de la stupidité . Si je ne peux pas vraiment chasser la bêtise , au moins laissez - moi exprimer le souhait de le faire avec un geste de protestation.

En fait, une bonne pratique consiste à encapsuler malloc(et à vos amis) les fonctions qui reviennent unsigned char *, et à ne jamais utiliser void *dans votre code. Si vous avez besoin d'un pointeur générique vers n'importe quel objet, utilisez un char *ou unsigned char *et avez des transtypages dans les deux directions. La seule relaxation à laquelle on peut s'adonner, peut-être, est d'utiliser des fonctions comme memsetet memcpysans transtypage.

Sur le sujet de la conversion et de la compatibilité C ++, si vous écrivez votre code de sorte qu'il se compile à la fois en C et C ++ (auquel cas vous devez convertir la valeur de retour de malloclors de son affectation à autre chose que void *), vous pouvez faire une très utile chose pour vous: vous pouvez utiliser des macros pour la conversion qui se traduisent par des transtypages de style C ++ lors de la compilation en C ++, mais réduisent à une conversion C lors de la compilation en C:

/* In a header somewhere */
#ifdef __cplusplus
#define strip_qual(TYPE, EXPR) (const_cast<TYPE>(EXPR))
#define convert(TYPE, EXPR) (static_cast<TYPE>(EXPR))
#define coerce(TYPE, EXPR) (reinterpret_cast<TYPE>(EXPR))
#else
#define strip_qual(TYPE, EXPR) ((TYPE) (EXPR))
#define convert(TYPE, EXPR) ((TYPE) (EXPR))
#define coerce(TYPE, EXPR) ((TYPE) (EXPR))
#endif

Si vous adhérez à ces macros, une simple greprecherche dans votre base de code pour ces identifiants vous montrera où se trouvent toutes vos conversions, afin que vous puissiez vérifier si certaines d'entre elles sont incorrectes.

Ensuite, si vous compilez régulièrement le code avec C ++, cela imposera l'utilisation d'une conversion appropriée. Par exemple, si vous utilisez strip_qualsimplement pour supprimer un constou volatile, mais que le programme change de telle manière qu'une conversion de type est maintenant impliquée, vous obtiendrez un diagnostic et vous devrez utiliser une combinaison de transtypages pour obtenir la conversion souhaitée.

Pour vous aider à adhérer à ces macros, le compilateur GNU C ++ (pas C!) A une belle fonctionnalité: un diagnostic optionnel qui est produit pour toutes les occurrences de transtypages de style C.

     -Wold-style-cast (C ++ et Objective-C ++ uniquement)
         Avertir si une conversion à l'ancienne (style C) vers un type non nul est utilisée
         au sein d'un programme C ++. Les transtypages de nouveau style (dynamic_cast,
         static_cast, reinterpret_cast et const_cast) sont moins vulnérables
         aux effets inattendus et beaucoup plus facile à rechercher.

Si votre code C se compile en C ++, vous pouvez utiliser cette -Wold-style-castoption pour trouver toutes les occurrences de la (type)syntaxe de transtypage qui peuvent se glisser dans le code, et poursuivre ces diagnostics en le remplaçant par un choix approprié parmi les macros ci-dessus (ou un combinaison, si nécessaire).

Ce traitement des conversions est la plus grande justification technique autonome pour travailler dans un "Clean C": le dialecte combiné C et C ++, qui à son tour justifie techniquement la conversion de la valeur de retour de malloc.

Kaz
la source
Comme d'autres l'ont souligné, je recommande généralement de ne pas mélanger le code C et C ++. Cependant, si vous avez de bonnes raisons de le faire, les macros peuvent être utiles.
Phil1970
@ Phil1970 Tout est écrit dans un dialecte cohérent, qui se trouve être portable pour les compilateurs C et C ++, et tire parti de certaines capacités de C ++. Il doit être tout compilé en C ++, ou bien tout compilé en C.
Kaz
C'est-à-dire ce que j'essayais de dire dans le commentaire précédent, c'est qu'il n'y a pas de mélange de C et C ++. L'intention est que le code soit tout compilé en C ou tout compilé en C ++.
Kaz
15

La meilleure chose à faire lors de la programmation en C chaque fois que cela est possible:

  1. Faites compiler votre programme via un compilateur C avec tous les avertissements activés -Wallet corrigez toutes les erreurs et avertissements
  2. Assurez-vous qu'aucune variable n'est déclarée comme auto
  3. Compilez-le ensuite à l'aide d'un compilateur C ++ avec -Wallet -std=c++11. Corrigez toutes les erreurs et avertissements.
  4. Compilez maintenant à nouveau à l'aide du compilateur C. Votre programme devrait maintenant se compiler sans aucun avertissement et contenir moins de bogues.

Cette procédure vous permet de profiter de la vérification stricte de type C ++, réduisant ainsi le nombre de bogues. En particulier, cette procédure vous oblige à inclure stdlib.hou vous obtiendrez

malloc n'a pas été déclaré dans ce domaine

et vous oblige également à lancer le résultat de mallocou vous obtiendrez

conversion non valide de void*versT*

ou quel que soit votre type de cible.

Les seuls avantages de l'écriture en C au lieu de C ++ que je peux trouver sont

  1. C a un ABI bien spécifié
  2. C ++ peut générer plus de code [exceptions, RTTI, modèles, polymorphisme d' exécution ]

Notez que le deuxième inconvénient devrait dans le cas idéal disparaître lors de l'utilisation du sous-ensemble commun à C avec la caractéristique polymorphe statique .

Pour ceux qui trouvent les règles strictes C ++ incommodes, nous pouvons utiliser la fonctionnalité C ++ 11 avec le type déduit

auto memblock=static_cast<T*>(malloc(n*sizeof(T))); //Mult may overflow...
user877329
la source
18
Utilisez un compilateur C pour le code C. Utilisez un compilateur C ++ pour le code C ++. Pas de si, pas de mais. La réécriture de votre code C en C ++ est une tout autre chose et peut - ou peut ne pas être - en valoir le temps et les risques.
Toby Speight
2
Je voudrais ajouter au conseil @TobySpeight: Si vous devez utiliser du code C dans un projet C ++, vous pouvez généralement compiler le code C en C (par exemple gcc -c c_code.c), le code C ++ en C ++ (par exemple g++ -c cpp_code.cpp), puis les lier ensemble (par exemple gcc c_code.o cpp_code.oou vice-versa selon les dépendances du projet). Maintenant, il ne devrait pas y avoir de raison de vous priver de fonctionnalités intéressantes dans les deux langues ...
autiste
1
@ user877329 C'est une alternative plus sensée à l'ajout minutieux de transtypages au code qui réduisent la lisibilité du code, uniquement pour être "compatible C ++".
autistique
1
Le principal avantage dans ce contexte est probablement que C vous permet d'écrire p = malloc(sizeof(*p));, ce qui n'a pas besoin d'être modifié en premier lieu si vous changez de pnom de type différent. L '"avantage" proposé de la conversion est que vous obtenez une erreur de compilation si ple type est incorrect, mais c'est encore mieux si cela fonctionne.
Peter Cordes
1
Je voudrais mentionner que l'écriture en C peut être nécessaire lors du ciblage de plateformes dépourvues de compilateurs C ++ appropriés. Les exceptions et les modèles sont des fonctionnalités qui aident généralement c ++ à générer du code plus petit et / ou plus efficace tandis que le polymorphisme d'exécution en C ++ est principalement équivalent à C.
user7860670
15

Non, vous ne lancez pas le résultat de malloc().

En général, vous nevoid * lancez pas vers ou depuis .

Une raison typique donnée pour ne pas le faire est que le #include <stdlib.h> ne pas pouvoir passer inaperçu. Ce n'est plus un problème depuis longtemps car C99 a rendu illégales les déclarations de fonctions implicites , donc si votre compilateur se conforme au moins à C99, vous obtiendrez un message de diagnostic.

Mais il y a un raison beaucoup plus forte de ne pas introduire de lancers de pointeurs inutiles:

En C, une conversion de pointeur est presque toujours une erreur . Cela est dû à la règle suivante ( §6.5 p7 dans N1570, le dernier projet pour C11):

Un objet doit avoir sa valeur stockée accessible uniquement par une expression lvalue qui a l'un des types suivants:
- un type compatible avec le type effectif de l'objet,
- une version qualifiée d'un type compatible avec le type effectif de l'objet,
- un type qui est le type signé ou non signé correspondant au type effectif de l'objet,
- un type qui est le type signé ou non signé correspondant à une version qualifiée du type effectif de l'objet,
- un type d'agrégation ou d'union qui en inclut un parmi les types susmentionnés parmi ses membres (y compris, récursivement, un membre d'une union sous-agrégée ou contenue), ou
- un type de caractère.

Ceci est également connu sous le nom de règle d'alias stricte . Le code suivant est donc un comportement non défini :

long x = 5;
double *p = (double *)&x;
double y = *p;

Et, parfois de manière surprenante, ce qui suit est également:

struct foo { int x; };
struct bar { int x; int y; };
struct bar b = { 1, 2};
struct foo *p = (struct foo *)&b;
int z = p->x;

, Parfois vous ne devez pointeurs de la distribution, mais étant donné la règle stricte de aliasing , vous devez être très prudent avec elle. Ainsi, toute occurrence d'un pointeur transtypé dans votre code est un endroit où vous devez vérifier sa validité . Par conséquent, vous n'écrivez jamais une distribution de pointeur inutile.

tl; dr

En bref: parce qu'en C, toute occurrence d'une conversion de pointeur devrait déclencher un drapeau rouge pour le code nécessitant une attention particulière, vous ne devez jamais écrire de transtypages de pointeur inutiles .


Notes annexes:

  • Il y a des cas où vous avez réellement besoin d' un cast void *, par exemple si vous souhaitez imprimer un pointeur:

    int x = 5;
    printf("%p\n", (void *)&x);

    La printf()conversion est nécessaire ici, car c'est une fonction variadique, donc les conversions implicites ne fonctionnent pas.

  • En C ++, la situation est différente. La conversion de types de pointeurs est quelque peu courante (et correcte) lorsque vous traitez des objets de classes dérivées. Par conséquent, il est logique qu'en C ++, la conversion vers et depuis void *n'est pas implicite. C ++ a tout un ensemble de différentes saveurs de casting.


la source
1
Dans vos exemples, vous évitez le vide *. il y a une différence entre la conversion de double * en int * et vice versa. malloc renvoie un pointel aligné sur le plus grand type standard, il n'y a donc pas de règles d'aliasing enfreintes même si quelqu'un transforme ce pointeur aligné en un autre type.
P__J__
L'aliasing n'a rien à voir avec l'alignement et pour le reste de votre commentaire - vous n'avez évidemment pas compris.
@PeterJ: juste au cas où, le but est d' éviter une conversion de pointeur inutile, donc cela ne ressemble pas à un morceau de code auquel vous devez porter une attention particulière.
Le problème d'alias strict n'a rien à voir avec les pointeurs vides. Afin d'obtenir des bogues causés par des violations strictes d'alias, vous devez déréférencer les données pointées. Et comme vous ne pouvez pas déréférencer un pointeur void, ces bogues ne sont par définition pas liés au pointeur void mais à autre chose.
Lundin
Au lieu de cela, vous devez établir une règle pour interdire toutes les conversions de pointeurs. Mais comment écririez-vous des choses comme les routines de sérialisation et la programmation liée au matériel? Des choses qui font la force de C. Ces lancers sont parfaits si vous savez ce que vous faites.
Lundin
15

Je préfère faire le casting, mais pas manuellement. Mon préféré utilise g_newet g_new0macros de glib. Si glib n'est pas utilisé, j'ajouterais des macros similaires. Ces macros réduisent la duplication de code sans compromettre la sécurité des types. Si vous vous trompez de type, vous obtiendrez un transtypage implicite entre des pointeurs non vides, ce qui entraînerait un avertissement (erreur en C ++). Si vous oubliez d'inclure l'en-tête qui définit g_newet g_new0, vous obtiendrez une erreur. g_newet les g_new0deux prennent les mêmes arguments, contrairement à malloccela prend moins d'arguments que calloc. Ajoutez simplement 0pour obtenir une mémoire initialisée à zéro. Le code peut être compilé avec un compilateur C ++ sans modifications.

proski
la source
12

La conversion est uniquement pour C ++ et non C. Dans le cas où vous utilisez un compilateur C ++, vous feriez mieux de le changer en compilateur C.


la source
9

Le concept derrière le pointeur void est qu'il peut être converti en n'importe quel type de données, c'est pourquoi malloc renvoie void. Vous devez également être conscient de la conversion automatique de caractères. Il n'est donc pas obligatoire de lancer le pointeur bien que vous deviez le faire. Il aide à garder le code propre et aide au débogage

iec2011007
la source
11
" Ce n'est pas obligatoire - mais vous devez le faire " - je pense qu'il y a là une contradiction!
Toby Speight
5
Je pense que vous devriez lire ce post à quelqu'un et voir s'il comprend ce que vous essayez de dire. Réécrivez-le ensuite, en précisant ce que vous voulez dire. Je ne comprends vraiment pas quelle est votre réponse.
Bill Woodger
9

Un pointeur vide est un pointeur générique et C prend en charge la conversion implicite d'un type de pointeur vide vers d'autres types, il n'est donc pas nécessaire de le transtyper explicitement.

Cependant, si vous voulez que le même code fonctionne parfaitement compatible sur une plate-forme C ++, qui ne prend pas en charge la conversion implicite, vous devez effectuer le transtypage, donc tout dépend de la convivialité.

dhana govindarajan
la source
9
  1. Comme d'autres l'ont dit, ce n'est pas nécessaire pour C, mais pour C ++.

  2. L'inclusion de la distribution peut permettre à un programme ou une fonction C de se compiler en C ++.

  3. En C, cela n'est pas nécessaire, car void * est automatiquement et en toute sécurité promu vers tout autre type de pointeur.

  4. Mais si vous lancez alors, cela peut masquer une erreur si vous avez oublié d'inclure oubliez d' stdlib.h . Cela peut provoquer des plantages (ou pire, ne pas provoquer de plantages jusqu'à bien plus tard dans une partie totalement différente du code).

    Parce que stdlib.h contient le prototype pour malloc est trouvé. En l'absence de prototype pour malloc, la norme exige que le compilateur C suppose que malloc retourne un int. S'il n'y a pas de transtypage, un avertissement est émis lorsque cet entier est affecté au pointeur; cependant, avec le casting, cet avertissement n'est pas produit, cachant un bug.

Mohit
la source
7

La conversion de malloc n'est pas nécessaire en C mais obligatoire en C ++.

La conversion n'est pas nécessaire en C à cause de:

  • void * est automatiquement et en toute sécurité promu vers tout autre type de pointeur dans le cas de C.
  • Il peut masquer une erreur si vous avez oublié d'inclure <stdlib.h> . Cela peut provoquer des plantages.
  • Si les pointeurs et les nombres entiers sont de tailles différentes, vous cachez un avertissement en transtypant et risquez de perdre des bits de votre adresse renvoyée.
  • Si le type du pointeur est modifié lors de sa déclaration, il peut également être nécessaire de modifier toutes les lignes où mallocest appelé et transtypé.

D'un autre côté, la diffusion peut augmenter la portabilité de votre programme. c'est-à-dire qu'il permet à un programme ou une fonction C de se compiler en C ++.

Aashish
la source
0

Pour moi, le retour à la maison et la conclusion ici est que la conversion mallocen C n'est absolument PAS nécessaire, mais si vous effectuez une conversion, cela n'affectera pas malloccar mallocvous allouera toujours votre espace mémoire béni demandé. Un autre point à retenir est la raison ou l'une des raisons pour lesquelles les gens font du casting et c'est pour leur permettre de compiler le même programme en C ou C ++.

Il peut y avoir d'autres raisons, mais d'autres raisons, très certainement, vous causeraient de graves problèmes tôt ou tard.

pasignature
la source
0

Vous pouvez, mais n'avez pas besoin de transtyper en C. Vous devez transtyper si ce code est compilé en C ++.

ivan.ukr
la source