¿Se permiten índices de matriz negativos en C?

115

Estaba leyendo un código y descubrí que la persona estaba usando arr[-2]para acceder al segundo elemento antes del arr, así:

|a|b|c|d|e|f|g|
       ^------------ arr[0]
         ^---------- arr[1]
   ^---------------- arr[-2]

eso está permitido?

Sé que arr[x]es lo mismo que *(arr + x). También lo arr[-2]es *(arr - 2), lo que parece estar bien. ¿Qué piensas?

bodacydo
fuente

Respuestas:

168

Eso es correcto. Desde C99 §6.5.2.1 / 2:

La de fi nición del operador de subíndice [] es que E1 [E2] es idéntico a (* ((E1) + (E2))).

No hay magia. Es una equivalencia 1-1. Como siempre, cuando se elimina la referencia a un puntero (*), debe asegurarse de que apunte a una dirección válida.

Matthew Flaschen
fuente
2
Tenga en cuenta también que no tiene que eliminar la referencia del puntero para obtener UB. La mera computación somearray-2no está definida a menos que el resultado esté en el rango desde el inicio de somearrayhasta 1 después de su final.
RBerteig
34
En los libros más antiguos, []se hacía referencia a ellos como un azúcar sintáctico para la aritmética de punteros. La forma favorita de confundir a los principiantes es escribir 1[arr], en lugar de arr[1], y verlos adivinar lo que se supone que significa.
Dummy00001
4
¿Qué sucede en sistemas de 64 bits (LP64) cuando tiene un índice int de 32 bits que es negativo? ¿Debería promoverse el índice a un int firmado de 64 bits antes del cálculo de la dirección?
Paul R
4
@Paul, de §6.5.6 / 8 (Operadores aditivos), "Cuando una expresión que tiene un tipo entero se suma o se resta de un puntero, el resultado tiene el tipo del operando del puntero. Si el operando del puntero apunta a un elemento de un objeto de matriz, y la matriz es lo suficientemente grande, el resultado apunta a un elemento desplazado del elemento original de modo que la diferencia de los subíndices de los elementos de matriz resultante y original es igual a la expresión entera ". Así que creo que se promoverá y ((E1)+(E2))será un puntero (de 64 bits) con el valor esperado.
Matthew Flaschen
@Matthew: gracias por eso, parece que debería funcionar como uno podría esperar razonablemente.
Paul R
63

Esto solo es válido si arres un puntero que apunta al segundo elemento de una matriz o un elemento posterior. De lo contrario, no es válido, porque accedería a la memoria fuera de los límites de la matriz. Entonces, por ejemplo, esto estaría mal:

int arr[10];

int x = arr[-2]; // invalid; out of range

Pero esto estaría bien:

int arr[10];
int* p = &arr[2];

int x = p[-2]; // valid:  accesses arr[0]

Sin embargo, es inusual utilizar un subíndice negativo.

James McNellis
fuente
No iría tan lejos como para decir que es inválido, solo potencialmente desordenado
Matt Joiner
13
@Matt: El código del primer ejemplo produce un comportamiento indefinido.
James McNellis
5
No es válido. Según el estándar C, explícitamente tiene un comportamiento indefinido. Por otro lado, si int arr[10];eran parte de una estructura con otros elementos antes de ella, arr[-2]podría potencialmente ser bien definidos, y se podía determinar si se basa en offsetof, etc
R .. GitHub dejar de ayudar a ICE
4
Lo encontré en la Sección 5.3 de K&R, cerca del final: If one is sure that the elements exist, it is also possible to index backwards in an array; p[-1], p[-2], and so on are syntactically legal, and refer to the elements that immediately precede p[0]. Of course, it is illegal to refer to objects that are not within the array bounds.Aún así, su ejemplo es mejor para ayudarme a entenderlo. ¡Gracias!
Qiang Xu
4
Perdón por la nigromancia del hilo, pero me encanta cómo K&R son ambiguos en cuanto a lo que significa "ilegal". La última oración hace que parezca que los accesos fuera de los límites arrojan un error de compilación. Ese libro es veneno para principiantes.
Martin
12

Me suena bien. Sin embargo, sería un caso raro que lo necesitara legítimamente.

Matt Joiner
fuente
9
No es que rara - es muy útil en el procesamiento de imágenes, por ejemplo con los operadores de la vecindad.
Paul R
Solo necesitaba usar esto porque estoy creando un grupo de memoria con una pila y un montón [estructura / diseño]. La pila crece hacia direcciones de memoria más altas, el montón crece hacia direcciones de memoria más bajas. Encuentro en el medio.
JMI MADISON
8

Lo que probablemente era eso arrapuntaba al medio de la matriz, por lo tanto, arr[-2]apuntaba a algo en la matriz original sin salirse de los límites.

Igor Zevaka
fuente
7

No estoy seguro de cuán confiable es esto, pero acabo de leer la siguiente advertencia sobre los índices de matriz negativos en sistemas de 64 bits (presumiblemente LP64): http://www.devx.com/tips/Tip/41349

El autor parece estar diciendo que los índices de matriz int de 32 bits con direccionamiento de 64 bits pueden resultar en cálculos de direcciones incorrectos a menos que el índice de matriz se promueva explícitamente a 64 bits (por ejemplo, a través de una conversión ptrdiff_t). De hecho, he visto un error de su naturaleza con la versión PowerPC de gcc 4.1.0, pero no sé si es un error del compilador (es decir, debería funcionar de acuerdo con el estándar C99) o un comportamiento correcto (es decir, el índice necesita una conversión a 64 bits para un comportamiento correcto)?

Paul R
fuente
3
Esto suena como un error del compilador.
tbleher
2

Sé que la pregunta está respondida, pero no pude resistirme a compartir esta explicación.

Recuerdo los principios del diseño del compilador, supongamos que a es una matriz int y el tamaño de int es 2, y la dirección base de a es 1000.

Cómo a[5]funcionará ->

Base Address of your Array a + (index of array *size of(data type for array a))
Base Address of your Array a + (5*size of(data type for array a))
i.e. 1000 + (5*2) = 1010

Esta explicación también es la razón por la que los índices negativos en matrices funcionan en C.

es decir, si a[-5]accedo me dará

Base Address of your Array a + (index of array *size of(data type for array a))
Base Address of your Array a + (-5 * size of(data type for array a))
i.e. 1000 + (-5*2) = 990

Me devolverá el objeto en la ubicación 990. Mediante esta lógica, podemos acceder a índices negativos en Array en C.

Ajinkya Patil
fuente
2

Acerca de por qué alguien querría usar índices negativos, los he usado en dos contextos:

  1. Tener una tabla de números combinatorios que te diga comb [1] [- 1] = 0; siempre puede verificar los índices antes de acceder a la tabla, pero de esta manera el código se ve más limpio y se ejecuta más rápido.

  2. Poner un centinela al principio de una mesa. Por ejemplo, quieres usar algo como

     while (x < a[i]) i--;

pero también debes comprobar que isea ​​positivo.
Solución: haz que a[-1]sea -DBLE_MAXasí, para que x&lt;a[-1]siempre sea falso.

Santiago Egido Arteaga
fuente
0
#include <stdio.h>

int main() // negative index
{ 
    int i = 1, a[5] = {10, 20, 30, 40, 50};
    int* mid = &a[5]; //legal;address,not element there
    for(; i < 6; ++i)
    printf(" mid[ %d ] = %d;", -i, mid[-i]);
}
Rathinavelu Muthaliar
fuente
1
Si bien este código puede responder a la pregunta, proporcionar un contexto adicional sobre por qué y / o cómo este código responde a la pregunta mejora su valor a largo plazo.
β.εηοιτ.βε
Python maravilloso ... tenlos. Un caso de uso simple es que uno puede acceder al último elemento de una matriz sin conocer el tamaño de la matriz, un requisito muy real en muchas situaciones de proyectos. También muchas DSL se benefician de esto.
Rathinavelu Muthaliar