2009-09-23 20 views
218

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 ser capaz de contener cualquier tipo de puntero. He leído en algunos sitios que he encontrado en los Googles que esto es legal y/o debe trabajar siempre:size_t contra uintptr_t

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

Así que en C99, la norma introduce las intptr_t y uintptr_t tipos, que son firmados y no firmados tipos garantizados para ser capaz de mantener los 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 real para usar uintptr_t (o mejor aún, un void *) en lugar de un size_t, que no sea 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 firmado capaz de contener las diferencias de puntero, y por lo tanto capaz de contener la mayoría de los punteros, entonces ¿cómo es distinto de intptr_t?

¿No son básicamente todos estos tipos que sirven versiones triviales 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".

Respuesta

210

size_t es un tipo que puede contener cualquier índice de matriz. Esto significa que, lógicamente , size_t debe ser capaz de sostener cualquier tipo de puntero

No necesariamente! Recordamos los días de las arquitecturas segmentadas de 16 bits, por ejemplo: una matriz puede estar limitada a un solo segmento (por lo que haría una de 16 bits size_t) PERO podría tener múltiples segmentos (por lo que se necesitaría un tipo de 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 arquitecturas segmentadas uniformemente direccionables, pero el estándar DEBE proveer una variedad más amplia que "lo que es normal en 2009", ¿sabes? -)

+4

Esto, junto con muchos otros que saltaron a la misma conclusión, explica la diferencia entre 'size_t' y' uintptr_t', pero ¿qué pasa con 'ptrdiff_t' y' intptr_t'? ¿No podrían estos dos almacenar el mismo rango? de valores en casi cualquier plataforma? ¿Por qué tienen tipos de enteros con tamaño de puntero tanto firmados como no firmados, especialmente si 'ptrdiff_t' ya sirve para un tipo de entero con el tamaño de puntero firmado. –

+7

Frase clave hay "en * casi * cualquier plataforma", @Chris. Una implementación es libre de restringir los punteros al rango 0xf000-0xffff; esto requiere un intptr_t de 16 bits, pero solo un ptrdiff_t de 12/13 bits. – paxdiablo

+22

@Chris, solo para punteros _inside the same array_ está bien definido para tomar su diferencia. Por lo tanto, exactamente en las mismas arquitecturas segmentadas de 16 bits (la matriz debe vivir dentro de un solo segmento pero dos arreglos diferentes pueden estar en segmentos diferentes) los punteros deben ser de 4 bytes, ¡pero las ** diferencias ** del puntero podrían ser de 2 bytes! –

11

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 abordar solo 64 KB (por ejemplo, la antigua arquitectura 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 arquitecturas pequeñas y especializadas. Todavía hay sistemas integrados que se están desarrollando con CPU de 8 o 16 bits, por ejemplo.

+0

Pero puede indexar punteros al igual que las matrices, ¿debería 'size_t' también ser capaz de manejar eso? ¿O las matrices dinámicas en algún segmento lejano aún estarían limitadas a la indexación dentro de su segmento? –

+0

Los punteros de indexación solo son técnicamente compatibles con el tamaño de la matriz que señalan, por lo que si una matriz está limitada a un tamaño de 64 KB, eso es todo lo que necesita la aritmética de punteros para admitir. Sin embargo, los compiladores de MS-DOS admitían un modelo de memoria "enorme", donde los punteros lejanos (punteros segmentados de 32 bits) se manipulaban para que pudieran abordar la totalidad de la memoria como una sola matriz, pero el aritmético hecho a los indicadores detrás de las escenas era bastante feo: cuando el desplazamiento aumentó más allá de un valor de 16 (o algo así), el desplazamiento se reintegró a 0 y la parte del segmento se incrementó. –

+5

Lea http://en.wikipedia.org/wiki/C_memory_model#Memory_segmentation y llore por los programadores de MS-DOS que murieron para que podamos ser libres. – Justicle

4

Imagino (y esto vale para todos escriba nombres) 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), utilizando wchar_t en lugar de unsigned short muestra la intención de que lo va a usar para almacenar un carácter ancho, en lugar de sólo un número arbitrario.

+0

Pero aquí hay una diferencia: en mi sistema, 'wchar_t' es mucho más grande que' short sin firmar', por lo que utilizar uno para el otro sería erróneo y crearía una preocupación de portabilidad seria (y moderna), mientras que la portabilidad se refiere a 'size_t 'y' uintptr_t' parecen estar en las tierras lejanas de 1980 -algo (puñalada al azar en la oscuridad en la fecha, allí) –

+0

Touché! Pero, de nuevo, 'size_t' y' uintptr_t' todavía tienen usos implícitos en sus nombres. – dreamlax

+0

Lo hacen, y quería saber si había una motivación para esto más allá de la simple claridad. Y resulta que hay. –

78

En cuanto a su estado de cuenta:

"El garantías estándar C que size_t es un tipo que puede contener cualquier índice de matriz Esto significa que, lógicamente, size_t debe ser capaz de mantener cualquier tipo de puntero."

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

Los punteros y los índices de matriz son no 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 esto 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 realmente no lo garantiza.

+0

En lugar de volver a escribir lo que dije en los comentarios de Alex Martelli, solo daré las gracias por la aclaración, pero reiteraré la segunda mitad de mi pregunta (la parte 'ptrdiff_t' contra' intptr_t'). –

+0

Esta aclaración me ayudó. ¡Gracias! – zelgit

2

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 ajustar todos los sistemas existentes y también proporcionar todos los posibles sistemas futuros.

Así que, claro, 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 tiene que preguntarse: qué podría haberse 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 compiladores de C ...

Además, mira lo que existía entonces. 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 ...

31

Voy a dejar que todas las otras respuestas representen el razonamiento con limitaciones de segmento, arquitecturas exóticas, etc.

¿No es la simple diferencia en los nombres razón suficiente para utilizar el tipo adecuado para el caso apropiado?

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 simplemente 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 programa.

+0

Estoy de acuerdo, pero estaba considerando algo así como un truco/truco (que claramente documentaría, por supuesto) que implica almacenar un tipo de puntero en un campo 'size_t'. –

+0

La razón por la que no sería la portabilidad. Desde el estándar C99 para 'intptr_t' y' uintptr_t': _ "Estos tipos son opcionales." _ –

-8
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.

+7

Todo esto muestra un peculiar capricho de sintaxis de C. La indexación de matriz se define en términos de que x [y] es equivalente a * (x + y), y como a + 3 y 3 + a son idénticos en tipo y valor, puede usar 3 [a] o a [3]. –