La norme C garantit que size_t
est un type pouvant contenir n’importe quel index de tableau. Cela signifie que, logiquement, size_t
devrait pouvoir contenir n'importe quel type de pointeur. J'ai lu sur certains sites trouvés sur Google que cela est légal et/ou devrait toujours fonctionner:
void *v = malloc(10);
size_t s = (size_t) v;
Alors, en C99, la norme introduit le intptr_t
et uintptr_t
types, qui sont des types signés et non signés capables de contenir des pointeurs:
uintptr_t p = (size_t) v;
Alors, quelle est la différence entre utiliser size_t
et uintptr_t
? Les deux ne sont pas signés et devraient pouvoir contenir n'importe quel type de pointeur, de sorte qu'ils semblent fonctionnellement identiques. Existe-t-il une vraie raison impérieuse d'utiliser uintptr_t
(ou mieux encore, un void *
) Plutôt qu'un size_t
, autre que la clarté? Dans une structure opaque, où le champ ne sera traité que par des fonctions internes, y a-t-il une raison pour ne pas le faire?
Par la même occasion, ptrdiff_t
est un type signé capable de contenir des différences de pointeur, et donc capable de contenir la plupart des points, de quelle manière est-il différent de intptr_t
?
Tous ces types ne servent-ils pas fondamentalement différentes versions d'une même fonction? Sinon 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 tel est le cas, pourquoi C99 a-t-il ajouté deux types essentiellement superflus au langage?
Je suis prêt à ignorer les indicateurs de fonction, car ils ne s'appliquent pas au problème actuel, mais n'hésitez pas à les mentionner, car je soupçonne furtivement qu'ils seront au cœur de la réponse "correcte".
size_t
Est un type pouvant contenir n'importe quel index de tableau. Cela signifie que, logiquement, size_t devrait pouvoir contenir n'importe quel type de pointeur
Pas nécessairement! Remontez au temps des architectures 16 bits segmentées, par exemple: un tableau peut être limité à un seul segment (comme le ferait un size_t
16 bits) MAIS vous pourriez avoir plusieurs segments (donc un 32 bits) Le type intptr_t
Serait nécessaire pour choisir le segment et le décalage qu’il contient). Je sais que ces choses semblent bizarres de nos jours d'architectures non segmentées uniformément adressables, mais la norme DOIT prendre en charge une variété plus large que "ce qui est normal en 2009", vous savez! -)
En ce qui concerne votre déclaration:
"La norme C garantit que
size_t
est un type pouvant contenir n’importe quel index de tableau. Cela signifie que, logiquement,size_t
devrait pouvoir contenir n’importe quel type de pointeur. "
C'est en réalité une erreur (une idée fausse résultant d'un raisonnement incorrect)(une). Vous pouvez penser le dernier découle du précédent, mais ce n'est pas le cas.
Les pointeurs et les index de tableaux sont pas la même chose. Il est tout à fait plausible d’envisager une implémentation conforme qui limite les tableaux à 65 536 éléments, mais permet aux pointeurs d’adresser toute valeur dans un espace d’adresses gigantesque de 128 bits.
C99 stipule que la limite supérieure d'un size_t
La variable est définie par SIZE_MAX
et cela peut être aussi bas que 65535 (voir C99 TR3, 7.18.3, inchangé en C11). Les pointeurs seraient assez limités s’ils étaient limités à cette plage dans les systèmes modernes.
En pratique, vous constaterez probablement que votre hypothèse est valable, mais ce n'est pas parce que la norme le garantit. Parce que cela ne le garantit pas .
(une) Ceci n'est pas une forme d'attaque personnelle, mais explique simplement 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. Par conséquent, cette chose doit être un chiot.
La gentillesse ou non des chiots n'a aucune incidence ici, tout ce que je dis, c'est que les deux faits ne mènent pas à la conclusion, car les deux premières phrases permettent l'existence de choses mignonnes qui sont pas chiots.
Ceci est similaire à votre première déclaration ne nécessitant pas nécessairement la seconde.
Je laisserai toutes les autres réponses se faire d'elles-mêmes en ce qui concerne le raisonnement avec les limitations de segment, les architectures exotiques, etc.
La simple différence de noms n'est-elle pas une raison suffisante pour utiliser le type approprié?
Si vous enregistrez une taille, utilisez size_t
. Si vous enregistrez un pointeur, utilisez intptr_t
. Une personne lisant votre code saura instantanément que "aha, il s'agit d'une taille de quelque chose, probablement en octets", et "oh, voici une valeur de pointeur stockée sous forme d'entier, pour une raison quelconque".
Sinon, vous pouvez simplement utiliser unsigned long
(ou, en ces temps modernes, unsigned long long
) pour tout. La taille ne fait pas tout, les noms de types ont une signification, ce qui est utile car cela aide à décrire le programme.
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 en 32 bits, mais un seul segment peut ne traiter que 64 Ko (par exemple l'ancienne architecture 8086 en mode réel).
Bien que ceux-ci ne soient plus couramment utilisés sur les ordinateurs de bureau, la norme C est conçue pour prendre en charge même les plus petites architectures spécialisées. Il existe toujours des systèmes embarqués en cours de développement avec des processeurs 8 ou 16 bits par exemple.
J'imagine (et cela s'applique à tous les noms de types) qu'il traduit mieux vos intentions en code.
Par exemple, même si unsigned short
et wchar_t
ont 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 simple nombre arbitraire.
En regardant à la fois en arrière et en avant, et en rappelant que diverses architectures bizarres ont été dispersées dans le paysage, je suis à peu près sûr qu'elles essayaient d'envelopper tous les systèmes existants et de prévoir tous les systèmes futurs possibles.
Donc, bien sûr, la façon dont les choses se sont arrangées, nous n’avons jusqu’à présent pas besoin de tant de types.
Mais même dans LP64, un paradigme plutôt commun, nous avions besoin de size_t et de ssize_t pour l'interface d'appel système. On peut imaginer un système hérité ou futur plus contraint, où l’utilisation d’un type 64 bits complet est coûteuse et qu’ils voudront peut-être s’aplanir sur les opérations d’E/S supérieures à 4 Go tout en conservant des pointeurs 64 bits.
Je pense que vous devez vous demander: ce qui aurait pu être développé, ce qui pourrait arriver dans le futur. (Peut-être des pointeurs Internet à l'échelle de systèmes distribués 128 bits, mais pas plus de 64 bits dans un appel système, ou peut-être même une limite 32 bits "héritée". :-) Image que les systèmes hérités pourraient recevoir de nouveaux compilateurs C. .
Aussi, regardez ce qui existait à l'époque. Outre les modèles de mémoire en mode réel zillion 286, qu’en est-il des unités centrales de pointeur CDC Word/18 bits de 60 bits? Que diriez-vous de la série Cray? Ne faites pas attention à ILP64, LP64, LLP64 normal. (J'ai toujours pensé que Microsoft était prétentieux avec LLP64, cela aurait dû être P64.) Je peux certainement imaginer un comité essayant de couvrir toutes les bases ...