size_t contra uintptr_t

El estándar C garantiza que size_t es un tipo que puede contener cualquier índice de matriz. Esto significa que, lógicamente, size_t debería poder contener cualquier tipo de puntero. He leído en algunos sitios que encontré en Google que esto es legal y / o siempre debería funcionar:

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

Entonces, en C99, el estándar introdujo los tipos intptr_t y uintptr_t , que son tipos firmados y sin firmar que garantizan poder contener punteros:

 uintptr_t p = (size_t) v; 

Entonces, ¿cuál es la diferencia entre usar size_t y uintptr_t ? Ambos no tienen firma, y ​​ambos deberían ser capaces de contener cualquier tipo de puntero, por lo que parecen funcionalmente idénticos. ¿Hay alguna razón convincente para usar uintptr_t (o mejor aún, un void * ) en lugar de un size_t , aparte de la claridad? En una estructura opaca, donde el campo será manejado solo por funciones internas, ¿hay alguna razón para no hacer esto?

Por la misma razón, ptrdiff_t ha sido un tipo firmado capaz de contener las diferencias de puntero, y por lo tanto es capaz de contener la mayoría de los punteros, entonces, ¿cómo es distinto de intptr_t ?

¿No están todos estos tipos básicamente sirviendo versiones trivialmente diferentes de la misma función? Si no, ¿por qué? ¿Qué no puedo hacer con uno de ellos que no puedo hacer con otro? Si es así, ¿por qué C99 agregó dos tipos esencialmente superfluos al lenguaje?

Estoy dispuesto a ignorar los indicadores de función, ya que no se aplican al problema actual, pero siéntete libre de mencionarlos, ya que tengo la sospecha de que serán fundamentales para la respuesta “correcta”.

size_t es un tipo que puede contener cualquier índice de matriz. Esto significa que, lógicamente, size_t debería poder contener cualquier tipo de puntero

¡No necesariamente! Retrocedamos a los días de las architectures segmentadas de 16 bits, por ejemplo: una matriz podría estar limitada a un solo segmento (por lo que una size_t 16 bits sí) PERO podría tener múltiples segmentos (por lo que se necesitaría un tipo intptr_t 32 bits intptr_t para elegir el segmento así como el desplazamiento dentro de él). Sé que estas cosas suenan raras en estos días de architectures no segmentadas, direccionables uniformemente, pero el estándar DEBE proporcionar una variedad más amplia que “lo que es normal en 2009”, ¿sabes? -)

En cuanto a su statement:

“El estándar C garantiza que size_t es un tipo que puede contener cualquier índice de matriz. Esto significa que, lógicamente, size_t debería poder contener cualquier tipo de puntero”.

Esto se llama una falacia, una idea errónea que resulta de un razonamiento incorrecto. Puede pensar que esto último se sigue del primero, pero eso no es necesariamente así.

Los punteros y los índices de matriz no son lo mismo. Es bastante plausible prever una implementación conforme que limite las matrices a 65536 elementos pero permita que los punteros aborden cualquier valor en un espacio de direcciones masivas de 128 bits.

C99 establece que el límite superior de una variable size_t está definido por SIZE_MAX y puede ser tan bajo como 65535 (ver C99 TR3, 7.18.3, sin cambios en C11). Los punteros serían bastante limitados si estuvieran restringidos a este rango en los sistemas modernos.

En la práctica, probablemente encontrará que su suposición es válida, pero no es porque el estándar lo garantice. Porque en realidad no lo garantiza.

Dejaré que todas las otras respuestas representen el razonamiento con limitaciones de segmentos, architectures exóticas, etc.

¿No es la simple diferencia en nombres razón suficiente para usar el tipo adecuado para la cosa adecuada?

Si está almacenando un tamaño, use size_t . Si está almacenando un puntero, use intptr_t . Una persona que lea tu código sabrá instantáneamente que “aha, este es un tamaño de algo, probablemente en bytes”, y “oh, aquí hay un valor de puntero almacenado como un entero, por alguna razón”.

De lo contrario, podría usar unsigned long (o, en estos tiempos modernos aquí, unsigned long long ) para todo. El tamaño no lo es todo, los nombres tipo llevan un significado que es útil ya que ayuda a describir el progtwig.

Es posible que el tamaño de la matriz más grande sea más pequeño que un puntero. Piense en architectures segmentadas: los punteros pueden ser de 32 bits, pero un solo segmento puede abordar solo 64 KB (por ejemplo, la antigua architecture 8086 en modo real).

Si bien estos ya no se usan comúnmente en las máquinas de escritorio, el estándar C está diseñado para admitir incluso architectures pequeñas y especializadas. Todavía hay sistemas integrados que se están desarrollando con CPU de 8 o 16 bits, por ejemplo.

Me imagino (y esto vale para todos los nombres de tipo) que transmite mejor tus intenciones en el código.

Por ejemplo, aunque unsigned short y wchar_t tienen el mismo tamaño en Windows (creo), usar wchar_t lugar de unsigned short muestra la intención de que lo uses para almacenar un carácter ancho, en lugar de solo un número arbitrario.

Mirando hacia atrás y hacia adelante, y recordando que varias architectures extrañas estaban dispersas por el paisaje, estoy bastante seguro de que estaban tratando de ajustar todos los sistemas existentes y también proporcionar todos los posibles sistemas futuros.

Así que, por cierto, de la forma en que se resolvieron las cosas, hasta ahora no hemos necesitado tantos tipos.

Pero incluso en LP64, un paradigma bastante común, necesitábamos size_t y ssize_t para la interfaz de llamada del sistema. Uno puede imaginar un sistema heredado o futuro más limitado, donde el uso de un tipo completo de 64 bits es costoso y es posible que deseen despejar en operaciones de E / S mayores de 4 GB pero que aún tengan punteros de 64 bits.

Creo que debe preguntarse: qué podría haber sido desarrollado, qué podría venir en el futuro. (Tal vez punteros de Internet de sistema distribuido de 128 bits, pero no más de 64 bits en una llamada al sistema, o quizás incluso un límite “heredado” de 32 bits. 🙂 Imagen de que los sistemas heredados pueden obtener nuevos comstackdores de C … .

Además, mira lo que existía en ese momento. Además de los miles de millones de modelos de memoria en modo real, ¿qué hay de los mainframes de 60 bits de palabra / 18 bits de CDC? ¿Qué tal la serie Cray? No importa normal ILP64, LP64, LLP64. (Siempre pensé que Microsoft era pretencioso con LLP64, debería haber sido P64.) Ciertamente puedo imaginarme un comité tratando de cubrir todas las bases …

 int main(){ int a[4]={0,1,5,3}; int a0 = a[0]; int a1 = *(a+1); int a2 = *(2+a); int a3 = 3[a]; return a2; } 

Implicando que intptr_t siempre debe sustituir a size_t y viceversa.