Ne malloc() alloue un bloc contigu de mémoire?
j'ai un morceau de code écrit par un programmeur de très vieille école :-) . il va quelque chose comme ça
typedef struct ts_request
{
ts_request_buffer_header_def header;
char package[1];
} ts_request_def;
ts_request_def* request_buffer =
malloc(sizeof(ts_request_def) + (2 * 1024 * 1024));
le programmeur travaille essentiellement sur un concept de débordement de tampon. Je sais que le code semble douteux. donc mes questions sont:
-
malloc alloue-t-il toujours un bloc de mémoire contigu ?. parce que dans ce code si les blocs ne sont pas contigus , le code échouera grand temps
-
faisant free(request_buffer) , va-t-il libérer tous les octets alloués par malloc I. E size of (ts_request_def) + (2 * 1024 * 1024), ou seulement les octets de la taille de la structure sizeof(ts_request_def)
-
voyez-vous des problèmes évidents avec cette approche , je dois en discuter avec mon patron et je voudrais souligner des failles avec cette approche
14 réponses
pour répondre à vos points numérotés.
- Oui.
- tous les octets. Malloc / free ne sait pas ou ne se soucie pas du type d'objet, juste de sa taille.
- il s'agit à proprement parler d'un comportement indéfini, mais un truc courant soutenu par de nombreuses implémentations. Voir ci-dessous pour d'autres alternatives.
la dernière norme C, ISO/IEC 9899:1999 (officieusement C99), permet flexible membres du tableau .
un exemple serait:
int main(void)
{
struct { size_t x; char a[]; } *p;
p = malloc(sizeof *p + 100);
if (p)
{
/* You can now access up to p->a[99] safely */
}
}
cette fonctionnalité Maintenant standardisée vous a permis d'éviter d'utiliser l'extension de mise en œuvre commune, mais non standard, que vous décrivez dans votre question. À proprement parler, utiliser un membre de tableau non flexible et accéder au-delà de ses limites est un comportement non défini, mais de nombreuses implémentations le documentent et l'encouragent.
en outre, gcc permet zéro-tableaux de longueur comme une extension. Les tableaux de longueur zéro sont illégaux dans la norme C, mais gcc a introduit cette fonctionnalité avant que C99 nous donne des membres de tableau flexibles.
en réponse à un commentaire, je vais expliquer pourquoi l'extrait ci-dessous est un comportement techniquement non défini. Les numéros de Section I renvoient à C99 (ISO / IEC 9899: 1999)
struct {
char arr[1];
} *x;
x = malloc(sizeof *x + 1024);
x->arr[23] = 42;
premièrement, le 6.5.2.1#2 montre que a[i] est identique à (*(A)+(i)), donc x->arr[23] est équivalent à (*((x->arr)+(23))). Maintenant, 6.5.6#8 (sur l'ajout d'un pointeur et un entier) dit:
" si l'opérande de pointeur et le résultat pointent vers des éléments du même objet array, ou un autre après le dernier élément de l'objet array, l'évaluation ne doit pas produire de débordement; sinon, le comportement est non défini ."
pour cette raison, parce que x - > arr[23] n'est pas dans le tableau, le comportement est indéfini. Vous pouvez toujours penser que c'est correct parce que le malloc() implique que le tableau a été prolongé, mais ce n'est pas strictement le cas. L'annexe Informative J. 2 (qui énumère des exemples de comportements non définis) fournit une clarification supplémentaire avec un exemple:
Un indice de tableau est hors de portée, même si un objet est apparemment accessible avec le Indice donné (comme dans l'expression lvalue a[1] [7] étant donné la déclaration int un[4][5]) (6.5.6).
3-c'est un truc C assez courant pour allouer un tableau dynamique à la fin d'une structure. L'alternative serait de mettre un pointeur dans la structure et ensuite d'allouer le tableau séparément, sans oublier de le libérer aussi. Que la taille est fixée à 2 mo semble un peu inhabituel.
1) oui, ou malloc échouera s'il n'y a pas un bloc contigu assez grand disponible. (Un échec avec malloc retournera un pointeur nul)
2) Oui. L'allocation de mémoire interne conservera la trace de la quantité de mémoire allouée avec cette valeur de pointeur et libérera tout cela.
3) c'est un peu un piratage de langue, et un peu douteux sur son utilisation. Il est encore sujet à des débordements de tampon aussi bien, juste peut prendre des attaquants légèrement plus long pour trouver une charge utile qui vont le faire. Le coût de la 'protection' est également assez élevé (avez-vous vraiment besoin d'un tampon >2MB par requête?). Il est aussi très laid, Bien que votre patron ne peut pas apprécier cet argument :)
c'est un truc C standard, et n'est pas plus dangereux que n'importe quel autre tampon.
si vous essayez de montrer à votre patron que vous êtes plus intelligent que" programmeur de très vieille école", ce code n'est pas un cas pour vous. La vieille école n'est pas forcément mauvaise. Semble la "vieille école" guy en sait assez sur la gestion de la mémoire ;)
Je ne pense pas que les réponses existantes atteignent tout à fait l'essence de cette question. Vous dites que le programmeur de la vieille école fait quelque chose comme ça;
typedef struct ts_request
{
ts_request_buffer_header_def header;
char package[1];
} ts_request_def;
ts_request_buffer_def* request_buffer =
malloc(sizeof(ts_request_def) + (2 * 1024 * 1024));
je pense qu'il est peu probable qu'il fasse exactement cela, parce que si c'est ce qu'il voulait faire, il pourrait le faire avec un code équivalent simplifié qui n'a pas besoin d'astuces;
typedef struct ts_request
{
ts_request_buffer_header_def header;
char package[2*1024*1024 + 1];
} ts_request_def;
ts_request_buffer_def* request_buffer =
malloc(sizeof(ts_request_def));
je parie que ce qu'il fait vraiment est quelque chose comme ça;
typedef struct ts_request
{
ts_request_buffer_header_def header;
char package[1]; // effectively package[x]
} ts_request_def;
ts_request_buffer_def* request_buffer =
malloc( sizeof(ts_request_def) + x );
ce qu'il veut réaliser est l'attribution d'une requête avec une taille de paquet variable X. Il est bien sûr illégal de déclarer la taille du tableau avec une variable, donc il s'en sort avec une astuce. On dirait qu'il sait ce qu'il me fait, le truc est bien vers l'extrémité respectable et pratique de l'échelle de la tromperie.
comme pour #3, sans plus de code, il est difficile de répondre. Je ne vois rien de mal à ça, à moins que ça arrive souvent. Je veux dire, vous ne voulez pas allouer 2MB morceaux de mémoire tout le temps. Vous ne voulez pas non plus le faire inutilement, par exemple si vous n'utilisez que 2k.
Le fait que vous n'aimez pas pour une raison quelconque, n'est pas suffisant pour objet, ou de justifier complètement ré-écrit. Je regarde l'utilisation de près, essayer de comprendre ce que l'original programmeur pensait, regardez de près pour les débordements de tampon (comme workmad3 a souligné) dans le code qui utilise cette mémoire.
Il y a beaucoup d'erreurs courantes que vous pouvez trouver. Par exemple, est-ce que le code est vérifié pour s'assurer que malloc() a réussi?
l'exploit (question 3) est vraiment à l'interface avec votre structure. Dans le contexte, cette affectation pourrait avoir du sens, et sans plus d'information, il est impossible de dire si elle est Sécuritaire ou non.
Mais si vous voulez dire des problèmes avec l'attribution de la mémoire plus grand que la structure, ce n'est pas du tout un mauvais c design (Je ne dirais même pas que c'est que l'ancienne école... ;))
Juste une dernière remarque ici - le point avec un char[1] est que la terminaison NULL sera toujours dans la structure déclarée, ce qui signifie qu'il peut y avoir 2 * 1024 * 1024 les caractères dans le tampon, et vous n'avez pas à rendre compte de la valeur nulle par un "+1". Qui pourrait ressembler à un petit exploit, mais je voulais juste faire remarquer.
j'ai vu et utilisé ce modèle fréquemment.
son avantage est de simplifier la gestion de la mémoire et d'éviter ainsi les risques de fuites de mémoire. Il suffit de libérer le bloc malloc'ed. Avec un tampon secondaire, vous aurez besoin de deux Free. Toutefois, il faut définir et utiliser une fonction de destructeur pour encapsuler cette opération afin que vous puissiez toujours changer son comportement, comme passer à un tampon secondaire ou ajouter des opérations supplémentaires à effectuer lors de la suppression de la structure.
L'accès aux éléments de réseaux est aussi un peu plus efficace, mais c'est de moins en moins important avec les ordinateurs modernes.
le code fonctionnera aussi correctement si l'alignement de la mémoire change dans la structure avec différents compilateurs car il est assez fréquent.
le seul problème potentiel que je vois est si le compilateur Permute l'ordre de stockage des variables membres parce que cette astuce exige que le champ du paquet reste le dernier en l'espace de stockage. Je ne sais pas si la norme C interdit la permutation.
Notez aussi que la taille du tampon alloué sera très probablement plus grande que nécessaire, au moins d'un octet avec les octets de remplissage supplémentaires s'il y en a.
Oui. malloc ne renvoie qu'un seul pointeur - comment pourrait-il dire à un demandeur qu'il a attribué plusieurs blocs discontiguous pour répondre à une demande?
voudrait ajouter que ce n'est pas commun mais je pourrais également appelé une pratique standard parce que L'API Windows est pleine d'une telle utilisation.
Vérifiez la structure D'en-tête BITMAP très courante par exemple.
http://msdn.microsoft.com/en-us/library/aa921550.aspx
le dernier quad RBG est un tableau de 1 taille, qui dépend exactement de cette technique.
cette astuce courante en C est aussi expliquée dans cette question de débordement de piles (quelqu'un peut-il expliquer cette définition de la structure de sable dans solaris?) .
En réponse à votre troisième question.
free
libère toujours toute la mémoire allouée à un seul coup.
int* i = (int*) malloc(1024*2);
free(i+1024); // gives error because the pointer 'i' is offset
free(i); // releases all the 2KB memory
la réponse aux questions 1 et 2 est oui.
à propos de la laideur (question 3) qu'est-ce que le programmeur essaie de faire avec cette mémoire allouée?
la chose à réaliser ici est que malloc
ne voit pas le calcul fait dans ce
malloc(sizeof(ts_request_def) + (2 * 1024 * 1024));
c'est la même que
int sz = sizeof(ts_request_def) + (2 * 1024 * 1024);
malloc(sz);
vous pourriez penser que son attribution 2 morceaux de mémoire , et dans l'esprit de yr ils sont "la structure", "quelques tampons". Mais malloc ne le voit pas du tout.