Negatif dizi indeksleri neden anlamlı?


14

C programlamasında garip bir deneyim yaşadım. Bu kodu düşünün:

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

Bunu derleyip çalıştırdığımda herhangi bir hata veya uyarı almıyorum. Öğretim elemanımın dediği gibi dizi dizini -1başka bir değişkene erişir. Hala kafam karıştı, neden dünyada bir programlama dili bu yeteneğe sahip? Yani, negatif dizi indekslerine neden izin verelim?


2
Bu soru C ile somut programlama dili olarak motive edilirken, bence burada ontopik olan (ancak eğer değilse) kavramsal bir soru olarak anlaşılabilir.
Raphael

7
@ Raphael katılmıyorum ve SO'ya ait olması gerektiğine inanıyorum, her iki durumda da bu ders kitabı tanımlanmamış davranış (dizinin dışındaki belleğe gönderme) ve uygun derleyici bayrakları bu konuda uyarmalıdır
cırcır ucube

@Ratchetfreak'e katılıyorum. Geçerli indeks aralığı [0, 5] olduğu için derleyici hatası gibi görünüyor. Dışarıda olan her şey bir derleme / çalışma zamanı hatası olmalıdır. Genel olarak, vektörler , ilk eleman endeksi kullanıcıya bağlı olan fonksiyonların özel durumudur . C sözleşmesi, öğelerin dizin 0'dan başlaması nedeniyle, negatif öğelere erişim hatasıdır.
Val

2
@ Raphael C'nin burada önemli olan dizilerle tipik diller üzerinde iki özelliği vardır. Birincisi, C'nin alt -1diziler içermesidir ve bir alt dizinin elemanına başvurmak, daha büyük dizideki diziden önceki öğeye başvurmak için mükemmel geçerli bir yoldur. Diğeri ise, dizin geçersizse program geçersizdir, ancak çoğu uygulamada aralık dışı bir hata değil sessiz kötü davranış elde edersiniz.
Gilles 'SO- kötü olmayı bırak'

4
@Gilles Sorunun konusu buysa, bu gerçekten Stack Overflow'da olmalıydı .
Raphael

Yanıtlar:


27

Dizi indeksleme işlemi a[i], C'nin aşağıdaki özelliklerinden anlam kazanır

  1. Sözdizimi a[i]ile eşdeğerdir *(a + i). Bu nedenle 5[a], 'nin 5. elementine girmek demek geçerlidir a.

  2. Pointer-aritmetik bir işaretçi verilen söylüyor pve bir tamsayı i, p + i işaretçi ptarafından ileri i * sizeof(*p)bayt

  3. Bir dizinin adı açok hızlı bir şekilde 0 öğesinin işaretçisine dönüşüra

Aslında, dizi indeksleme işaretçi indeksleme için özel bir durumdur. Bir işaretçi görünüyor gibi bu bir dizi, içindeki herhangi bir yere herhangi bir keyfi ifadesini işaret edebilir yana p[-1]olduğunu değil sınavla yanlış ve derleyiciler yok bu yüzden hatalar olarak tüm bu ifadeleri dikkate (olamaz).

Sizin örnek aslında bir dizinin adıdır aslında geçersiz. İfade sonucu olarak anlamlı bir işaretçi değeri varsa IIRC, tanımlanmamış bir dizinin 0. öğe için bir işaretçi olarak, bilinen bir yöntemdir. Böylece, akıllı bir derleyici bunu algılayabilir ve bir hata olarak işaretleyebilir. Diğer derleyiciler hala rastgele bir yığın yuvasına bir işaretçi vererek kendinizi ayağa vurmanıza izin verirken uyumlu olabilir.a[-1]aa - 1a

Bilgisayar bilimi yanıtı:

  • C'de, []operatör dizilerde değil, işaretçilerde tanımlanır. Özellikle, işaretçi aritmetiği ve işaretçi dereference olarak tanımlanır.

  • C'de bir işaretçi soyut (start, length, offset)olarak bu koşulu taşıyan bir demettir 0 <= offset <= length. İşaretçi aritmetiği esasen ofset üzerinde aritmetik olarak kaldırılır, işlemin sonucu işaretçi koşulunu ihlal ederse tanımlanmamış bir değer olduğu uyarısı ile. İşaretçinin referansını kaldırmak ek bir kısıtlama ekler offset < length.

  • C, bir undefined behaviourderleyicinin bu tupu tek bir sayı olarak somut olarak temsil etmesine izin veren ve işaretçi durumunun herhangi bir ihlalini tespit etmesi gerekmeyen bir nosyona sahiptir . Soyut semantiği tatmin eden herhangi bir program somut (kayıplı) semantik ile güvende olacaktır. Soyut semantiği ihlal eden herhangi bir şey, yorum yapmadan derleyici tarafından kabul edilebilir ve onunla yapmak istediği her şeyi yapabilir.


Lütfen herhangi bir programlama dilinin kendine özgü ifadelerine bağlı olarak değil, genel bir cevap vermeye çalışın.
Raphael

6
@Raphael, soru açıkça C ile ilgiliydi. Sanırım bir C derleyicisinin C'nin tanımı içinde görünüşte anlamsız bir ifadeyi neden derlemesine izin verildiğine dair özel bir soruyu ele aldım
Hari

Özellikle C ile ilgili sorular burada sorun değil; soru hakkındaki yorumuma dikkat edin.
Raphael

5
Sorunun karşılaştırmalı dilbilimsel yönünün hala yararlı olduğuna inanıyorum. Belirli bir uygulamanın neden belirli bir somut anlambilimi sergilediğine dair oldukça "bilgisayar bilimi" aromalı bir açıklama yaptığımı düşünüyorum.
Hari

15

Diziler basitçe bitişik bellek parçaları olarak düzenlenir. [İ] gibi bir dizi erişimi, bellek konumu adresiOf (a) + i'ye erişime dönüştürülür . Bu kod a[-1]mükemmel şekilde anlaşılabilir, sadece dizinin başlamasından önceki adrese atıfta bulunur.

Bu çılgın görünebilir, ancak buna izin verilmesinin birçok nedeni vardır:

  • bir [-] dizinine i dizisinin sınırlar içinde olup olmadığını kontrol etmek pahalıdır.
  • bazı programlama teknikleri aslında a[-1]geçerli olanı kullanır. Örneğin, aaslında dizinin başlangıcı değil, dizinin ortasına bir işaretçi olduğunu biliyorsanız, a[-1]basitçe işaretçinin solundaki dizinin elemanını alır.

6
Başka bir deyişle, muhtemelen kullanılmamalıdır. Dönemi. Ne, adın Donald Knuth ve 17 talimat daha kaydetmeye çalışıyorsun? Elbette, devam et.
Raphael

Cevabınız için teşekkürler, Ama fikri anlayamadım. BTW
Anlayana

2
@Raphael: Kola nesne modelinin uygulanması, vtable'ı depolamak için -1 konumunu kullanır: piumarta.com/software/cola/objmodel2.pdf . Böylece alanlar, nesnenin pozitif kısmında, negatif ise vtable içinde depolanır. Ayrıntıları hatırlayamıyorum, ama bunun tutarlılıkla ilgili olduğunu düşünüyorum.
Dave Clarke

@ DeZéroToxin: Bir dizi gerçekten sadece bellekteki bir konumdur, yanında bazı konumlar mantıksal olarak dizinin bir parçasıdır. Ama gerçekten, bir dizi sadece bir işaretçi.
Dave Clarke

1
@ Raphael, bazı durumlar a[-1]için mükemmel bir mantıklı , bu özel durumda yasadışı yasadışı (ancak derleyici tarafından yakalanmadı)a
vonbrand

4

Diğer cevapların açıkladığı gibi, bu C'de tanımlanmamış bir davranıştır . C'nin bir "yüksek seviye birleştirici" olarak tanımlandığını (ve çoğunlukla kullanıldığını) düşünün. C'nin kullanıcıları ödün vermeyen hızı nedeniyle buna değer veriyorlar ve çalışma zamanında bir şeyleri kontrol etmek (çoğunlukla) sırf performans uğruna söz konusu değil. Diğer dillerden gelen insanlar için saçma görünen bazı C yapıları, C'de bu şekilde mükemmel mantıklıdır a[-1]. Evet, her zaman mantıklı değil (


1
Bu cevabı beğendim. Bunun neden iyi olduğu için gerçek bir neden verir.
darxsys

3

Doğrudan belleğe erişen bellek ayırma yöntemleri yazmak için böyle bir özellik kullanılabilir. Böyle bir kullanım, iki bloğun birleştirilip birleştirilemeyeceğini belirlemek için önceki bellek bloğunu negatif dizi dizini kullanarak kontrol etmektir. Kalıcı bir bellek yöneticisi geliştirdiğimde bu özelliği kullandım.


2

C güçlü yazılmamıştır. Standart bir C derleyicisi dizi sınırlarını kontrol etmez. Diğer bir şey, C'deki bir dizinin bitişik bir bellek bloğundan başka bir şey olmaması ve indekslemenin 0'da başlamasıdır, bu nedenle -1 endeksi, daha önce bit deseninin bulunduğu konumdur a[0].

Diğer diller olumsuz endeksleri hoş bir şekilde kullanır. Python'da, a[-1]son öğeyi a[-2]döndürür, ikinci öğeden son öğeye döndürür vb.


2
Güçlü yazma ve dizi indeksleri arasındaki ilişki nedir? Dizi indekslerinin doğal olması gereken türde doğal diller var mı?
Raphael

@Raphael Bildiğim kadarıyla, güçlü yazma, yazım hatalarının yakalandığı anlamına gelir. Bir dizi bir türdür, IndexOutOfBounds bir hatadır, bu nedenle güçlü bir şekilde yazılan bir dilde bu rapor edilir, C'de bu olmaz. Demek istediğim şey o.
saadtaame

Bildiğim diller dizi indisleri tiptedir int, bu nedenle a[-5]ve daha genel int i; ... a[i] = ...;doğru yazılır. Dizin hataları yalnızca çalışma zamanında algılanır. Elbette, akıllı bir derleyici bazı ihlalleri tespit edebilir .
Raphael

@Raphael Dizin türleri değil, bir bütün olarak dizi veri türü hakkında konuşuyorum. Bu, C'nin neden kullanıcıların [-5] yazmasına izin verdiğini açıklar. Evet, -5 doğru dizin türüdür ancak sınırların dışındadır ve bu bir hatadır. Cevabımda derleme veya çalışma zamanı türü denetiminden söz edilmiyor.
saadtaame

1

Basit bir deyişle:

C'deki tüm değişkenler (diziler dahil) bellekte saklanır. Diyelim ki 14 baytlık "belleğiniz" var ve aşağıdakileri başlattınız:

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

Ayrıca, int boyutunu 2 bayt olarak düşünün. Daha sonra varsayımsal olarak, belleğin ilk 2 baytında a tamsayısı kaydedilir. Sonraki 2 baytta dizinin ilk konumunun tamsayısı kaydedilir (yani [0] dizisi anlamına gelir).

Sonra, [-1] dizisi, bellekte kaydedilen tamsayıdan [0] dizisinden hemen önce olan ve bizim varsayımsal olarak, a tamsayısı anlamına gelir. Gerçekte, bu değişkenlerin hafızada depolanma şekli değildir.


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

CS.SE'ye Hoşgeldiniz! Okumanın açıklaması veya açıklaması ile gelen cevapları arıyoruz. Biz bir kodlama sitesi değiliz ve sadece bir kod bloğu olan cevaplar istemiyoruz. Bu tür bilgileri sağlamak için cevabınızı düzenleyip düzenleyemeyeceğinizi düşünebilirsiniz . Teşekkür ederim!
DW
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.