271 votes

size_t vs. uintptr_t

La norme C garantit que size_t est un type qui peut contenir n'importe quel indice de tableau. Cela signifie que, logiquement, size_t doit pouvoir contenir n'importe quel type de pointeur. J'ai lu sur certains sites que j'ai trouvés sur Google que cela est légal et/ou devrait toujours fonctionner :

void *v = malloc(10);
size_t s = (size_t) v;

Ainsi, en C99, la norme a introduit la fonction intptr_t et uintptr_t qui sont des types signés et non signés dont la capacité à contenir des pointeurs est garantie :

uintptr_t p = (size_t) v;

Alors quelle est la différence entre utiliser size_t et uintptr_t ? Les deux sont non signés, et les deux devraient pouvoir contenir n'importe quel type de pointeur, donc ils semblent fonctionnellement identiques. Y a-t-il une raison impérieuse d'utiliser uintptr_t (ou mieux encore, un void * ) plutôt qu'un size_t à part la clarté ? Dans une structure opaque, où le champ ne sera traité que par des fonctions internes, y a-t-il une raison de ne pas le faire ?

Dans le même ordre d'idées, ptrdiff_t a été un type signé capable de contenir des différences de pointeurs, et donc capable de contenir la plupart des pointeurs, alors comment se distingue-t-il de intptr_t ?

Tous ces types ne servent-ils pas essentiellement des versions trivialement différentes de la même fonction ? Si ce n'est pas le cas, pourquoi ? Qu'est-ce que je ne peux pas faire avec l'un d'eux que je ne peux pas faire avec un autre ? Si oui, pourquoi C99 a ajouté deux types essentiellement superflus au langage ?

Je suis prêt à ne pas tenir compte des pointeurs de fonction, car ils ne s'appliquent pas au problème actuel, mais n'hésitez pas à les mentionner, car je soupçonne qu'ils seront essentiels à la "bonne" réponse.

259voto

Alex Martelli Points 330805

size_t est un type qui peut contenir n'importe quel indice de tableau. Cela signifie que, logiquement, size_t devrait être capable de contenir n'importe quel type de pointeur

Pas forcément ! Revenons à l'époque des architectures 16 bits segmentées, par exemple : un tableau pouvait être limité à un seul segment (donc un tableau 16 bits size_t ferait l'affaire) MAIS vous pourriez avoir plusieurs segments (donc un segment de 32-bit intptr_t serait nécessaire pour choisir le segment ainsi que le décalage à l'intérieur de celui-ci). Je sais que ces choses semblent bizarres en ces jours d'architectures non segmentées uniformément adressables, mais la norme DOIT répondre à une variété plus large que "ce qui est normal en 2009", vous savez !-)

7 votes

Ceci, ainsi que les nombreux autres qui ont sauté sur la même conclusion, explique la différence entre size_t et uintptr_t mais qu'en est-il ptrdiff_t et intptr_t - Ces deux types ne seraient-ils pas capables de stocker la même plage de valeurs sur presque toutes les plates-formes ? Pourquoi avoir à la fois des types d'entiers signés et non signés de la taille d'un pointeur, particulièrement si ptrdiff_t remplit déjà l'objectif d'un type d'entier signé de la taille d'un pointeur.

9 votes

L'expression clé est "sur presque n'importe quelle plateforme", @Chris. Une implémentation est libre de restreindre les pointeurs à la plage 0xf000-0xffff - cela nécessite un intptr_t de 16 bits mais seulement un ptrdiff_t de 12/13 bits.

35 votes

@Chris, seulement pour les pointeurs dans le même tableau est bien défini pour prendre leur différence. Ainsi, sur exactement les mêmes architectures 16 bits segmentées (le tableau doit vivre à l'intérieur d'un seul segment mais deux tableaux différents peuvent être dans des segments différents) les pointeurs doivent être de 4 octets mais le pointeur différences pourrait être de 2 octets !

94voto

paxdiablo Points 341644

En ce qui concerne votre déclaration :

"La norme C garantit que size_t est un type qui peut contenir n'importe quel indice de tableau. Cela signifie que, logiquement, size_t devrait pouvoir contenir n'importe quel type de pointeur."

Il s'agit en fait d'un sophisme (une idée fausse résultant d'un raisonnement incorrect). (a) . Vous pouvez pensez à le second découle du premier, mais ce n'est pas vraiment le cas.

Les pointeurs et les index de tableaux sont pas la même chose. Il est tout à fait plausible d'envisager une mise en œuvre conforme qui limite les tableaux à 65536 éléments mais autorise les pointeurs à adresser n'importe quelle valeur dans un espace d'adressage massif de 128 bits.

La norme C99 stipule que la limite supérieure d'un size_t La variable est définie par SIZE_MAX et cette valeur peut être aussi basse que 65535 (voir C99 TR3, 7.18.3, inchangé en C11). Les pointeurs seraient assez limités s'ils étaient restreints à cette plage dans les systèmes modernes.

En pratique, vous constaterez probablement que votre hypothèse se vérifie, mais ce n'est pas parce que la norme le garantit. Parce qu'elle n'a pas le garantir.


(a) C'est pas aucune forme d'attaque personnelle d'ailleurs, je ne fais qu'indiquer pourquoi vos déclarations sont erronées dans le contexte de la pensée critique. Par exemple, le raisonnement suivant est également invalide :

Tous les chiots sont mignons. Cette chose est mignonne. Donc cette chose doit être un chiot.

Le caractère mignon ou non des chiots n'a pas d'importance ici, tout ce que je dis c'est que les deux faits ne mènent pas à la conclusion, parce que les deux premières phrases permettent l'existence de choses mignonnes qui sont pas des chiots.

Cela va dans le sens de votre première déclaration, sans nécessairement imposer la seconde.

0 votes

Plutôt que de répéter ce que j'ai dit dans les commentaires pour Alex Martelli, je vais simplement dire merci pour la clarification, mais réitérer la seconde partie de ma question (la ptrdiff_t vs. intptr_t partie).

0 votes

Se moquer de l'OP n'est pas mon idée d'une réponse utile.

7 votes

Ivan, comme pour la plupart des communications, il doit y avoir une compréhension commune de certains éléments de base. Si vous considérez cette réponse comme une "plaisanterie", je vous assure que c'est une mauvaise compréhension de mon intention. En supposant que vous fassiez référence à mon commentaire sur le "sophisme logique" (je ne vois pas d'autre possibilité), il s'agissait d'une déclaration factuelle, et non d'une déclaration faite aux dépens du PO. Si vous souhaitez suggérer des béton amélioration pour minimiser la possibilité de malentendu (plutôt qu'une simple plainte générale), je serais heureux de l'envisager.

44voto

unwind Points 181987

Je laisserai toutes les autres réponses se passer d'explications sur les limitations de segment, les architectures exotiques, etc.

N'est-ce pas le simple différence de noms une raison suffisante pour utiliser le bon type pour la bonne chose ?

Si vous stockez une taille, utilisez size_t . Si vous stockez un pointeur, utilisez intptr_t . Une personne lisant votre code saura instantanément que "aha, ceci est une taille de quelque chose, probablement en octets", et "oh, voici une valeur de pointeur stockée comme un entier, pour une raison quelconque".

Sinon, vous pouvez simplement utiliser unsigned long (ou, en ces temps modernes, unsigned long long ) pour tout. La taille n'est pas tout, les noms de types ont une signification qui est utile puisqu'elle aide à décrire le programme.

0 votes

Je suis d'accord, mais j'envisageais une sorte de hack/trick (que je documenterais clairement, bien sûr) impliquant le stockage d'un type de pointeur dans un fichier de type size_t champ.

1 votes

La norme @MarkAdler n'exige pas que les pointeurs soient représentables sous forme d'entiers : Tout type de pointeur peut être converti en un type entier. Sauf indication contraire, le résultat est défini par l'implémentation. Si le résultat ne peut pas être représenté dans le type entier, le comportement est indéfini. Le résultat ne doit pas nécessairement se trouver dans la plage de valeurs d'un type entier. Ainsi, seuls void* , intptr_t et uintptr_t sont garantis de pouvoir représenter n'importe quel pointeur vers des données.

0 votes

C'est une pensée excessivement naïve. Par exemple, lorsque vous devez aligner des champs de structures génériques, le choix entre size_t et pointeurs peut être erroné. Il faut alors utiliser uintptr_t, car lui seul garantit le même alignement et le même décalage.

14voto

Michael Burr Points 181287

Il est possible que la taille du plus grand tableau soit plus petite qu'un pointeur. Pensez aux architectures segmentées - les pointeurs peuvent être de 32 bits, mais un seul segment peut être capable d'adresser seulement 64 Ko (par exemple l'ancienne architecture 8086 en mode réel).

Bien que ces derniers ne soient plus couramment utilisés dans les ordinateurs de bureau, la norme C est destinée à prendre en charge même les petites architectures spécialisées. Il existe encore des systèmes embarqués développés avec des CPU de 8 ou 16 bits, par exemple.

0 votes

Mais on peut indexer les pointeurs comme des tableaux, donc il faudrait size_t pourrait aussi être capable de gérer cela ? Ou bien les tableaux dynamiques dans un segment éloigné seraient-ils toujours limités à l'indexation dans leur segment ?

0 votes

L'indexation des pointeurs n'est techniquement supportée que jusqu'à la taille du tableau vers lequel ils pointent - donc si un tableau est limité à une taille de 64KB, c'est tout ce que l'arithmétique des pointeurs doit supporter. Cependant, les compilateurs MS-DOS supportaient un modèle de mémoire "énorme", dans lequel les pointeurs distants (pointeurs segmentés 32 bits) étaient manipulés de manière à pouvoir adresser l'ensemble de la mémoire comme un seul tableau - mais l'arithmétique appliquée aux pointeurs en coulisses était plutôt moche - lorsque l'offset dépassait une valeur de 16 (ou quelque chose comme ça), l'offset revenait à 0 et la partie segment était incrémentée.

8 votes

Lire fr.wikipedia.org/wiki/C_memory_model#Memory_segmentation et pleurer pour les programmeurs MS-DOS qui sont morts pour que nous puissions être libres.

6voto

dreamlax Points 47152

J'imagine (et cela vaut pour tous les noms de types) que cela permet de mieux transmettre vos intentions dans le code.

Par exemple, même si unsigned short et wchar_t sont de la même taille sous Windows (je pense), en utilisant wchar_t au lieu de unsigned short montre l'intention de l'utiliser pour stocker un caractère large, plutôt qu'un nombre arbitraire.

0 votes

Mais il y a une différence ici - sur mon système, wchar_t est beaucoup plus grande qu'une unsigned short donc utiliser l'un pour l'autre serait erroné et créerait un sérieux (et moderne) problème de portabilité, alors que les problèmes de portabilité entre size_t et uintptr_t semblent se situer dans les terres lointaines de 1980-quelque chose (coup de poignard aléatoire dans le noir sur la date, là)

0 votes

Touché ! Mais encore une fois, size_t et uintptr_t ont toujours des utilisations implicites dans leurs noms.

0 votes

Ils le font, et je voulais savoir s'il y avait une motivation pour cela au-delà de la simple clarté. Et il s'avère qu'il y en a une.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X