web-dev-qa-db-fra.com

Calloc () peut-il allouer plus que SIZE_MAX au total?

Dans une récente révision du code , il a été affirmé que

Sur certains systèmes, calloc() peut allouer plus de SIZE_MAX octets au total, alors que malloc() est limité.

Je prétends que c'est une erreur, car calloc() crée de l'espace pour un tableau d'objets - qui, étant un tableau, est lui-même un objet. Et aucun objet ne peut avoir une taille plus grande que SIZE_MAX.

Alors lequel de nous est correct? Sur un système (éventuellement hypothétique) avec un espace adresse supérieur à la plage de size_t, calloc() est-il autorisé à réussir lorsqu'il est appelé avec des arguments dont le produit est supérieur à SIZE_MAX?

Pour le rendre plus concret: le programme suivant va-t-il jamais sortir avec un statut non nul?

#include <stdint.h>
#include <stdlib.h>

int main()
{
     return calloc(SIZE_MAX, 2) != NULL;
}
29
Toby Speight

SIZE_MAX ne spécifie pas nécessairement la taille maximale d'un objet, mais plutôt la valeur maximale de size_t, qui n'est pas nécessairement la même chose. Voir Pourquoi la taille maximale d'un tableau est-elle "trop ​​grande"?

Mais évidemment, il n’est pas bien défini de transmettre une valeur supérieure à SIZE_MAX à une fonction qui attend un paramètre size_t. Donc, en théorie, SIZE_MAX est la limite, et en théorie, calloc autoriserait SIZE_MAX * SIZE_MAX octets à allouer.

Le problème avec malloccalloc est qu’ils allouent des objets sans type. Les objets de type ont des restrictions, telles que ne jamais dépasser une certaine limite, telle que SIZE_MAX. Mais les données pointées par le résultat de ces fonctions n'ont pas de type. Ce n'est pas (encore) un tableau.

Formellement, les données n'ont pas de type _/déclaré, mais lorsque vous stockez quelque chose dans les données allouées, il obtient le type effectif de l'accès aux données utilisé pour le stockage (C17 6.5 §6).

Ceci à son tour signifie qu'il serait possible pour calloc d'allouer plus de mémoire que n'importe quel type en C peut en contenir, car ce qui est alloué n'a pas (encore) de type.

Par conséquent, en ce qui concerne le standard C, il est parfaitement correct que calloc(SIZE_MAX, 2) renvoie une valeur différente de NULL. Comment utiliser réellement cette mémoire allouée de manière judicieuse, ou quels systèmes supportant même de tels blocs de mémoire sur le tas, est une autre histoire.

15
Lundin

Calloc () peut-il allouer plus que SIZE_MAX au total?

L'affirmation "Sur certains systèmes, calloc() peut allouer plus de SIZE_MAX total d'octets alors que malloc() est limité." provient d'un commentaire que j'ai posté, je vais expliquer mon raisonnement.


size_t

size_t est du type non signé d'au moins 16 bits.

size_t qui est le type entier non signé du résultat de l'opérateur sizeof; C11dr §7.19 2

"Sa valeur définie pour l'implémentation doit être supérieure ou égale à la magnitude ... de la valeur correspondante indiquée ci-dessous" ... limite de size_tSIZE_MAX ... 65535 §7.20.3 2

taille de

L'opérateur sizeof donne la taille (en octets) de son opérande, qui peut être un expression ou le nom entre parenthèses d'un type. §6.5.3.4 2

calloc

void *calloc(size_t nmemb, size_t size);

La fonction calloc alloue de l'espace pour un tableau d'objets nmemb, chacun de size étant de taille. §7.22.3.2 2


Prenons une situation où nmemb * size dépasse bien SIZE_MAX.

size_t alot = SIZE_MAX/2;
double *p = calloc(alot, sizeof *p); // assume `double` is 8 bytes.

Si calloc() a réellement alloué nmemb * size octets et si p != NULL est vrai, quelle spécification cela at-il violé?

La taille de chaque élément (chaque objet) est représentable.

// Nicely reports the size of a pointer and an element.
printf("sizeof p:%zu, sizeof *p:%zu\n", sizeof p, sizeof *p); 

Chaque élément est accessible.

// Nicely reports the value of an `element` and the address of the element
for (size_t i = 0; i<alot; i++) {
  printf("value a[%zu]:%g, address:%p\n", i, p[i], (void*) &p[i]); 
}

calloc() details

"espace pour un tableau d'objets nmemb": il s'agit certainement d'un point de discorde clé. Est-ce que l'option "alloue de l'espace pour le tableau" requiert <= SIZE_MAX? Je n'ai rien trouvé dans la spécification C d'exiger cette limite et conclu donc:

calloc() peut allouer plus de SIZE_MAX au total.


Il est certainement inhabituel} pour calloc() avec de grands arguments pour renvoyer non -NULL - conforme ou non. Habituellement, ces allocations dépassent la mémoire disponible, le problème est donc sans objet. Le seul cas que j'ai rencontré concernait le modèle de mémoire Huge , où size_t était de 16 bits et le pointeur de l'objet, de 32 bits.

20
chux

Juste un ajout: avec un peu de math, vous pouvez montrer que SIZE_MAX * SIZE_MAX = 1 (lorsqu’il est évalué selon les règles C). 

Toutefois, calloc (SIZE_MAX, SIZE_MAX) n'est autorisé qu'à effectuer l'une des deux opérations suivantes: Renvoyer un pointeur sur un tableau d'éléments SIZE_MAX d'octets SIZE_MAX, OR renvoie NULL. Il n'est PAS permis de calculer la taille totale simplement en multipliant les arguments, en obtenant un résultat de 1 et en allouant un octet, effacé à 0.

2
gnasher729

Si un programme dépasse les limites d'implémentation, le comportement n'est pas défini. Ceci découle de la définition d'une limite de mise en œuvre comme une restriction imposée aux programmes par la mise en œuvre (3.13 en C11). La norme indique également que les programmes strictement conformes doivent respecter les limites d’implémentation (4p5 en C11). Mais cela implique également les programmes en général, car la norme ne dit pas ce qui se passe lorsque la plupart des limites d’implémentation sont dépassées (c’est donc l’autre type de comportement indéfini, dans lequel la norme ne spécifie pas ce qui se passe).

La norme ne définit pas non plus les limites d’implémentation pouvant exister, donc ceci un peu de carte blanche, mais je pense qu’il est raisonnable que la taille maximale de l’objet soit réellement pertinente pour les allocations d’objets. (Soit dit en passant, la taille maximale de l'objet est inférieure à celle de SIZE_MAX, car la différence entre les pointeurs et -char au sein de l'objet doit pouvoir être représentée dans ptrdiff_t.)

Cela nous amène à l'observation suivante: un appel à calloc (SIZE_MAX, 2) dépasse la taille maximale de l'objet; par conséquent, une implémentation peut renvoyer une valeur arbitraire tout en restant conforme à la norme.

Certaines implémentations renverront en fait un pointeur non nul pour un appel tel que calloc (SIZE_MAX / 2 + 2, 2) car l'implémentation ne vérifie pas que le résultat de la multiplication ne correspond pas à une valeur size_t. La question de savoir s'il s'agit d'une bonne idée est une autre affaire, étant donné que la limite d'implémentation peut être vérifiée aussi facilement dans ce cas, et qu'il existe un moyen parfaitement simple de signaler les erreurs. Personnellement, je considère le manque de contrôle de débordement dans calloc comme un bogue d’implémentation, et j’ai signalé les bogues aux développeurs quand je les ai vus, mais techniquement, c’est simplement un problème de qualité d’implémentation.

Pour les tableaux de longueur variable sur la pile, la règle concernant le dépassement des limites d'implémentation entraînant un comportement indéfini est plus évidente:

size_t length = SIZE_MAX / 2 + 2;
short object[length];

Il n'y a vraiment rien qu'une implémentation puisse faire ici, donc ça doit être indéfini.

2
Florian Weimer

Selon le texte de la norme, peut-être, parce que la norme est (certains diraient intentionnellement) vague sur ce genre de chose.

Selon 6.5.3.4 ¶2:

L'opérateur sizeof donne la taille (en octets) de son opérande

et selon 7.19 ¶2:

taille_t

qui est le type entier non signé du résultat de l'opérateur sizeof;

Le premier ne peut pas être satisfait en général si l'implémentation admet n'importe quel type (y compris les types de tableaux) dont la taille n'est pas représentable dans size_t. Notez que, que vous interprétiez ou non le texte relatif au pointeur renvoyé par calloc pointant vers "un tableau", il existe toujours un tableau impliqué dans tout objet: le tableau superposé de type unsigned char[sizeof object] qui correspond à representation.

Au mieux, une implémentation qui permet la création de tout objet plus grand que SIZE_MAX (ou PTRDIFF_MAX, pour d'autres raisons) pose des problèmes de qualité de travail irréprochable. L'affirmation selon laquelle vous devez comptabiliser de telles implémentations sur la révision de code est fausse, à moins que vous ne cherchiez spécifiquement à assurer la compatibilité avec une implémentation C cassée particulière (parfois pertinente pour Embedded, etc.).

2
R..

De

7.22.3.2 La fonction calloc

Synopsis

 #include <stdlib.h>
 void *calloc(size_t nmemb, size_t size);`

La description
2 La fonction calloc alloue de l'espace pour un tableau d'objets nmemb dont la taille correspond à la taille. L'espace est initialisé à tous les bits zéro.

Résultats
3 La fonction calloc renvoie un pointeur null ou un pointeur vers l'espace alloué.

Je ne vois pas pourquoi l'espace alloué devrait être limité à SIZE_MAX octets.

2
Swordfish

La norme ne dit rien sur la possibilité de créer un pointeur de telle sorte que ptr+number1+number2 puisse être un pointeur valide, mais number1+number2 dépasserait SIZE_MAX. Cela permet certainement la possibilité de number1+number2 dépasser PTRDIFF_MAX (bien que pour une raison quelconque, C11 ait décidé d’exiger que même les implémentations avec un espace d’adresse 16 bits utilisent un ptrdiff_t 32 bits).

La norme ne stipule pas que les implémentations fournissent un moyen de créer des pointeurs sur des objets aussi volumineux. Cependant, il définit une fonction, calloc(), dont la description suggère qu'il pourrait être demandé de tenter de créer un tel objet, et suggérerait que calloc() renvoie un pointeur nul s'il ne peut pas créer l'objet.

La possibilité d’attribuer utilement tout type d’objet est toutefois un problème de qualité d’implémentation. La norme n’imposerait jamais le succès d’une demande d’allocation particulière, ni interdirait à une implémentation de renvoyer un pointeur qui pourrait se révéler inutilisable (dans certains environnements Linux, un malloc () pourrait renvoyer un pointeur vers une région de espace adresse; une tentative d'utilisation du pointeur lorsque la mémoire physique disponible est insuffisante peut provoquer un piège fatal). Il serait certainement préférable pour une implémentation non capricieuse de calloc(x,y) de renvoyer null si le produit numérique x et y dépasse SIZE_MAX plutôt que de générer un pointeur qui ne peut pas être utilisé pour accéder à ce nombre d'octets. Toutefois, la norme ne précise pas si le renvoi d'un pointeur pouvant être utilisé pour accéder à des objets y de x octets doit être considéré comme meilleur ou pire que le renvoi de null. Chaque comportement serait avantageux dans certaines situations et défavorable dans d'autres.

0
supercat