c - size_t vs. uintptr_t
pointers size-t (7)
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 debería funcionar siempre:
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 no firmados que se garantiza que pueden contener punteros:
uintptr_t p = (size_t) v;
Entonces, ¿cuál es la diferencia entre usar size_t
y uintptr_t
? Ambos están sin firmar, y ambos deberían poder contener cualquier tipo de puntero, por lo que parecen funcionalmente idénticos. ¿Hay alguna razón convincente real 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?
De la misma manera, ptrdiff_t
ha sido un tipo con signo capaz de mantener las diferencias de puntero y, por lo tanto, capaz de contener la mayoría de los punteros, así que, ¿cómo se diferencia 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 punteros de función, ya que no se aplican al problema actual, pero siéntase 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! Regrese a los días de las arquitecturas segmentadas de 16 bits, por ejemplo: una matriz podría estar limitada a un solo segmento (por lo que haría un tamaño de 16 bits) PERO podría tener múltiples segmentos (por lo que se necesitaría un tipo intptr_t
32 bits para elegir el segmento, así como el desplazamiento dentro de él). Sé que estas cosas suenan extrañas en estos días de arquitecturas no segmentadas que se pueden abordar de manera uniforme, pero el estándar DEBE cubrir una variedad más amplia que "lo que es normal en 2009", ¿saben? -)
Con respecto a su declaración:
"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 es en realidad una falacia (un concepto erróneo que resulta de un razonamiento incorrecto) (a) . Puedes pensar que lo último se sigue de lo anterior, pero ese no es realmente el caso.
Los punteros y los índices de matriz no son lo mismo. Es bastante plausible prever una implementación conforme que limite los arreglos a 65536 elementos, pero permite que los punteros dirijan cualquier valor a un espacio de direcciones de 128 bits.
C99 indica que el límite superior de una variable size_t
está definido por SIZE_MAX
y que 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 se mantiene, pero eso no es porque la norma lo garantiza. Porque en realidad no lo garantiza.
(a) Por cierto, esto no es una forma de ataque personal, simplemente declara por qué sus declaraciones son erróneas en el contexto del pensamiento crítico. Por ejemplo, el siguiente razonamiento también es inválido:
Todos los cachorros son lindos. Esta cosa es linda. Por lo tanto esta cosa debe ser un cachorro.
La ternura o no de los cachorros no tiene relación aquí, todo lo que digo es que los dos hechos no llevan a la conclusión, porque las dos primeras oraciones permiten la existencia de cosas lindas que no son cachorros.
Esto es similar a su primera declaración que no necesariamente exige la segunda.
Es posible que el tamaño de la matriz más grande sea más pequeño que un puntero. Piense en arquitecturas segmentadas: los punteros pueden ser de 32 bits, pero un solo segmento puede ser capaz de abordar solo 64 KB (por ejemplo, la antigua arquitectura 8086 en modo real).
Si bien esto ya no se usa comúnmente en las máquinas de escritorio, el estándar C está diseñado para admitir incluso arquitecturas 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 se aplica a todos los nombres de tipo) que transmite mejor sus intenciones en el código.
Por ejemplo, a pesar de que unsigned short
y wchar_t
son del mismo tamaño en Windows (creo), usar wchar_t
lugar de unsigned short
muestra la intención de usarlo 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 arquitecturas extrañas estaban dispersas por el paisaje, estoy bastante seguro de que estaban tratando de envolver todos los sistemas existentes y también proporcionar todos los sistemas futuros posibles.
Tan seguro, la forma en que las cosas se resolvieron, 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 llamadas al sistema. Uno puede imaginar un sistema heredado o futuro más restringido, donde el uso de un tipo completo de 64 bits es costoso y es posible que quieran apostar en operaciones de E / S de más de 4 GB pero que aún tengan punteros de 64 bits.
Creo que hay que preguntarse: qué podría haberse desarrollado, qué podría venir en el futuro. (Quizás los punteros de todo el sistema de Internet de 128 bits distribuidos, pero no más de 64 bits en una llamada al sistema, o tal vez incluso un límite "heredado" de 32 bits. :-) Imagen de que los sistemas heredados pueden obtener nuevos compiladores de C ... .
También, mira lo que existía entonces. Además de los zillion 286 modelos de memoria en modo real, ¿qué hay de los mainframes de puntero CDC de 60 bits / palabras de 18 bits? ¿Qué hay de la serie Cray? No importa normal ILP64, LP64, LLP64. (Siempre pensé que Microsoft era pretencioso con LLP64, debería haber sido P64). Ciertamente, puedo imaginar un comité tratando de cubrir todas las bases ...
Permitiré que todas las otras respuestas se defiendan por sí mismas con respecto al razonamiento con limitaciones de segmento, arquitecturas exóticas, etc.
¿No es la simple diferencia en los nombres razón suficiente para usar el tipo adecuado para la cosa correcta?
Si está almacenando un tamaño, use size_t
. Si está almacenando un puntero, use intptr_t
. Una persona que lea su 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 que se almacena como un entero, por alguna razón".
De lo contrario, podría usar unsigned long
(o, en estos tiempos modernos, unsigned long long
) para todo. El tamaño no lo es todo, los nombres de los tipos tienen un significado que es útil ya que ayuda a describir el programa.
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;
}
Lo que implica que intptr_t siempre debe sustituir a size_t y viceversa.