Malloc () alloue-t-il un bloc de mémoire contigu?

J’ai un morceau de code écrit par un très vieux programmeur d’école :-). Ca fait plutot comme ca

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épassement de tampon. Je sais que le code a l’air louche. donc mes questions sont:

  1. Malloc alloue-t-il toujours des blocs de mémoire contigus? car dans ce code si les blocs ne sont pas contigus, le code échouera gros

  2. Faire free (request_buffer) libérera tous les octets alloués par malloc ie sizeof (ts_request_def) + (2 * 1024 * 1024), ou seulement les octets de la taille de la structure sizeof (ts_request_def)

  3. Voyez-vous des problèmes évidents avec cette approche, je dois en discuter avec mon patron et je voudrais signaler toute lacune avec cette approche

Pour répondre à vos points numérotés.

  1. Oui.
  2. Tous les octets. Malloc / free ne connaît ni le type de l’object, ni sa taille.
  3. C’est à proprement parler un comportement indéfini, mais une astuce courante supscope par de nombreuses implémentations. Voir ci-dessous pour d’autres alternatives.

La dernière norme C, ISO / IEC 9899: 1999 (officieusement C99), autorise les éléments de masortingce flexibles .

Un exemple de ceci 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é désormais normalisée vous a permis d’éviter d’utiliser l’extension d’implémentation commune, mais non standard, que vous décrivez dans votre question. À proprement parler, utiliser un membre de groupe non flexible et accéder au-delà de ses limites est un comportement indéfini, mais de nombreuses implémentations la documentent et l’encouragent.

De plus, gcc autorise les tableaux de longueur nulle comme extension. Les tableaux de longueur nulle sont illégaux en C standard, mais gcc a introduit cette fonctionnalité avant que C99 ne nous fournisse des membres de tableau flexibles.

En réponse à un commentaire, je vais expliquer pourquoi l’extrait de code ci-dessous est un comportement techniquement indéfini. Les numéros de section que je cite se réfèrent à C99 (ISO / IEC 9899: 1999)

 struct { char arr[1]; } *x; x = malloc(sizeof *x + 1024); x->arr[23] = 42; 

Premièrement, 6.5.2.1 # 2 montre que [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 d’un entier) dit:

“Si l’opérande de pointeur et le résultat pointent vers des éléments du même object de tableau, ou d’un élément après le dernier élément de l’object de tableau, l’évaluation ne produira pas de dépassement de capacité; sinon, le comportement n’est pas défini .”

Pour cette raison, étant donné que x-> arr [23] ne fait pas partie du tableau, le comportement est indéfini. Vous pouvez toujours penser que c’est bon parce que malloc () implique que le tableau a maintenant été étendu, mais ce n’est pas ssortingctement le cas. L’annexe informative J.2 (qui répertorie des exemples de comportement non défini) fournit des éclaircissements supplémentaires avec un exemple:

Un indice de tableau est en dehors de la plage, même si un object est apparemment accessible avec l’indice donné (comme dans l’expression lvalue a [1] [7] étant donné la déclaration int a [4] [5]) (6.5.6).

3 – C’est une astuce assez commune pour atsortingbuer un tableau dynamic à la fin d’une structure. L’alternative serait de placer un pointeur dans la structure, puis d’allouer le tableau séparément, sans oublier de le libérer également. Que la taille soit fixée à 2 Mo semble un peu inhabituel cependant.

1) Oui, ou malloc échouera s’il n’y a pas un bloc contigu suffisamment grand disponible. (Un échec avec malloc retournera un pointeur NULL)

2) Oui ça va. L’allocation de mémoire interne garde la trace de la quantité de mémoire allouée avec cette valeur de pointeur et la libère entièrement.

3) C’est un peu un hack de langage et un peu douteux quant à son utilisation. Il rest également sujet à des débordements de mémoire tampon, mais il faudra peut-être un peu plus de temps aux attaquants pour trouver la charge utile qui le provoquera. Le coût de la «protection» est également très élevé (avez-vous vraiment besoin de plus de 2 Mo par tampon de requête?). C’est aussi très moche, même si votre patron n’appréciera peut-être pas cet argument 🙂

C’est une astuce standard en C, et n’est pas plus dangereux qu’un autre tampon.

Si vous essayez de montrer à votre patron que vous êtes plus intelligent que “très vieux programmeur d’école”, ce code n’est pas un cas pour vous. La vieille école n’est pas nécessairement mauvaise. On dirait que le gars “old school” en sait assez sur la gestion de la mémoire;)

Je ne pense pas que les réponses existantes vont tout à fait dans le sens de ce problème. 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 le fasse exactement, car si c’est ce qu’il voulait faire, il pourrait le faire avec un code équivalent simplifié qui ne nécessite aucune astuce;

 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, c’est allouer une requête avec une taille de paquet variable x. Il est bien entendu illégal de déclarer la taille du tableau avec une variable, alors il contourne cela avec un truc. On dirait qu’il sait ce qu’il me fait, le truc va bien vers la fin respectable et pratique de la ruse en C.

Quant à # 3, sans plus de code, il est difficile de répondre. Je ne vois rien de mal à cela, à moins que cela n’arrive souvent. Je veux dire, vous ne voulez pas allouer des morceaux de mémoire 2mb tout le temps. Vous ne voulez pas non plus le faire inutilement, par exemple si vous utilisez uniquement 2k.

Le fait que vous n’aimiez pas cela pour une raison quelconque ne suffit pas pour vous y opposer ou pour justifier une réécriture complète. J’examinerais de près l’utilisation, essayerais de comprendre ce que pensait le programmeur d’origine, de près les débordements de mémoire tampon (comme l’a fait remarquer workmad3) dans le code qui utilise cette mémoire.

Vous pouvez trouver de nombreuses erreurs courantes. Par exemple, le code vérifie-t-il que malloc () a réussi?

L’exploit (question 3) est vraiment à la hauteur de l’interface vers cette structure. Dans le contexte, cette atsortingbution peut avoir un sens, et sans informations complémentaires, il est impossible de dire si elle est sécurisée ou non.
Mais si vous parlez de problèmes pour allouer de la mémoire plus grande que la structure, ceci n’est en aucun cas un mauvais design en C (je ne dirais même pas qu’il s’agit de la vieille école …;))
Juste une dernière remarque ici – le point avec un caractère [1] est que la valeur NULL de fin sera toujours dans la structure déclarée, ce qui signifie qu’il peut y avoir 2 * 1024 * 1024 caractères dans le tampon, et vous n’avez pas à rendre compte pour la valeur NULL par un “+1”. Cela pourrait ressembler à un petit exploit, mais je voulais simplement le souligner.

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 le risque de fuite de mémoire. Il suffit de libérer le bloc mallocé. Avec un tampon secondaire, vous aurez besoin de deux gratuits. Cependant, il convient de définir et d’utiliser une fonction de destructeur pour encapsuler cette opération afin que vous puissiez toujours modifier son comportement, comme pour passer à la mémoire tampon secondaire ou append des opérations supplémentaires à effectuer lors de la suppression de la structure.

L’access aux éléments du tableau est également légèrement plus efficace, mais il est de moins en moins important avec les ordinateurs modernes.

Le code fonctionnera également 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, car cette astuce nécessite que le champ de package rest le dernier dans le stockage. Je ne sais pas si la norme C interdit la permutation.

Notez également que la taille de la mémoire tampon allouée sera très probablement supérieure à celle requirejse, au moins d’un octet avec les octets de remplissage supplémentaires éventuels.

Oui. malloc ne renvoie qu’un seul pointeur. Comment pourrait-il indiquer à un demandeur qu’il a alloué plusieurs blocs non contigus pour répondre à une demande?

J’aimerais append que ce n’est pas courant, mais je pourrais aussi l’appeler une pratique standard parce que l’API Windows est pleine d’une telle utilisation.

Vérifiez la structure très commune des en-têtes BITMAP, 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 commune du C est également expliquée dans cette question de StackOverflow (Quelqu’un peut-il expliquer cette définition de la structure dirent dans solaris?) .

En réponse à votre troisième question.

free libère toujours toute la mémoire allouée d’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 être fait dans ce

 malloc(sizeof(ts_request_def) + (2 * 1024 * 1024)); 

C’est la même chose que

  int sz = sizeof(ts_request_def) + (2 * 1024 * 1024); malloc(sz); 

Vous pensez peut-être qu’il alloue 2 blocs de mémoire et, dans l’esprit, il s’agit de «la structure», de «certains tampons». Mais malloc ne voit pas ça du tout.