Mengapa indeks array negatif masuk akal?

14

Saya telah menemukan pengalaman aneh dalam pemrograman C. Pertimbangkan kode ini:

int main(){
  int array1[6] = {0, 1, 2, 3, 4, 5};
  int array2[6] = {6, 7, 8, 9, 10, 11};

  printf("%d\n", array1[-1]);
  return 0;
}

Ketika saya mengkompilasi dan menjalankan ini, saya tidak mendapatkan kesalahan atau peringatan. Seperti kata dosen saya, indeks array -1mengakses variabel lain. Saya masih bingung, mengapa ada bahasa pemrograman yang memiliki kemampuan ini? Maksud saya, mengapa mengizinkan indeks array negatif?

Mohammed Fawzan
sumber
2
Sementara pertanyaan ini dimotivasi dengan C sebagai bahasa pemrograman konkret, saya pikir itu dapat dipahami sebagai pertanyaan konseptual yang ontopic di sini (jika nyaris).
Raphael
7
@Raphael Saya tidak setuju dan percaya itu seharusnya milik SO, baik cara ini adalah perilaku buku teks yang tidak terdefinisi (merujuk memori di luar array) dan flag compiler yang tepat harus memperingatkan tentang hal ini
ratchet freak
Saya setuju dengan @ratchetfreak. Tampaknya menjadi kompiler cacat karena rentang indeks yang valid adalah [0, 5]. Apa pun yang ada di luar harus merupakan kesalahan kompilasi / runtime. Seperti pada umumnya, vektor adalah kasus fungsi tertentu yang indeks elemen pertamanya tergantung pada pengguna. Karena kontrak C adalah elemen yang dimulai pada indeks 0, itu adalah kesalahan untuk mengakses elemen negatif.
Val
2
@Raphael C memiliki dua kekhasan atas bahasa yang khas dengan array yang penting di sini. Pertama adalah bahwa C memiliki sub -1-array dan merujuk ke elemen subarray adalah cara yang benar-benar valid untuk merujuk ke elemen sebelum array dalam array yang lebih besar. Yang lain adalah bahwa jika indeks tidak valid, program tidak valid, tetapi dalam kebanyakan implementasi Anda akan mendapatkan perilaku buruk yang diam, bukan kesalahan di luar jangkauan.
Gilles 'SANGAT berhenti menjadi jahat'
4
@Gilles Jika itu inti dari pertanyaan, ini seharusnya memang berada di Stack Overflow .
Raphael

Jawaban:

27

Operasi pengindeksan array a[i]mendapatkan artinya dari fitur-fitur C berikut

  1. Sintaksnya a[i]setara dengan *(a + i). Oleh karena itu sah untuk mengatakan 5[a]untuk mendapatkan elemen ke-5 dari a.

  2. Pointer-aritmatika mengatakan bahwa diberi pointer pdan integer i, p + i pointer pmaju oleh i * sizeof(*p)byte

  3. Nama array adengan cepat berpindah ke sebuah pointer ke elemen 0 daria

Akibatnya, pengindeksan array adalah kasus khusus pengindeksan pointer. Karena pointer dapat menunjuk ke suatu tempat di dalam array, setiap ekspresi arbitrer yang terlihat seperti p[-1]itu tidak salah dengan pemeriksaan, dan kompiler tidak (tidak bisa) menganggap semua ekspresi sebagai kesalahan.

Contoh Anda di a[-1]mana asebenarnya nama array sebenarnya tidak valid. IIRC, itu tidak terdefinisi jika ada nilai pointer yang berarti sebagai hasil dari ekspresi di a - 1mana adiketahui menjadi pointer ke elemen 0 array. Jadi, kompiler yang pintar bisa mendeteksi ini dan menandainya sebagai kesalahan. Kompiler lain masih dapat memenuhi persyaratan sementara memungkinkan Anda untuk menembak diri sendiri dengan memberikan pointer ke slot tumpukan acak.

Jawaban ilmu komputer adalah:

  • Dalam C, []operator didefinisikan pada pointer, bukan array. Secara khusus, itu didefinisikan dalam hal pointer aritmatika dan pointer dereference.

  • Dalam C, sebuah pointer secara abstrak adalah tuple (start, length, offset)dengan kondisi itu 0 <= offset <= length. Pointer aritmatika pada dasarnya mengangkat aritmatika pada offset, dengan peringatan bahwa jika hasil operasi melanggar kondisi pointer, itu adalah nilai yang tidak ditentukan. De-referensi pointer menambahkan kendala tambahan itu offset < length.

  • C memiliki gagasan undefined behaviouryang memungkinkan kompiler untuk secara konkret menyatakan bahwa tuple sebagai angka tunggal, dan tidak harus mendeteksi pelanggaran kondisi pointer. Setiap program yang memenuhi semantik abstrak akan aman dengan semantik konkret (lossy). Apa pun yang melanggar semantik abstrak dapat, tanpa komentar, diterima oleh kompiler dan dapat melakukan apa pun yang ingin dilakukan dengannya.

Hari
sumber
Cobalah memberikan jawaban umum, bukan jawaban tergantung pada keistimewaan bahasa pemrograman tertentu.
Raphael
6
@ Raphael, pertanyaannya secara eksplisit tentang C. Saya pikir saya membahas pertanyaan spesifik mengapa kompiler C diizinkan untuk mengkompilasi ekspresi yang tampaknya tidak berarti dalam definisi C.
Hari
Pertanyaan-pertanyaan tentang C khususnya bersifat offtopic di sini; catat komentar saya pada pertanyaan.
Raphael
5
Saya percaya aspek linguistik komparatif dari pertanyaan ini masih berguna. Saya percaya saya memberikan deskripsi rasa "ilmu komputer" yang cukup tentang mengapa implementasi spesifik menunjukkan semantik konkret tertentu.
Hari
15

Array hanya ditata sebagai potongan memori yang berdekatan. Akses array seperti [i] dikonversi ke akses ke alamat lokasi memori. Dari (a) + i. Kode a[-1]ini dapat dimengerti dengan sempurna, hanya merujuk ke alamat satu sebelum dimulainya array.

Ini mungkin terlihat gila, tetapi ada banyak alasan mengapa ini diizinkan:

  • mahal untuk memeriksa apakah indeks i ke [-] berada dalam batas array.
  • beberapa teknik pemrograman sebenarnya memanfaatkan fakta yang a[-1]valid. Sebagai contoh, jika saya tahu bahwa asebenarnya bukan awal dari array, tetapi sebuah pointer ke tengah array, maka a[-1]cukup mendapatkan elemen array yang ada di sebelah kiri pointer.
Dave Clarke
sumber
6
Dengan kata lain, itu mungkin tidak boleh digunakan. Titik. Apa, nama Anda Donald Knuth dan Anda mencoba menyimpan 17 instruksi lainnya? Dengan segala cara, silakan.
Raphael
Terima kasih atas jawabannya, Tapi saya tidak mengerti. BTW saya akan membacanya berulang-ulang sampai saya mengerti .. :)
Mohammed Fawzan
2
@Raphael: Implementasi model objek cola menggunakan posisi -1 untuk menyimpan tabel : piumarta.com/software/cola/objmodel2.pdf . Dengan demikian bidang disimpan di bagian positif dari objek dan tabel di negatif. Saya tidak dapat mengingat detailnya, tetapi saya pikir ini berkaitan dengan konsistensi.
Dave Clarke
@ DeZéroToxin: Array benar-benar hanya sebuah lokasi di memori, dengan beberapa lokasi di sebelahnya yang secara logis merupakan bagian dari array. Tapi sungguh, sebuah array hanyalah sebuah pointer.
Dave Clarke
1
@Raphael, a[-1]masuk akal untuk beberapa kasus a, dalam kasus khusus ini jelas ilegal (tetapi tidak ditangkap oleh kompiler)
vonbrand
4

Seperti jawaban lain menjelaskan, ini adalah perilaku yang tidak terdefinisi dalam C. Pertimbangkan bahwa C didefinisikan (dan sebagian besar digunakan) sebagai "assembler tingkat tinggi". Pengguna C menghargainya karena kecepatannya yang tidak kenal kompromi, dan memeriksa hal-hal saat runtime (sebagian besar) di luar pertanyaan demi kinerja semata. Beberapa konstruksi C yang terlihat tidak masuk akal untuk orang-orang yang datang dari bahasa lain masuk akal dalam bahasa C, seperti ini a[-1]. Ya, itu tidak selalu masuk akal (

vonbrand
sumber
1
Saya suka jawaban ini. Memberikan alasan nyata mengapa ini tidak masalah.
darxsys
3

Seseorang dapat menggunakan fitur seperti itu untuk menulis metode alokasi memori yang mengakses memori secara langsung. Salah satu penggunaan tersebut adalah untuk memeriksa blok memori sebelumnya menggunakan indeks array negatif untuk menentukan apakah dua blok dapat digabungkan. Saya telah menggunakan fitur ini ketika saya mengembangkan manajer memori yang tidak mudah menguap.

Theron W Genaux
sumber
2

C tidak diketik dengan kuat. Kompiler C standar tidak akan memeriksa batas array. Hal lain adalah bahwa array dalam C hanyalah blok memori yang berdekatan dan pengindeksan dimulai pada 0 sehingga indeks -1 adalah lokasi dari bit-pattern apa pun sebelumnya a[0].

Bahasa lain mengeksploitasi indeks negatif dengan cara yang baik. Dalam Python, a[-1]akan mengembalikan elemen terakhir, a[-2]akan mengembalikan elemen kedua ke terakhir dan seterusnya.

saadtaame
sumber
2
Apa kaitan antara pengetikan dan indeks array yang kuat? Apakah ada bahasa dengan tipe untuk naturals di mana indeks array harus naturals?
Raphael
@ Raphael Sejauh yang saya tahu, pengetikan yang kuat berarti bahwa kesalahan ketik tertangkap. Array adalah tipe, IndexOutOfBounds adalah kesalahan sehingga dalam bahasa yang diketik dengan kuat ini akan dilaporkan, dalam C ini tidak akan. Itu yang saya maksud.
saadtaame
Dalam bahasa yang saya tahu, indeks array bertipe int, jadi a[-5]dan, lebih umum, int i; ... a[i] = ...;diketik dengan benar. Kesalahan indeks hanya terdeteksi pada saat runtime. Tentu saja, kompiler pintar dapat mendeteksi beberapa pelanggaran.
Raphael
@ Raphael Saya berbicara tentang tipe data array secara keseluruhan, bukan tipe indeks. Itu menjelaskan mengapa C memungkinkan pengguna untuk menulis [-5]. Ya, -5 adalah tipe indeks yang benar tetapi di luar batas dan itu adalah kesalahan. Tidak ada menyebutkan tipe kompilasi atau runtime yang memeriksa jawaban saya.
saadtaame
1

Dengan kata sederhana:

Semua variabel (termasuk array) di C disimpan dalam memori. Katakanlah Anda memiliki 14 byte "memori" dan Anda menginisialisasi yang berikut:

int a=0;
int array1[6] = {0, 1, 2, 3, 4, 5};

Juga, pertimbangkan ukuran int sebagai 2 byte. Kemudian, secara hipotetis, dalam 2 byte pertama memori integer a akan disimpan. Dalam 2 byte berikutnya integer posisi pertama array akan disimpan (itu berarti array [0]).

Kemudian, ketika Anda mengatakan array [-1] seperti merujuk ke integer yang disimpan dalam memori yang tepat sebelum array [0], yang dalam kita adalah, secara hipotesis, integer a. Pada kenyataannya, ini bukan cara variabel disimpan dalam memori.

Dchris
sumber
0
//:Example of negative index:
//:A memory pool with a heap and a stack:

unsigned char memory_pool[64] = {0};

unsigned char* stack = &( memory_pool[ 64 - 1] );
unsigned char* heap  = &( memory_pool[ 0     ] );

int stack_index =    0;
int  heap_index =    0;

//:reserve 4 bytes on stack:
stack_index += 4;

//:reserve 8 bytes on heap:
heap_index  += 8;

//:Read back all reserved memory from stack:
for( int i = 0; i < stack_index; i++ ){
    unsigned char c = stack[ 0 - i ];
    //:do something with c
};;
//:Read back all reserved memory from heap:
for( int i = 0; i < heap_index; i++ ){
    unsigned char c = heap[ 0 + i ];
    //:do something with c
};;
JMI MADISON
sumber
Selamat datang di CS.SE! Kami sedang mencari jawaban yang datang dengan penjelasan atau deskripsi bacaan. Kami bukan situs pengkodean, dan kami tidak ingin jawaban yang hanya berupa blok kode. Anda dapat mempertimbangkan apakah Anda dapat mengedit jawaban Anda untuk memberikan informasi semacam itu. Terima kasih!
DW