¿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?

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.


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.


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.


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.


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)?


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.


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.


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]);
}

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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.