Os índices de array negativos são permitidos em C?

115

Eu estava lendo um código e descobri que a pessoa estava usando arr[-2]para acessar o 2º elemento antes do arr, assim:

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

Isso é permitido?

Eu sei que arr[x]é o mesmo que *(arr + x). Então arr[-2]é *(arr - 2), o que parece OK. O que você acha?

bodacydo
fonte

Respostas:

168

Está correto. De C99 §6.5.2.1 / 2:

A definição do operador subscrito [] é que E1 [E2] é idêntico a (* ((E1) + (E2))).

Não há mágica. É uma equivalência 1-1. Como sempre, ao remover a referência de um ponteiro (*), você precisa ter certeza de que está apontando para um endereço válido.

Matthew Flaschen
fonte
2
Observe também que você não precisa desreferenciar o ponteiro para obter UB. A mera computação somearray-2é indefinida, a menos que o resultado esteja na faixa do início somearraya 1 após o fim.
RBerteig
34
Em livros mais antigos, eles []eram referenciados como um açúcar de sintaxe para aritmética de ponteiros. A maneira favorita de confundir iniciantes é escrever 1[arr]- em vez de arr[1]- e observá-los adivinhando o que isso significa.
Dummy00001
4
O que acontece em sistemas de 64 bits (LP64) quando você tem um índice interno de 32 bits que é negativo? O índice deve ser promovido para um int assinado de 64 bits antes do cálculo do endereço?
Paul R
4
@Paul, de §6.5.6 / 8 (Operadores aditivos), "Quando uma expressão que tem tipo inteiro é adicionada ou subtraída de um ponteiro, o resultado tem o tipo do operando ponteiro. Se o operando ponteiro apontar para um elemento de um objeto de matriz, e a matriz é grande o suficiente, o resultado aponta para um elemento deslocado do elemento original de forma que a diferença dos subscritos dos elementos resultantes e originais da matriz seja igual à expressão inteira. " Portanto, acho que será promovido, e ((E1)+(E2))será um ponteiro (64 bits) com o valor esperado.
Matthew Flaschen
@Matthew: obrigado por isso - parece que deve funcionar como se poderia razoavelmente esperar.
Paul R
63

Isso só é válido se arrfor um ponteiro que aponta para o segundo elemento em uma matriz ou um elemento posterior. Caso contrário, não é válido, porque você acessaria a memória fora dos limites do array. Então, por exemplo, isso seria errado:

int arr[10];

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

Mas isso seria bom:

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

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

No entanto, é incomum usar um subscrito negativo.

James McNellis
fonte
Eu não iria tão longe a ponto de dizer que é inválido, apenas potencialmente confuso
Matt Joiner
13
@Matt: o código do primeiro exemplo produz um comportamento indefinido.
James McNellis
5
É inválido. Pelo padrão C, ele explicitamente tem comportamento indefinido. Por outro lado, se fizesse int arr[10];parte de uma estrutura com outros elementos antes dela, arr[-2]poderia ser potencialmente bem definida e você poderia determinar se ela é baseada em offsetofetc.
R .. GitHub PARE DE AJUDAR ICE
4
Encontrei-o na Seção 5.3 K&R, perto do 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.Mesmo assim, seu exemplo é melhor para me ajudar a entendê-lo. Obrigado!
Qiang Xu
4
Desculpe pela necromancia do thread, mas eu adoro como K&R são ambíguos quanto ao que "ilegal" significa. A última frase soa como se os acessos fora dos limites gerassem um erro de compilação. Esse livro é um veneno para iniciantes.
Martin
12

Parece bom para mim. No entanto, seria raro você precisar dele legitimamente.

Matt Joiner
fonte
9
Não é que rara - é muito útil em, por exemplo processamento de imagem com operadores da vizinhança.
Paul R
Eu só precisava usar isso porque estou criando um pool de memória com uma pilha e heap [estrutura / design]. A pilha cresce em direção a endereços de memória superiores, o heap cresce em direção a endereços de memória inferior. Encontro no meio.
JMI MADISON
8

O que provavelmente era isso arrera apontar para o meio da matriz, portanto, arr[-2]apontar para algo na matriz original sem sair dos limites.

Igor Zevaka
fonte
7

Não tenho certeza de quão confiável isso é, mas acabei de ler a seguinte advertência sobre índices de matriz negativos em sistemas de 64 bits (LP64, presumivelmente): http://www.devx.com/tips/Tip/41349

O autor parece estar dizendo que índices de array int de 32 bits com endereçamento de 64 bits podem resultar em cálculos de endereço incorretos, a menos que o índice de array seja explicitamente promovido para 64 bits (por exemplo, por meio de uma conversão ptrdiff_t). Na verdade, vi um bug de sua natureza com a versão PowerPC do gcc 4.1.0, mas não sei se é um bug do compilador (ou seja, deve funcionar de acordo com o padrão C99) ou comportamento correto (ou seja, o índice precisa ser convertido para 64 bits para o comportamento correto)?

Paul R
fonte
3
Isso soa como um bug do compilador.
tbleher
2

Sei que a pergunta foi respondida, mas não resisti em compartilhar essa explicação.

Lembro-me dos Princípios de design do compilador. Vamos supor que a seja uma matriz int e o tamanho de int seja 2 e o endereço base de a seja 1000.

Como a[5]vai 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

Essa explicação também é a razão pela qual índices negativos em matrizes funcionam em C.

ou seja, se eu acessar, a[-5]ele 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

Ele me retornará o objeto no local 990. Por essa lógica, podemos acessar índices negativos no Array em C.

Ajinkya Patil
fonte
2

Sobre por que alguém iria querer usar índices negativos, usei-os em dois contextos:

  1. Ter uma tabela de números combinatórios que informa comb [1] [- 1] = 0; você sempre pode verificar os índices antes de acessar a tabela, mas desta forma o código parece mais limpo e executa mais rápido.

  2. Colocando um centinel no início de uma mesa. Por exemplo, você deseja usar algo como

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

mas então você também deve verificar se ié positivo.
Solução: faça com que a[-1]seja -DBLE_MAX, para que x&lt;a[-1]sempre seja falso.

Santiago Egido Arteaga
fonte
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
fonte
1
Embora este código possa responder à pergunta, fornecer contexto adicional sobre por que e / ou como este código responde à pergunta melhora seu valor a longo prazo.
β.εηοιτ.βε
Python groovy ... tê-los. Um caso de uso simples é quando se pode acessar o último elemento de um array sem saber o tamanho do array, um requisito muito real em muitas situações de projeto. Além disso, muitas DSLs se beneficiam disso.
Rathinavelu Muthaliar