Soru Dizi bozunumu nedir?


Bir dizinin bozulması nedir? Dizi işaretçileri ile herhangi bir ilişki var mı?


281
2017-09-22 17:24


Menşei


az bilinen: unary plus operatörü bir "çürüme operatörü" olarak kullanılabilir: Verilen int a[10]; int b(void);, sonra +a bir int işaretçisi ve +b bir işlev işaretçisidir. Referans kabul eden bir şablona aktarmak istiyorsanız kullanışlıdır. - Johannes Schaub - litb
@litb - parens aynısını yapar (örneğin, (a) bir işaretçiye değerlendiren bir ifade olmalıdır), doğru mu? - Michael Burr
std::decay C ++ 14'ten bir dizi unary + üzerinde çürümenin daha az belirsiz bir yolu olurdu. - legends2k
@ JohannesSchaub-litb bu soru hem C hem de C ++ olarak etiketlendiğinden beri bunu açıklığa kavuşturmak isterim +a ve +b C ++ 'da yasaldır, C' de yasadışıdır (C11 6.5.3.3 / 1 "Tekli işlenen + veya - operatör aritmetik tipine sahip olacaktır.) - M.M
@lege Sağ. Ama sanırım bu, unary + ile hile olarak bilinen bir şey değil. Bahsettiğim nedeni sadece çürümediği için değil, çünkü oynamak için bazı eğlenceli şeyler çünkü;) - Johannes Schaub - litb


Cevaplar:


Dizilerin işaretçilere "çürüme" dediği söyleniyor. Olarak bildirilen bir C ++ dizisi int numbers [5] yeniden işaret edilemez, yani söyleyemezsiniz numbers = 0x5a5aff23. Daha önemlisi çürüme terimi, tür ve boyut kaybını ifade eder; numbers çürütmek int* boyut bilgilerini (sayı 5) kaybederek ve türünü değil int [5] daha fazla Buraya bakın çürümenin olmadığı durumlar.

Bir diziyi değere göre geçiriyorsanız, gerçekten yaptığınız şey bir işaretçiyi kopyalamaktır - dizinin ilk öğesinin işaretçisi, parametreye kopyalanır (türü, dizinin öğesi öğesinin bir işaretçisi olmalıdır). Bu dizinin çürüyen doğası nedeniyle çalışır; bir kez bozuldu, sizeof Artık tam dizinin boyutunu vermez, çünkü aslında bir işaretçi olur. Bu nedenle (diğer nedenlerin yanında) referans veya işaretçiden geçmek tercih edilir.

Dizide geçmek için üç yol1:

void by_value(const T* array)   // const T array[] means the same
void by_pointer(const T (*array)[U])
void by_reference(const T (&array)[U])

Son iki uygun verecek sizeof Birincisi, dizi argümanı parametreye atanması için çürüdüğünden beri birincisi olmaz.

1 Sabit U, derleme zamanında bilinmelidir.


207
2017-09-22 17:29



İlk değer nasıl geçiyor? - rlbond
by_value, dizinin ilk elemanına bir işaretçi iletiyor; fonksiyon parametreleri bağlamında, T a[] aynıdır T *a. by_pointer aynı şeyi geçiyor, işaretçi değeri artık yeterlilikte const. Bir işaretçiyi geçmek istiyorsanız diziye (dizinin ilk öğesinin bir işaretçisinin aksine), sözdizimi T (*array)[U]. - John Bode
"bu diziye açık bir işaretçi" - bu yanlış. Eğer a bir dizi char, sonra a tipi char[N]ve bozulacak char*; fakat &a tipi char(*)[N]ve olacak değil çürüme. - Pavel Minaev
@FredOverflow: Yani eğer U iki yerde değiştirmeyi veya sessiz hataları riske atmayı hatırlamanıza gerek yok değişiklikler ... Özerklik! - Lightness Races in Orbit
"Bir diziyi değere göre geçiriyorsanız, gerçekten yaptığınız şey bir işaretçiyi kopyalamaktır" Bu bir anlam ifade etmemektedir, çünkü diziler değerden geçemez, dönem. - juanchopanza


Diziler temel olarak C / C ++ 'daki işaretçilerle aynıdır, ancak tam olarak değil. Bir diziyi dönüştürdüğünüzde:

const int a[] = { 2, 3, 5, 7, 11 };

bir işaretçiye (hangi döküm olmadan çalışır ve bu nedenle bazı durumlarda beklenmedik bir şekilde gerçekleşebilir):

const int* p = a;

yeteneğini kaybedersiniz sizeof dizideki öğeleri saymak için operatör:

assert( sizeof(p) != sizeof(a) );  // sizes are not equal

Bu kayıp yeteneği "çürüme" olarak adlandırılır.

Daha fazla bilgi için, bunu kontrol edin dizi bozulma hakkında makale.


76
2017-09-22 17:31



Diziler değil temelde işaretçilerle aynıdır; tamamen farklı hayvanlar. Çoğu bağlamda, bir dizi tedavi edilebilir sanki bir işaretçiydi ve bir işaretçi tedavi edilebilir sanki Bir dizi vardı, ama onlar kadar yakın. - John Bode
@John, lütfen kesin olmayan dilimi affedin. Uzun bir backstory içinde bogged olmadan cevap almak için çalışıyordum ve "temelde ... ama tam olarak değil" kolejde var gibi bir açıklama kadar iyi. İlgilenen yorumlardan daha doğru bir resim alabileceğine eminim. - system PAUSE
"yayınlamadan çalışır", tür dönüşümleri hakkında konuşurken "örtük olarak gerçekleşir" ifadesini kullanır. - M.M


İşte standardın söylediği (C99 6.3.2.1/3 - Diğer işlenenler - Lvalues, diziler ve işlev göstergeleri):

Sizeof operatörünün veya unary & operatörün işleneni dışında veya   Bir diziyi başlatmak için kullanılan literal dosya dizgesi, "type of type" türünde bir ifadedir.   element ‘türüne imleç with with ile bir ifadeye dönüştürülür.   dizi nesnesi ve bir değer değil.

Bu, dizi adının her zaman bir ifadede kullanıldığı zaman, dizideki ilk öğeye otomatik olarak bir göstericiye dönüştürüldüğü anlamına gelir.

İşlev adlarının benzer bir şekilde davrantığını unutmayın, ancak işlev işaretçileri çok daha az kullanılır ve çok daha özel bir şekilde kullanılır; bu, dizi adlarının işaretçilere otomatik olarak dönüştürülmesi kadar neredeyse karışıklığa neden olmaz.

C ++ standardı (4.2 Array - işaretçi dönüşümü) dönüştürme gereksinimini (vurgu vurgulama) gevşetir:

“N dizisi” veya “T'nin bilinmeyen bir dizisi” tipi bir değer veya bir değer kutu bir değere dönüştürülebilir   “işaretçi” ye

Yani dönüşüm yok var Neredeyse her zaman C'de olduğu gibi gerçekleşir (bu, işlevlerin aşırı yüklenmesine veya şablonların dizi türünde eşleşmesine olanak tanır).

Bu aynı zamanda C'de fonksiyon prototipleri / tanımlarında dizi parametrelerinden kaçınmalısınız (bence - herhangi bir genel anlaşma olup olmadığından emin değilim). Kafa karışıklığına neden olurlar ve bir kurgu vardırlar - işaretçi parametrelerini kullanırlar ve kafa karışıklığı tamamen ortadan kalkmayabilir, ancak en azından parametre bildirimi yalan söylemez.


40
2017-09-22 19:27



Örnek bir "kod dizisi" türü "türünde" tür "dizisi" dizisinin "diziyi başlatmak için kullanılan bir dizgi" olduğu durum nedir? - Garrett
@Garrett char x[] = "Hello"; . 6 eleman dizisi "Hello" çürümez; yerine x boyut alır 6 ve elementleri "Hello". - M.M


"Decay" ifadesi, bir ifadenin bir dizi tipinden bir işaretçi türüne dolaylı olarak dönüştürülmesini ifade eder. Çoğu bağlamda, derleyici bir dizi ifadesini gördüğünde, ifadenin türünü "T öğesinin N-öğesi dizisinden" "işaretçiye" dönüştürür ve ifadenin değerini dizinin ilk öğesinin adresine ayarlar. . Bu kuralın istisnaları, bir dizinin ya sizeof veya & operatörler veya dizi, bir bildirimde başlatıcı olarak kullanılan bir dize değişmezidir.

Aşağıdaki kodu varsayalım:

char a[80];
strcpy(a, "This is a test");

İfade a "char adlı 80-eleman dizisidir" ifadesi ve "Bu bir testtir" ifadesi "char öğesinin 16 öğeli dizisidir" ifadesidir (C cinsinden C ++ dizesi değişmezleri const karakter dizisidir). Ancak, aramada strcpy()her iki ifade de sizeof veya &Bu nedenle, türleri örtülü olarak "char işaretçisine" dönüştürülür ve değerleri, her öğedeki ilk öğenin adresine ayarlanır. Ne strcpy() alır diziler değil, prototipinde görüldüğü gibi, işaretçiler:

char *strcpy(char *dest, const char *src);

Bu bir dizi işaretçi ile aynı şey değil. Örneğin:

char a[80];
char *ptr_to_first_element = a;
char (*ptr_to_array)[80] = &a;

Her ikisi de ptr_to_first_element ve ptr_to_array aynı değer; a. Bununla birlikte, bunlar farklı türlerdir ve aşağıda gösterildiği gibi farklı muamele görürler:

a[i] == ptr_to_first_element[i] == (*ptr_to_array)[i] != *ptr_to_array[i] != ptr_to_array[i]

İfadeyi hatırla a[i] olarak yorumlanır *(a+i) (yalnızca dizi türü bir işaretçi türüne dönüştürülürse çalışır) a[i] ve ptr_to_first_element[i] aynısını yap. İfade (*ptr_to_array)[i] olarak yorumlanır *(*a+i). İfadeler *ptr_to_array[i] ve ptr_to_array[i] içeriğe bağlı olarak derleyici uyarılarına veya hatalarına yol açabilir; Değerlendirmek için onları bekliyorsanız kesinlikle yanlış bir şey yapacaklar a[i].

sizeof a == sizeof *ptr_to_array == 80

Yine, bir dizi bir işleneni olduğunda sizeofişaretçi türüne dönüştürülmez.

sizeof *ptr_to_first_element == sizeof (char) == 1
sizeof ptr_to_first_element == sizeof (char *) == whatever the pointer size
                                                  is on your platform

ptr_to_first_element char için basit bir işaretçidir.


22
2017-09-22 20:21



değil "This is a test" is of type "16-element array of char" bir "15-element array of char"? (\ 0 için uzunluk 14 + 1) - chux


C'deki dizilerin değeri yoktur.

Bir nesnenin değeri bekleniyorsa, ancak nesne bir dizidir, ilk öğesinin adresi yerine, türüyle kullanılır. pointer to (type of array elements).

Bir fonksiyonda, tüm parametreler değer olarak geçirilir (diziler istisna değildir). Bir işlevi bir dizide geçirdiğinizde "bir işaretçiye dönüşür" (sic); Bir diziyi başka bir şeyle karşılaştırdığınızda, yine "işaretçiye dönüşür" (sic); ...

void foo(int arr[]);

Foo fonksiyonu bir dizinin değerini bekler. Fakat, C'de, dizilerin değeri yoktur! Yani foo bunun yerine dizinin ilk elemanının adresini alır.

int arr[5];
int *ip = &(arr[1]);
if (arr == ip) { /* something; */ }

Yukarıdaki karşılaştırmada, arr hiçbir değeri yoktur, bu yüzden bir işaretçi olur. İnt için bir işaretçi olur. Bu işaretçi değişkenle karşılaştırılabilir ip.

Dizi indeksleme sözdiziminde tekrar görmek için kullanılır, arr 'bir işaretçiye bozulur'

arr[42];
/* same as *(arr + 42); */
/* same as *(&(arr[0]) + 42); */

Bir dizinin bir işaretçiye dönüşmediği tek zaman, sizeof işlecinin işleneni veya & işlecinin ('işlecin' adresi ') veya bir karakter dizisini başlatmak için kullanılan bir dize değişmezidir.


12
2017-09-22 17:55



"Dizilerin değeri yok" - bunun anlamı ne? Elbette dizilerin değeri var ... onlar nesneler, işaretçiler olabilir, ve C ++ 'da bunlara referanslar, vb. - Pavel Minaev
Kesinlikle, "Değer" in C cinsinden bir nesnenin bit türünün bir türüne göre yorumlanması olarak tanımlandığına inanıyorum. Bir dizi tipi ile bunun yararlı bir anlamını bulmakta zorlanıyorum. Bunun yerine, bir işaretçiye dönüştürdüğünüzü söyleyebilirsin, ancak bu dizinin içeriğini yorumlamıyor, sadece yerini alıyor. Ne elde ederseniz, bir işaretçinin değeri (ve bu bir adres), bir dizinin değeri değil ("dizgenin" tanımında kullanılan "içerilen öğelerin değerlerinin sırası"). Dedi ki, bir işaretçi aldığında "dizinin değeri" demek adil. - Johannes Schaub - litb
Her neyse, biraz belirsizliğin olduğunu düşünüyorum: Bir nesnenin değeri ve bir ifadenin değeri ("rvalue" gibi). Eğer ikinci yoldan yorumlanırsa, o zaman bir dizi ifadesi kesinlikle bir değere sahiptir: Bu onu bir değere indirgemekten kaynaklanan ve işaretçi ifadesidir. Ancak eski yoldan yorumlanırsa, o zaman elbette bir dizi nesnesi için yararlı bir anlam yoktur. - Johannes Schaub - litb
Küçük bir düzeltme ile ifade için +1; diziler için sadece bir beyit değil [konum, tip]. Dizi durumunda üçüncü konum için başka bir şey var mıydı? Ben hiç düşünemiyorum. - legends2k
@ legends2k: Sanırım, dizilerdeki üçüncü lokasyonu, sadece bir beyne sahip olma özel bir durumdan kaçınmak için kullandım. Belki [konum, yazın, geçersiz] daha iyi olurdu. - pmg


Bu dizi rots ve noktaya işaret ediliyor ;-)

Aslında, bir diziyi bir yere iletmek istiyorsanız, ancak bunun yerine işaretçiyi geçirirsiniz (çünkü sizin için tüm diziyi kim cehennemden geçirir?), İnsanlar zayıf dizinin işaretçiye dönüştüğünü söylüyor.


6
2017-09-22 17:28



Güzelce dedi. Bir işaretçiye ya da çürümeye engel olan bir bozucu bozucu olmayan güzel bir dizi ne olurdu? C'de bir örnek gösterebilir misin? Teşekkürler. - Unheilig
@Unheilig, elbette, bir diziyi yapıya yerleştirebilir ve yapıyı geçirebilir. - Michael Krelin - hacker
"İş" ile ne demek istediğinden emin değilim. Dizinin geçmişine erişmesine izin verilmez, ancak gerçekte ne olacağını beklerseniz beklendiği gibi çalışır. Bu davranış (yine resmi olarak tanımlanmamış) korunur. - Michael Krelin - hacker
Bozulma, diziyi herhangi bir yerde (diğer cevaplarda tarif edildiği gibi) geçmeyen birçok durumda da gerçekleşir. Örneğin, a + 1 . - M.M


Dizi bozulması, bir dizinin bir işleve bir parametre olarak geçirilmesi durumunda, bir işaretçiyi ("bozulacak") ile aynı şekilde ele alınır.

void do_something(int *array) {
  // We don't know how big array is here, because it's decayed to a pointer.
  printf("%i\n", sizeof(array));  // always prints 4 on a 32-bit machine
}

int main (int argc, char **argv) {
    int a[10];
    int b[20];
    int *c;
    printf("%zu\n", sizeof(a)); //prints 40 on a 32-bit machine
    printf("%zu\n", sizeof(b)); //prints 80 on a 32-bit machine
    printf("%zu\n", sizeof(c)); //prints 4 on a 32-bit machine
    do_something(a);
    do_something(b);
    do_something(c);
}

Yukarıdakiler için iki komplikasyon veya istisna vardır.

Birincisi, C ve C ++ 'da çok boyutlu dizilerle uğraşırken, sadece ilk boyut kaybolur. Bunun nedeni, dizilerin bellekte bitişik olarak düzenlenmesidir, dolayısıyla derleyici, ofsetleri bellek bloğuna hesaplayabilen ilk boyuttan her şeyi bilmelidir.

void do_something(int array[][10])
{
    // We don't know how big the first dimension is.
}

int main(int argc, char *argv[]) {
    int a[5][10];
    int b[20][10];
    do_something(a);
    do_something(b);
    return 0;
}

İkincisi, C ++ 'da, dizilerin boyutunu çıkarmak için şablonları kullanabilirsiniz. Microsoft, bu gibi Güvenli CRT işlevlerinin C ++ sürümleri için kullanır. strcpy_sve güvenilir bir şekilde benzer bir numara kullanabilirsiniz bir dizi içindeki elemanların sayısını almak.


1
2017-09-22 17:35



çürüme, diğer bir çok durumda gerçekleşir, sadece bir diziyi bir işleve geçirmekten ibaret değildir. - M.M


tl; dr: Tanımladığınız bir diziyi kullandığınızda, aslında ilk elemanına bir işaretçi kullanırsınız.

Böylece:

  • Yazarken arr[idx] gerçekten sadece söylüyorsun *(arr + idx).
  • bir dizi parametresi belirttiğinizde bile işlevler dizileri gerçekten yalnızca parametre olarak almaz.

Bu kuralın istisnaları istisna:

  • Sabit uzunluktaki dizileri, işlevler içindeki struct.
  • sizeof() Bir işaretçinin boyutu değil, dizi tarafından alınan boyut verir.

0
2018-03-13 09:59