una - tipos de arreglos en c++
¿Cuál es el tipo correcto para los índices de matriz en C? (8)
Casi siempre uso size_t
para índices de matriz / contadores de bucle. Claro que hay algunas instancias especiales en las que puede querer compensaciones firmadas, pero en general, usar un tipo firmado tiene muchos problemas:
El mayor riesgo es que si una persona que llama le pasa un tamaño / desplazamiento enorme tratando las cosas como sin firmar (o si lo lee de un archivo equivocado), puede interpretarlo como un número negativo y no captar que es fuera de los límites. Por ejemplo, if (offset<size) array[offset]=foo; else error();
if (offset<size) array[offset]=foo; else error();
escribirá en algún lado que no debería.
Otro problema es la posibilidad de un comportamiento indefinido con un desbordamiento de entero con signo. Ya sea que use aritmética sin firmar o firmada, hay problemas de desbordamiento para tener en cuenta y verificar, pero personalmente encuentro que el comportamiento sin firmar es mucho más fácil de manejar.
Otra razón más para utilizar la aritmética sin signo (en general): a veces estoy usando índices como compensaciones en una matriz de bits y quiero usar% 8 y / 8 o% 32 y / 32. Con tipos firmados, estas serán operaciones de división reales. Con unsigned, se pueden generar las operaciones de bitwise y / bitshift esperadas.
¿Qué tipo de índice de matriz en C99 debería usarse? Tiene que funcionar en LP32, ILP32, ILP64, LP64, LLP64 y más. No tiene que ser un tipo C89.
He encontrado 5 candidatos:
size_t
-
ptrdiff_t
-
intptr_t
/uintptr_t
-
int_fast*_t
/uint_fast*_t
-
int_least*_t
/uint_least*_t
Hay un código simple para ilustrar mejor el problema. ¿Cuál es el mejor tipo para i
y j
en estos dos bucles particulares? Si hay una buena razón, dos tipos diferentes también están bien.
for (i=0; i<imax; i++) {
do_something(a[i]);
}
/* jmin can be less than 0 */
for (j=jmin; j<jmax; j++) {
do_something(a[j]);
}
PD En la primera versión de la pregunta, había olvidado los índices negativos.
PPS No voy a escribir un compilador C99. Sin embargo, cualquier respuesta de un programador de compiladores sería muy valiosa para mí.
Pregunta similar:
- size_t contra uintptr_t
El contexto de esta pregunta si es diferente, sin embargo.
Como el tipo de sizeof(array)
(y el argumento de malloc
) es size_t
, y la matriz no puede contener más elementos que su tamaño, se deduce que size_t
se puede usar para el índice de la matriz.
EDITAR Este análisis es para matrices basadas en 0, que es el caso común. ptrdiff_t
funcionará en cualquier caso, pero es un poco extraño que una variable de índice tenga un tipo de diferencia de puntero.
Creo que deberías usar ptrdiff_t
por los siguientes motivos
- Los índices pueden ser negativos (por lo tanto, todos los tipos sin firmar, incluido
size_t
, están fuera de toda duda) - El tipo de
p2 - p1
esptrdiff_t
. El tipo dei
en el reverso,*(p1 + i)
, también debería ser ese tipo (nótese que*(p + i)
es equivalente ap[i]
)
En su situación, usaría ptrdiff_t
. No es solo que las indicaciones pueden ser negativas. Es posible que desee realizar una cuenta regresiva hasta cero, en cuyo caso los tipos firmados arrojan un error desagradable y sutil:
for(size_t i=5; i>=0; i--) {
printf("danger, this loops forever/n);
}
Eso no sucederá si usa ptrdiff_t
o cualquier otro tipo de firma adecuado. En sistemas POSIX, puede usar ssize_t
.
Personalmente, a menudo solo uso int
, a pesar de que podría decirse que no es lo correcto.
Mi elección: ptrdiff_t
Muchos han votado por ptrdiff_t
, pero algunos han dicho que es extraño indexar usando un tipo de diferencia de puntero. Para mí, tiene perfecto sentido: el índice de matriz es la diferencia del puntero de origen.
Algunos también han dicho que size_t
es correcto porque está diseñado para contener el tamaño. Sin embargo, como algunos han comentado: este es el tamaño en bytes, por lo que generalmente puede contener valores varias veces mayores que el máximo posible índice de matriz.
Si comienza en 0
, use size_t porque ese tipo debe poder indexar cualquier matriz:
-
sizeof
devuelve, por lo que no es válido para que una matriz tenga más elementos quesize_t
-
malloc
toma como argumento, como lo menciona Amnon
Si comienza por debajo de cero, cambie para comenzar en cero y use size_t
, que está garantizado que funciona debido a las razones anteriores. Entonces reemplace:
for (j = jmin; j < jmax; j++) {
do_something(a[j]);
}
con:
int *b = &a[jmin];
for (size_t i = 0; i < (jmax - jmin); i++) {
do_something(b[i]);
}
Por qué no usar:
ptrdiff_t : el valor máximo que esto representa puede ser menor que el valor máximo de
size_t
.Esto se menciona en cppref , y la posibilidad de un comportamiento indefinido si la matriz es demasiado grande se sugiere en C99 6.5.5 / 9:
Cuando se restan dos punteros, ambos señalarán elementos del mismo objeto de matriz, o uno más allá del último elemento del objeto de matriz; el resultado es la diferencia de los subíndices de los dos elementos de la matriz. El tamaño del resultado está definido por la implementación, y su tipo (un tipo de entero con signo) es ptrdiff_t definido en el encabezado. Si el resultado no es representable en un objeto de ese tipo, el comportamiento no está definido .
Por curiosidad,
intptr_t
también podría ser más grande quesize_t
en una arquitectura de memoria segmentada: https://.com/a/1464194/895245GCC también impone límites adicionales al tamaño máximo de los objetos de matriz estática: ¿Cuál es el tamaño máximo de una matriz en C?
uintptr_t : No estoy seguro. Así que solo usaría
size_t
porque estoy más seguro :-)
Si conoce la longitud máxima de su matriz por adelantado, puede usar
-
int_fast*_t / uint_fast*_t
-
int_least*_t / uint_least*_t
En todos los demás casos, recomendaría usar
-
size_t
o
-
ptrdiff_t
dependiendo del clima, quiere permitir índices negativos.
Utilizando
-
intptr_t / uintptr_t
también sería seguro, pero tiene una semántica un poco diferente.
Yo uso unsigned int
. (Aunque prefiero la taquigrafía unsigned
)
En C99, se garantiza que unsigned int
indexará cualquier matriz portátil. Solo se garantiza que admitan matrices de 65''535 bytes o menores, y el valor unsigned int
máximo unsigned int
es de al menos 65''535.
De §el proyecto público WG14 N1256 del estándar C99:
5.2.4.1 Límites de traducción
La implementación debe ser capaz de traducir y ejecutar al menos un programa que contenga al menos una instancia de cada uno de los siguientes límites: (Las implementaciones deben evitar imponer límites de traducción fijos siempre que sea posible).
(...)
- 65535 bytes en un objeto (solo en un entorno alojado)
(...)
5.2.4.2 Límites numéricos
Se requiere una implementación para documentar todos los límites especificados en esta subcláusula, que se especifican en los encabezados
<limits.h>
y<float.h>
. Se especifican límites adicionales en<stdint.h>
.5.2.4.2.1 Tamaños de tipos enteros
<limits.h>
Los valores que figuran a continuación se sustituirán por expresiones constantes adecuadas para su uso en las directivas
#if
preprocesamiento. Además, a excepción deCHAR_BIT
yMB_LEN_MAX
, lo siguiente se reemplazará por expresiones que tengan el mismo tipo que una expresión que sea un objeto del tipo correspondiente convertido de acuerdo con las promociones enteras. Sus valores definidos por la implementación serán iguales o mayores en magnitud (valor absoluto) a los mostrados, con el mismo signo.(...)
- valor máximo para un objeto de tipo
unsigned int
UINT_MAX
65535 // 2 ^ 16 - 1
En ANSI C, el tamaño máximo de matriz portátil es en realidad de solo 32''767 bytes, por lo que incluso funcionará un int
firmado, que tiene un valor máximo de al menos 32''767 (Apéndice A.4).
De §2.2.4 de un proyecto C89:
2.2.4.1 Límites de traducción
La implementación debe ser capaz de traducir y ejecutar al menos un programa que contenga al menos una instancia de cada uno de los siguientes límites: (Las implementaciones deben evitar imponer límites de traducción fijos siempre que sea posible).
(...)
- 32767 bytes en un objeto (solo en un entorno alojado)
(...)
2.2.4.2 Límites numéricos
Una implementación conforme deberá documentar todos los límites especificados en esta sección, que se especificarán en los encabezados
<limits.h>
y<float.h>
."Tamaños de tipos integrales
<limits.h>
"Los valores que figuran a continuación se sustituirán por expresiones constantes adecuadas para su uso en las directivas #if de preprocesamiento. Sus valores definidos por la implementación serán iguales o mayores en magnitud (valor absoluto) que los mostrados, con el mismo signo.
(...)
- valor máximo para un objeto de tipo int
INT_MAX