Apakah indeks array negatif diperbolehkan di C?

115

Saya baru saja membaca beberapa kode dan menemukan bahwa orang tersebut menggunakan arr[-2]untuk mengakses elemen ke-2 sebelum arr, seperti:

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

Apa itu diperbolehkan?

Saya tahu itu arr[x]sama dengan *(arr + x). Begitu arr[-2]juga *(arr - 2), yang tampaknya oke. Bagaimana menurut anda?

bodacydo
sumber

Jawaban:

168

Itu betul. Dari C99 §6.5.2.1 / 2:

Definisi dari operator subskrip [] adalah bahwa E1 [E2] identik dengan (* ((E1) + (E2))).

Tidak ada keajaiban. Ini setara 1-1. Seperti biasa saat mendereferensi penunjuk (*), Anda harus memastikan penunjuk tersebut mengarah ke alamat yang valid.

Matthew Flaschen
sumber
2
Perhatikan juga bahwa Anda tidak perlu mendereferensi penunjuk untuk mendapatkan UB. Hanya komputasi somearray-2yang tidak ditentukan kecuali hasilnya berada dalam kisaran dari awal somearrayhingga 1 setelah akhirnya.
RBerteig
34
Dalam buku-buku yang lebih tua, []itu direferensikan sebagai gula sintaks untuk aritmatika penunjuk. Cara favorit untuk membingungkan pemula adalah dengan menulis 1[arr]- alih-alih arr[1]- dan melihat mereka menebak-nebak apa maksudnya.
Dummy00001
4
Apa yang terjadi pada sistem 64 bit (LP64) ketika Anda memiliki indeks int 32 bit yang negatif? Haruskah indeks dipromosikan ke 64 bit yang ditandatangani sebelum kalkulasi alamat?
Paul R
4
@Paul, dari §6.5.6 / 8 (Operator aditif), "Ketika ekspresi yang memiliki tipe integer ditambahkan ke atau dikurangi dari pointer, hasilnya memiliki tipe operand pointer. Jika operand pointer menunjuk ke elemen dari objek larik, dan lariknya cukup besar, hasilnya menunjuk ke elemen yang diimbangi dari elemen asli sedemikian rupa sehingga perbedaan subskrip elemen larik yang dihasilkan dan asli sama dengan ekspresi bilangan bulat. " Jadi saya pikir itu akan dipromosikan, dan ((E1)+(E2))akan menjadi pointer (64-bit) dengan nilai yang diharapkan.
Matthew Flaschen
@Matthew: terima kasih untuk itu - sepertinya itu harus berfungsi seperti yang diharapkan secara wajar.
Paul R
63

Ini hanya valid jika arrpointer yang menunjuk ke elemen kedua dalam array atau elemen yang lebih baru. Jika tidak, ini tidak valid, karena Anda akan mengakses memori di luar batas array. Jadi, misalnya, ini salah:

int arr[10];

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

Tapi ini tidak masalah:

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

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

Namun, tidak biasa menggunakan subskrip negatif.

James McNellis
sumber
Saya tidak akan mengatakan lebih jauh dengan mengatakan itu tidak valid, hanya berpotensi berantakan
Matt Joiner
13
@Matt: Kode dalam contoh pertama menghasilkan perilaku yang tidak ditentukan.
James McNellis
5
Itu tidak valid. Dengan standar C, secara eksplisit memiliki perilaku yang tidak terdefinisi. Di sisi lain, jika int arr[10];merupakan bagian dari struktur dengan elemen lain sebelumnya, arr[-2]berpotensi terdefinisi dengan baik, dan Anda dapat menentukan apakah itu didasarkan pada offsetof, dll.
R .. GitHub STOP HELPING ICE
4
Ditemukan di K&R Bagian 5.3, di bagian akhir: 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.Tetap saja, teladan Anda lebih baik dalam membantu saya memahaminya. Terima kasih!
Qiang Xu
4
Maaf untuk benang necromancy, tapi saya suka bagaimana K&R ambigu tentang apa artinya "ilegal". Kalimat terakhir membuatnya terdengar seperti akses di luar batas memunculkan kesalahan kompilasi. Buku itu adalah racun bagi pemula.
Martin
12

Kedengarannya bagus untukku. Akan menjadi kasus yang jarang terjadi bahwa Anda membutuhkannya secara sah.

Matt Joiner
sumber
9
Ini bukan yang langka - itu sangat berguna dalam pengolahan citra misalnya dengan operator lingkungan.
Paul R
Saya hanya perlu menggunakan ini karena saya membuat kumpulan memori dengan tumpukan dan tumpukan [struktur / desain]. Tumpukan tumbuh ke alamat memori yang lebih tinggi, tumpukan tumbuh ke alamat memori yang lebih rendah. Bertemu di tengah.
JMI MADISON
8

Apa yang mungkin adalah yang arrmenunjuk ke tengah array, sehingga arr[-2]menunjuk ke sesuatu dalam array asli tanpa keluar batas.

Igor Zevaka
sumber
7

Saya tidak yakin seberapa andal ini, tetapi saya baru saja membaca peringatan berikut tentang indeks array negatif pada sistem 64-bit (LP64 mungkin): http://www.devx.com/tips/Tip/41349

Penulis tampaknya mengatakan bahwa indeks larik int 32 bit dengan pengalamatan 64 bit dapat menghasilkan kalkulasi alamat yang buruk kecuali indeks larik secara eksplisit dipromosikan ke 64 bit (misalnya melalui ptrdiff_t cast). Saya benar-benar telah melihat bug dari sifatnya dengan versi PowerPC dari gcc 4.1.0, tetapi saya tidak tahu apakah itu bug kompiler (yaitu harus bekerja sesuai dengan standar C99) atau perilaku yang benar (yaitu indeks membutuhkan cast ke 64 bit untuk perilaku yang benar)?

Paul R
sumber
3
Ini terdengar seperti bug kompiler.
tbleher
2

Saya tahu pertanyaannya telah terjawab, tetapi saya tidak dapat menahan diri untuk tidak membagikan penjelasan ini.

Saya ingat Prinsip desain Kompiler, Mari kita asumsikan a adalah larik int dan ukuran int adalah 2, & Alamat dasar untuk a adalah 1000.

Bagaimana a[5]bekerja ->

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

Penjelasan ini juga menjadi alasan mengapa indeks negatif dalam array bekerja di C.

yaitu jika saya mengaksesnya a[-5]akan memberi saya

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

Ini akan mengembalikan saya objek di lokasi 990. Dengan logika ini kita dapat mengakses indeks negatif di Array di C.

Ajinkya Patil
sumber
2

Tentang mengapa seseorang ingin menggunakan indeks negatif, saya telah menggunakannya dalam dua konteks:

  1. Memiliki tabel bilangan kombinatorial yang memberi tahu Anda sisir [1] [- 1] = 0; Anda selalu dapat memeriksa indeks sebelum mengakses tabel, tetapi dengan cara ini kode terlihat lebih bersih dan dijalankan lebih cepat.

  2. Menempatkan centinel di awal tabel. Misalnya, Anda ingin menggunakan sesuatu seperti

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

tetapi kemudian Anda juga harus memeriksa bahwa iitu positif.
Solusi: membuatnya sehingga a[-1]adalah -DBLE_MAX, sehingga x&lt;a[-1]akan selalu palsu.

Santiago Egido Arteaga
sumber
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
sumber
1
Meskipun kode ini dapat menjawab pertanyaan, memberikan konteks tambahan tentang mengapa dan / atau bagaimana kode ini menjawab pertanyaan tersebut meningkatkan nilai jangka panjangnya.
β.εηοιτ.βε
Python groovy ... miliki mereka. Kasus penggunaan sederhana adalah seseorang dapat mengakses elemen terakhir dari sebuah array tanpa mengetahui ukuran array, persyaratan yang sangat nyata dalam banyak situasi Proyek. Juga banyak DSL mendapat manfaat dari ini.
Rathinavelu Muthaliar