unsigned int vs. size_t

492

Noté que el código C y C ++ moderno parece usarse en size_tlugar de int/ unsigned intprácticamente en todas partes, desde parámetros para funciones de cadena C hasta STL. Tengo curiosidad sobre la razón de esto y los beneficios que trae.

Robar
fuente

Respuestas:

388

El size_ttipo es el tipo entero sin signo que es el resultado del sizeofoperador (y el offsetofoperador), por lo que se garantiza que es lo suficientemente grande como para contener el tamaño del objeto más grande que su sistema puede manejar (por ejemplo, una matriz estática de 8 Gb).

El size_ttipo puede ser mayor, igual o menor que an unsigned int, y su compilador puede hacer suposiciones al respecto para la optimización.

Puede encontrar información más precisa en el estándar C99, sección 7.17, cuyo borrador está disponible en Internet en formato pdf , o en el estándar C11, sección 7.19, también disponible como borrador pdf .

Remo.D
fuente
50
No Piense en x86-16 con el modelo de memoria grande (no enorme): los punteros están lejos (32 bits), pero los objetos individuales están limitados a 64k (por lo que size_t puede ser de 16 bits).
dan04
8
"tamaño del objeto más grande" no es una redacción pobre, pero es absolutamente correcta. El sexto de un objeto puede ser mucho más limitado que el espacio de direcciones.
gnasher729
3
"su compilador podría asumirlo": ¡espero que el compilador conozca el rango exacto de valores que size_tpuede representar! Si no es así, ¿quién lo hace?
Marc van Leeuwen
44
@Marc: Creo que el punto era más que el compilador podría hacer algo con ese conocimiento.
8
Solo desearía que este tipo cada vez más popular no requiriera la inclusión de un archivo de encabezado.
user2023370
74

En resumen, size_tnunca es negativo, y maximiza el rendimiento porque está diseñado para ser el tipo entero sin signo que es lo suficientemente grande, pero no demasiado grande, para representar el tamaño del objeto más grande posible en la plataforma de destino.

Los tamaños nunca deben ser negativos, y de hecho size_tes un tipo sin signo. Además, debido a size_tque no tiene signo, puede almacenar números que son aproximadamente dos veces más grandes que en el tipo con signo correspondiente, porque podemos usar el bit de signo para representar la magnitud, como todos los demás bits en el entero sin signo. Cuando ganamos un bit más, estamos multiplicando el rango de números que podemos representar por un factor de aproximadamente dos.

Entonces, usted pregunta, ¿por qué no solo usar un unsigned int? Es posible que no pueda contener números lo suficientemente grandes. En una implementación donde unsigned inthay 32 bits, el mayor número que puede representar es 4294967295. Algunos procesadores, como el IP16L32, pueden copiar objetos más grandes que 4294967295bytes.

Entonces, preguntas, ¿por qué no usar un unsigned long int? Exige un peaje de rendimiento en algunas plataformas. El estándar C requiere que una longocupe al menos 32 bits. Una plataforma IP16L32 implementa cada una de 32 bits como un par de palabras de 16 bits. Casi todos los operadores de 32 bits en estas plataformas requieren dos instrucciones, si no más, porque trabajan con los 32 bits en dos fragmentos de 16 bits. Por ejemplo, mover una longitud de 32 bits generalmente requiere dos instrucciones de la máquina: una para mover cada fragmento de 16 bits.

El uso size_tevita este costo de rendimiento. De acuerdo con este fantástico artículo , "Tipo size_tes un typedef que es un alias para algún tipo de entero sin signo, típicamente unsigned into unsigned long, pero posiblemente incluso unsigned long long. Se supone que cada implementación de Standard C elige el entero sin signo que es lo suficientemente grande, pero no más grande de lo necesario. para representar el tamaño del objeto más grande posible en la plataforma de destino ".

Rose Perrone
fuente
1
Lamento comentar sobre esto después de tanto tiempo, pero solo tuve que confirmar el número más grande que puede contener un int sin signo; tal vez estoy malinterpretando su terminología, pero pensé que el número más grande que un int sin signo puede contener es 4294967295, 65356 siendo el máximo de un corto sin signo.
Mitch el
Si su int sin signo ocupa 32 bits, entonces sí, el número más grande que puede contener es 2 ^ 32 - 1, que es 4294967295 (0xffffffff). ¿Tienes alguna otra pregunta?
Rose Perrone
3
@Mitch: el valor más grande que se puede representar en una unsigned intlata y varía de un sistema a otro. Se requiere que sea al menos 65536 , pero es común 4294967295y podría ser 18446744073709551615(2 ** 64-1) en algunos sistemas.
Keith Thompson el
1
El valor más grande que puede contener un int sin signo de 16 bits es 65535, no 65536. Una diferencia pequeña pero importante como 65536 es igual a 0 en un int sin signo de 16 bits.
Sie Raybould
1
@ gnasher729: ¿Está seguro del estándar C ++? Después de buscar durante algún tiempo, tengo la impresión de que simplemente eliminaron todas las garantías absolutas sobre los rangos de enteros (excluyendo unsigned char). El estándar no parece contener la cadena '65535' o '65536' en ninguna parte, y '+32767' solo aparece (1.9: 9) en una nota como el mayor entero posible representable en int; ¡ni siquiera se ofrece garantía que INT_MAXno pueda ser más pequeña que eso!
Marc van Leeuwen
51

El tipo size_t es el tipo devuelto por el operador sizeof. Es un entero sin signo capaz de expresar el tamaño en bytes de cualquier rango de memoria admitido en la máquina host. Está (típicamente) relacionado con ptrdiff_t en que ptrdiff_t es un valor entero con signo tal que sizeof (ptrdiff_t) y sizeof (size_t) son iguales.

Al escribir código C, siempre debe usar size_t cuando se trate de rangos de memoria.

El tipo int, por otro lado, se define básicamente como el tamaño del valor entero (con signo) que la máquina host puede usar para realizar la aritmética de enteros de manera más eficiente. Por ejemplo, en muchas computadoras antiguas tipo PC, el valor sizeof (size_t) sería 4 (bytes) pero sizeof (int) sería 2 (byte). La aritmética de 16 bits era más rápida que la aritmética de 32 bits, aunque la CPU podía manejar un espacio de memoria (lógico) de hasta 4 GiB.

Utilice el tipo int solo cuando le interese la eficiencia, ya que su precisión real depende en gran medida de las opciones del compilador y de la arquitectura de la máquina. En particular, el estándar C especifica los siguientes invariantes: sizeof (char) <= sizeof (short) <= sizeof (int) <= sizeof (long) sin colocar otras limitaciones en la representación real de la precisión disponible para el programador para cada uno de Estos tipos primitivos.

Nota: Esto NO es lo mismo que en Java (que en realidad especifica la precisión de bits para cada uno de los tipos 'char', 'byte', 'short', 'int' y 'long').

Kevin S.
fuente
La definición de facto de int es que es de 16 bits en 16 máquinas y de 32 bits en algo más grande. Se ha escrito demasiado código que supone que int tiene 32 bits de ancho, para cambiar esto ahora y, como resultado, las personas siempre deben usar size_t o {, u} int {8,16,32,64} _t si quieren algo específico: - como medida de precaución, las personas deberían usar siempre estos, en lugar de los tipos enteros integrales.
Más claro el
3
"Es un entero sin signo capaz de expresar el tamaño en bytes de cualquier rango de memoria compatible con la máquina host". -> No. size_tes capaz de representar el tamaño de cualquier objeto individual (por ejemplo: número, matriz, estructura). Todo el rango de memoria puede excedersize_t
chux - Reinstale Monica
"Al escribir código C, siempre debe usar size_t cuando se trate de rangos de memoria". - eso implica que cada índice de cada matriz debería ser size_t- Espero que no te refieras a eso. La mayoría de las veces no tratamos con matrices donde la cardinalidad del espacio de direcciones + portabilidad incluso importa. En estos casos lo tomarías size_t. En todos los demás casos, se toman índices de enteros (firmados). Debido a que la confusión (que viene sin previo aviso) que surge del comportamiento de flujo subyacente insospechado de los no firmados es más común y peor que los problemas de portabilidad que pueden surgir en los otros casos.
johannes_lalala
23

Escriba size_t debe ser lo suficientemente grande como para almacenar el tamaño de cualquier objeto posible. Unsigned int no tiene que satisfacer esa condición.

Por ejemplo, en sistemas de 64 bits, int y unsigned int pueden tener 32 bits de ancho, pero size_t debe ser lo suficientemente grande como para almacenar números mayores que 4G

Maciej Hehl
fuente
38
"objeto" es el lenguaje utilizado por el estándar.
R .. GitHub DEJA DE AYUDAR AL HIELO
2
Creo size_tque solo tendría que ser tan grande si el compilador pudiera aceptar un tipo X tal que sizeof (X) arrojaría un valor mayor que 4G. La mayoría de los compiladores rechazarían typedef unsigned char foo[1000000000000LL][1000000000000LL], por ejemplo , e incluso foo[65536][65536];podrían ser legítimamente rechazados si excede un límite documentado definido por la implementación.
supercat
1
@ MattJoiner: La redacción está bien. "Objeto" no es nada vago, sino que se define como "región de almacenamiento".
Carreras de ligereza en órbita
4

Este extracto del manual glibc 0.02 también puede ser relevante al investigar el tema:

Existe un problema potencial con el tipo size_t y las versiones de GCC anteriores a la versión 2.4. ANSI C requiere que size_t siempre sea un tipo sin signo. Para compatibilidad con los archivos de encabezado de los sistemas existentes, GCC define size_t en stddef.h' to be whatever type the system'ssys / types.h 'define que es. La mayoría de los sistemas Unix que definen size_t en `sys / types.h ', lo definen como un tipo con signo. Algún código en la biblioteca depende de que size_t sea un tipo sin signo y no funcionará correctamente si está firmado.

El código de la biblioteca GNU C que espera que size_t no esté firmado es correcto. La definición de size_t como tipo con signo es incorrecta. Planeamos que en la versión 2.4, GCC siempre definirá size_t como un tipo sin signo, y fixincludes' script will massage the system'ssys / types.h 'para no entrar en conflicto con esto.

Mientras tanto, solucionamos este problema diciéndole a GCC explícitamente que use un tipo sin signo para size_t al compilar la biblioteca GNU C. `configure 'detectará automáticamente qué tipo de GCC usa para size_t organizar para anularlo si es necesario.

Graeme Burke
fuente
3

Si mi compilador está configurado en 32 bits, size_tno es más que un typedef para unsigned int. Si mi compilador está configurado en 64 bits, size_tno es más que un typedef para unsigned long long.

Pez cebra
fuente
1
Se puede definir como unsigned longpara ambos casos en algunos sistemas operativos.
StaceyGirl
-4

size_t es el tamaño de un puntero.

Entonces, en 32 bits o el modelo ILP32 común (entero, largo, puntero) size_t es de 32 bits. y en 64 bits o el modelo LP64 común (largo, puntero) size_t es de 64 bits (los enteros siguen siendo de 32 bits).

Hay otros modelos, pero estos son los que usa g ++ (al menos por defecto)


fuente
15
size_tno es necesariamente del mismo tamaño que un puntero, aunque comúnmente lo es. Un puntero debe poder apuntar a cualquier ubicación en la memoria; size_tsolo tiene que ser lo suficientemente grande como para representar el tamaño del objeto individual más grande.
Keith Thompson el