Plus précisément, qu'est-ce qu'il y a de dangereux à lancer le résultat de malloc?

86

Maintenant, avant que les gens ne commencent à marquer cela comme un dup, j'ai lu tout ce qui suit, dont aucun ne fournit la réponse que je recherche:

  1. C FAQ: Quel est le problème avec la conversion de la valeur de retour de malloc?
  2. SO: Dois-je convertir explicitement la valeur de retour de malloc ()?
  3. SO: Inutile de lancer des pointeurs en C
  4. SO: Est-ce que je lance le résultat de malloc?

La FAQ C et de nombreuses réponses aux questions ci-dessus citent une erreur mystérieuse que mallocla valeur de retour du casting peut masquer; cependant, aucun d'entre eux ne donne un exemple précis d'une telle erreur dans la pratique. Maintenant, faites attention que j'ai dit erreur , pas d' avertissement .

Maintenant donné le code suivant:

#include <string.h>
#include <stdio.h>
// #include <stdlib.h>

int main(int argc, char** argv) {

    char * p = /*(char*)*/malloc(10);
    strcpy(p, "hello");
    printf("%s\n", p);

    return 0;
}

Compiler le code ci-dessus avec gcc 4.2, avec et sans le cast donne les mêmes avertissements, et le programme s'exécute correctement et fournit les mêmes résultats dans les deux cas.

anon@anon:~/$ gcc -Wextra nostdlib_malloc.c -o nostdlib_malloc
nostdlib_malloc.c: In function ‘main’:
nostdlib_malloc.c:7: warning: incompatible implicit declaration of built-in function ‘malloc’
anon@anon:~/$ ./nostdlib_malloc 
hello

Quelqu'un peut - il donner un exemple de code spécifique d 'une erreur de compilation ou d' exécution qui pourrait se produire en raison de mallocla valeur de retour de la conversion, ou est - ce juste une légende urbaine?

Edit Je suis tombé sur deux arguments bien écrits concernant ce problème:

  1. En faveur de la diffusion: Avis CERT: convertir immédiatement le résultat d'un appel de fonction d'allocation de mémoire en un pointeur vers le type alloué
  2. Contre la diffusion (erreur 404 du 14/02/2012: utilisez la copie Internet Archive Wayback Machine du 27/01/2010. {18/03/2016: "La page ne peut pas être explorée ou affichée en raison du fichier robots.txt."})
Robert S. Barnes
la source
6
le cast de voidpointeurs permet de compiler le code en C ++; certaines personnes disent que c'est une fonctionnalité, je dirais que c'est un bug;)
Christoph
1
aussi, lisez les commentaires sur le premier de vos liens car il décrit ce que vous devez faire au lieu de diffuser
Christoph
3
Je vais suivre les conseils du CERT pour inclure le casting. De plus, je n'oublierai jamais d'inclure stdlib.h. :)
Abhinav
1
Voici un exemple SO d 'une erreur d' exécution de compilation en raison de mallocla valeur de retour du transtypage: transtypage int*sur un arch 64 bits.
John_West
1
cette question n'est Cpas étiquetée C++(il s'agit de deux langues différentes). Toute discussion (comme dans certaines réponses) n'est donc pas pertinente pour cette question.
user3629249

Réponses:

66

Vous n'obtiendrez pas une erreur du compilateur , mais un avertissement du compilateur . Comme le disent les sources que vous citez (en particulier la première ), vous pouvez obtenir une erreur d'exécution imprévisible lors de l'utilisation de la distribution sans inclurestdlib.h .

Donc l'erreur de votre côté n'est pas le casting, mais oublier d'inclure stdlib.h. Les compilateurs peuvent supposer qu'il mallocs'agit d'une fonction retournant int, convertissant ainsi le void*pointeur réellement retourné par mallocvers int, puis vers votre type de pointeur en raison de la conversion explicite. Sur certaines plates-formes, les intpointeurs peuvent prendre différents nombres d'octets, de sorte que les conversions de type peuvent entraîner une corruption des données.

Heureusement, les compilateurs modernes donnent des avertissements qui indiquent votre erreur réelle. Voir le gccrésultat que vous avez fourni: il vous avertit que la déclaration implicite ( int malloc(int)) est incompatible avec le intégré malloc. Donc gccsemble savoir mallocmême sans stdlib.h.

Oublier le casting pour éviter cette erreur est généralement le même raisonnement que l'écriture

if (0 == my_var)

au lieu de

if (my_var == 0)

puisque ce dernier pourrait conduire à un bogue sérieux si l'on confondait =et ==, alors que le premier conduirait à une erreur de compilation. Personnellement, je préfère ce dernier style car il reflète mieux mon intention et je n'ai pas tendance à faire cette erreur.

Il en va de même pour la conversion de la valeur renvoyée par malloc: je préfère être explicite dans la programmation et je vérifie généralement pour inclure les fichiers d'en-tête pour toutes les fonctions que j'utilise.

Ferdinand Beyer
la source
2
Il semblerait que puisque le compilateur met en garde contre la déclaration implicite incompatible, alors ce n'est pas un problème tant que vous faites attention aux avertissements de votre compilateur.
Robert
4
@Robert: oui, compte tenu de certaines hypothèses sur le compilateur. Lorsque les gens donnent des conseils sur la meilleure façon d'écrire C en général , ils ne peuvent pas supposer que la personne qui reçoit les conseils utilise une version récente de gcc.
Steve Jessop
4
Oh, et la réponse à la deuxième question est que l'appelant contient le code pour récupérer la valeur de retour (qu'il pense être un int) et la convertir en T *. L'appelé écrit simplement la valeur de retour (comme un void *) et retourne. Donc, selon la convention d'appel: les retours int et les retours void * peuvent ou non être au «même endroit» (registre ou emplacement de pile); int et void * peuvent avoir ou non la même taille; la conversion entre les deux peut ou non être un non-op. Donc cela pourrait "juste fonctionner", ou la valeur pourrait être corrompue (certains bits perdus, peut-être), ou l'appelant pourrait prendre complètement la mauvaise valeur.
Steve Jessop
1
@ RobertS.Barnes en retard à la fête, mais: La valeur de retour ne fait généralement pas partie de la signature de la fonction, même pas en C ++. L'éditeur de liens génère simplement un saut vers un symbole, c'est tout.
Peter - Réintègre Monica
3
Vous pouvez obtenir une erreur d'exécution imprévisible lors de l'utilisation du cast sans inclure stdlib.h . C'est vrai, mais ne pas inclure stdlib.hest déjà une erreur en soi, même si vous n'obtenez que des avertissements de "déclaration implicite".
Jabberwocky
45

L'un des bons arguments de plus haut niveau contre la diffusion du résultat de mallocest souvent ignoré, même si, à mon avis, il est plus important que les problèmes de niveau inférieur bien connus (comme la troncature du pointeur lorsque la déclaration est manquante).

Une bonne pratique de programmation consiste à écrire du code, qui est aussi indépendant que possible du type. Cela signifie, en particulier, que les noms de types doivent être mentionnés dans le code le moins possible ou mieux ne pas être mentionnés du tout. Cela s'applique aux transtypages (évitez les transtypages inutiles), aux types comme arguments de sizeof(évitez d'utiliser des noms de type dans sizeof) et, généralement, à toutes les autres références aux noms de type.

Les noms de type appartiennent aux déclarations. Autant que possible, les noms de type doivent être limités aux déclarations et uniquement aux déclarations.

De ce point de vue, ce bit de code est mauvais

int *p;
...
p = (int*) malloc(n * sizeof(int));

et c'est bien mieux

int *p;
...
p = malloc(n * sizeof *p);

non pas simplement parce qu'il "ne malloctransforme pas le résultat de ", mais plutôt parce qu'il est indépendant du type (ou indépendant du type, si vous préférez), parce qu'il s'ajuste automatiquement à n'importe quel type pdéclaré avec, sans nécessiter aucune intervention de l'utilisateur.

Fourmi
la source
Fwiw, je pense que c'est plus ou moins la même raison que celle-ci: stackoverflow.com/questions/953112/... mais concentré sur l'indépendance de type plutôt que sur le bricolage. Bien sûr, le premier découle du second (ou vice versa), donc au moins il est mentionné parfois . :)
détendez-vous
5
@unwind vous signifie probablement DRY plutôt que DIY
kratenko
18

Les fonctions non prototypées sont supposées retourner int.

Vous lancez donc un intvers un pointeur. Si les pointeurs sont plus larges que ints sur votre plateforme, il s'agit d'un comportement très risqué.

De plus, bien sûr, certaines personnes considèrent les avertissements comme des erreurs, c'est-à-dire que le code doit être compilé sans eux.

Personnellement, je pense que le fait que vous n'ayez pas besoin de convertir void *en un autre type de pointeur est une fonctionnalité en C, et considère que le code est cassé.

se détendre
la source
14
J'ai cette conviction que le compilateur en sait plus sur le langage que moi, donc s'il me met en garde contre quelque chose, je fais attention.
György Andrasek
3
Dans de nombreux projets, le code C est compilé en tant que C ++ où vous devez convertir le fichier void*.
laalto
nit: " par défaut , les fonctions non prototypées sont supposées retourner int." - Voulez-vous dire qu'il est possible de changer le type de retour des fonctions non prototypées?
pmg
1
@laalto - C'est vrai, mais ça ne devrait pas l'être. C est C, pas C ++, et doit être compilé avec un compilateur C, pas un compilateur C ++. Il n'y a pas d'excuse: GCC (l'un des meilleurs compilateurs C du marché) fonctionne sur presque toutes les plates-formes imaginables (et génère également un code hautement optimisé). Quelles raisons pourriez-vous avoir pour compiler C avec un compilateur C ++, autres que la paresse et les normes lâches?
Chris Lutz
3
Exemple de code que vous pouvez compiler à la fois comme C et C ++: #ifdef __cplusplus \nextern "C" { \n#endif static inline uint16_t swb(uint16_t a) {return ((a << 8) | ((a >> 8) & 0xFF); } \n#ifdef __cplusplus\n } \n#endif. Maintenant, pourquoi vous voudriez appeler malloc dans une fonction statique en ligne, je ne sais vraiment pas, mais les en-têtes qui fonctionnent dans les deux sont à peine inconnus.
Steve Jessop
11

Si vous faites cela lors de la compilation en mode 64 bits, votre pointeur renvoyé sera tronqué à 32 bits.

EDIT: Désolé d'être trop bref. Voici un exemple de fragment de code à des fins de discussion.

principale()
{
   char * c = (char *) malloc (2);
   printf ("% p", c);
}

Supposons que le pointeur de tas retourné soit quelque chose de plus grand que ce qui est représentable dans un int, disons 0xAB00000000.

Si malloc n'est pas prototypé pour renvoyer un pointeur, la valeur int retournée sera initialement dans un registre avec tous les bits significatifs définis. Maintenant, le compilateur dit: "OK, comment puis-je convertir et int en pointeur". Ce sera soit une extension de signe, soit une extension zéro des 32 bits de poids faible que malloc "retourne" en omettant le prototype. Puisque int est signé, je pense que la conversion sera une extension de signe, qui dans ce cas convertira la valeur à zéro. Avec une valeur de retour de 0xABF0000000, vous obtiendrez un pointeur non nul qui vous amusera également lorsque vous essayez de le déréférencer.

Peeter Joot
la source
1
Pourriez-vous expliquer en détail comment cela se produirait?
Robert S.Barnes
5
Je pense que Peeter Joot a compris que "Par défaut, les fonctions non prototypées sont supposées retourner int" sans y compris stdlib.h, et sizeof (int) est de 32 bits tandis que sizeof (ptr) est de 64.
Test
4

Une règle logicielle réutilisable:

Dans le cas de l'écriture d'une fonction en ligne dans laquelle utilisé malloc (), afin de la rendre réutilisable pour le code C ++ également, veuillez faire un casting de type explicite (par exemple (char *)); sinon le compilateur se plaindra.

Tester
la source
avec un peu de chance, avec l'inclusion (récente) des optimisations de lien-time dans gcc (voir gcc.gnu.org/ml/gcc/2009-10/msg00060.html ), la déclaration de fonctions en ligne dans les fichiers d'en-tête ne sera plus nécessaire
Christoph
vous avez de mauvaises idées. savez-vous que ce qui est portable et multiplateforme parmi les différents compilateurs / versions / architectures? ok, vous ne pouvez pas. alors que signifie réutilisable?
Test
2
lors de l'écriture de C ++, malloc / free n'est PAS la bonne méthodologie. Utilisez plutôt new / delete. IE il ne devrait y avoir aucun appel / nada / zero à malloc / free en code C ++
user3629249
3
@ user3629249: Lors de l' écriture d' une fonction qui doit être utilisable à l' intérieur soit du code C ou le code C, en utilisant malloc/ freepour les deux est susceptible d'être mieux que d' essayer d'utiliser mallocen C et newen C ++, en particulier si les structures de données sont partagées entre C et C ++ code et il est possible qu'un objet soit créé en code C et publié en code C ++ ou vice versa.
supercat
3

Un pointeur void en C peut être assigné à n'importe quel pointeur sans cast explicite. Le compilateur donnera un avertissement mais il peut être réutilisable en C ++ par conversion de type en type malloc()correspondant. Sans moulage de type, il peut également être utilisé en C , car C n'est pas une vérification de type stricte . Mais C ++ est strictement une vérification de type , il est donc nécessaire de taper cast malloc()en C ++.

Yogeesh HT
la source
Si vous utilisez malloc en C ++, vous feriez mieux d'avoir une sacrée bonne raison! ; p
antred