Dizi-işaretçi çürümesi nedir?


384

Dizi-işaretçi çürümesi nedir? Dizi işaretçileriyle herhangi bir ilişkisi var mı?


73
az bilinen: tekli artı operatörü bir "bozunma operatörü" olarak kullanılabilir: Verilen int a[10]; int b(void);, o +azaman bir int işaretçi ve +bbir işlev işaretçi. Referans kabul eden bir şablona iletmek istiyorsanız kullanışlıdır.
Johannes Schaub - litb

3
@litb - parens aynısını yapar (örneğin, (a) bir işaretçiye göre değerlendirilen bir ifade olmalıdır), değil mi?
Michael Burr

21
std::decayC ++ 14'ten, bir diziyi tekli + üzerine çürütmenin daha az belirsiz bir yolu olurdu.
legends2k

21
Bu soru C ve C hem etiketli beri @ JohannesSchaub-LITB ++, ben her ne kadar olduğunu açıklamak istiyorum +ave +bC ++ 'yasal, o C (yasadışı C11 6.5.3.3/1 "tekli göreni +veya -operatör sahip olacaktır aritmetik türü ")
MM

5
@ lege Doğru. Ama sanırım bu unary + ile hile kadar az bilinmemektedir. Bahsetmemin nedeni sadece çürümesi değil, oynamak için eğlenceli şeyler olduğu için;)
Johannes Schaub - litb

Yanıtlar:


283

Dizilerin işaretçilere "bozulduğu" söylenir. Olarak bildirilen bir C ++ dizisi int numbers [5]yeniden işaret edilemez, yani söyleyemezsiniz numbers = 0x5a5aff23. Daha da önemlisi, bozulma terimi tip ve boyut kaybını ifade eder; boyut bilgisini (sayı 5) kaybederek numbersbozunur int*ve tür artık olmaz int [5]. Çürümenin gerçekleşmediği durumlar için buraya bakın .

Bir diziyi değere göre geçiriyorsanız, gerçekte yaptığınız şey bir işaretçi kopyalamaktır - dizinin ilk öğesine bir işaretçi parametreye kopyalanır (türü de dizi öğesinin türünü gösteren bir işaretçi olmalıdır). Bu, dizinin çürüyen doğası nedeniyle çalışır; Bir kez çürüdüğünde, sizeofartık tam bir işaretçi boyutu vermez, çünkü aslında bir işaretçi olur. Bu nedenle (diğer nedenlerin yanı sıra) referans veya işaretçi ile geçilmesi tercih edilir.

Bir dizi 1'e geçmenin üç yolu :

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 ikisi uygun sizeofbilgi verirken, ilki dizi bağımsız değişkeni parametreye atanmak üzere bozulduğundan beri vermez.

1 U sabiti derleme zamanında bilinmelidir.


8
İlk değere göre geçiş nasıl?
rlbond

10
by_value, dizinin ilk öğesine bir işaretçi geçiriyor; fonksiyon parametreleri bağlamında, T a[]ile aynıdır T *a. by_pointer aynı şeyi geçiyor, ancak işaretçi değeri artık geçerli const. Diziye bir işaretçi iletmek istiyorsanız ( dizinin ilk öğesine işaretçi yerine), sözdizimi kullanılır T (*array)[U].
John Bode

4
"bu diziye açık bir işaretçi ile" - bu yanlıştır. Eğer abir dizi ise char, o zaman abir türdür char[N]ve char*; ama &atiptedir char(*)[N]ve edecektir değil çürüme.
Pavel Minaev

5
@FredOverflow: Yani Udeğişiklik yaparsanız iki yerde değiştirmeyi veya sessiz hataları riske atmayı hatırlamanız gerekmez ... Özerklik!
Orbit'te Hafiflik Yarışları

4
"Bir diziyi değere göre geçiriyorsanız, gerçekten yaptığınız şey bir işaretçiyi kopyalamaktır.
juanchopanza

103

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

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

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

const int* p = a;

sizeofoperatörün dizideki öğeleri sayma yeteneğini kaybedersiniz :

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

Bu kayıp yeteneğe "çürüme" denir.

Daha fazla ayrıntı için dizi bozulması hakkında bu makaleye göz atın .


51
Diziler temelde işaretçilerle aynı değildir ; tamamen farklı hayvanlardır. En bağlamlarda, bir dizi tedavi edilebilir sanki bir işaretçi vardı ve bir işaretçi tedavi edilebilir sanki bir dizi vardı, ama yakın olarak o en aldıkları olarak.
John Bode

20
@John, lütfen kesin dilimi affedin. Ben uzun bir arka planda bataklık olmadan cevap almaya çalışıyordum ve "temelde ... ama tam olarak değil" şimdiye kadar üniversitede aldığım kadar iyi bir açıklama. İlgilenen herkesin daha önce onaylanmış yorumunuzdan daha doğru bir resim alabileceğinden eminim.
sistem DURAKLAT

"döküm olmadan çalışır", tür dönüşümleri hakkında konuşurken "dolaylı olarak gerçekleşir" ile aynı anlama gelir
MM

47

Standardın söylediği (C99 6.3.2.1/3 - Diğer işlenenler - Değerler, diziler ve fonksiyon göstergeleri):

Sizeof operatörünün veya tekli & operatörünün işleneni veya bir diziyi başlatmak için kullanılan bir dize değişmezi dışında, '' tür dizisi '' türüne sahip bir ifade, '' işaretçisi türündeki bir ifadeye dönüştürülür. dizi nesnesinin ilk öğesini işaret eden ve bir lvalue olmayan '' yazın.

Bu, bir ifadede dizi adı her kullanıldığında, otomatik olarak dizideki 1. öğeye bir işaretçiye dönüştürüldüğü anlamına gelir.

İşlev adlarının benzer bir şekilde davrandığını, ancak işlev işaretleyicilerinin, dizi adlarının işaretçilere otomatik olarak dönüştürülmesi kadar karışıklığa neden olmadığı için çok daha az ve çok daha özel bir şekilde kullanıldığını unutmayın.

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

Bir lvalue veya tip “NT dizisi” ya da “T bağlı bilinmeyen dizisi” bir rvalue olabilir “T. pointer” tipi bir rvalue dönüştürülebilir

Dönüşüm vermez Yani var hemen hemen her zaman C yaptığı gibi gerçekleşmesi (bu fonksiyonlar aşırı sağlayan veya şablonlar dizi türüne eşleşir).

Bu nedenle C'de fonksiyon prototiplerinde / tanımlarında dizi parametrelerini kullanmaktan kaçınmalısınız (bence - genel bir anlaşma olup olmadığından emin değilim). Karışıklık yaratırlar ve yine de bir kurgudur - işaretçi parametrelerini kullanın ve karışıklık tamamen ortadan kalkmayabilir, ancak en azından parametre beyanı yalan söylemez.


2
"'Tür dizisi' türüne sahip" ifadesinin "bir diziyi başlatmak için kullanılan bir dize değişmezi" olduğu örnek bir kod satırı nedir?
Garrett

4
@Garrett char x[] = "Hello";. 6 öğeden oluşan dizi "Hello"bozulmaz; bunun yerine xboyut alır 6ve öğeleri öğelerinden başlatılır "Hello".
MM

30

"Çürüme", bir ifadenin dizi türünden işaretçi türüne örtük dönüştürülmesini ifade eder. Çoğu bağlamda, derleyici bir dizi ifadesi gördüğünde, ifade türünü "T öğesinin N öğesi dizisinden" "işaretçiye T" ye dönüştürür ve ifadenin değerini dizinin ilk öğesinin adresine ayarlar. . Bu kuralın istisnaları, bir dizi ya sizeofda &işleçlerin işleneni olduğunda ya da 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"80 elemanlı char dizisi" türünde ve "Bu bir testtir" ifadesi "16 elemanlı char dizisi" türündedir (C'de; C ++ dize değişmezleri sabit karakter dizileridir). Bununla birlikte, çağrısında strcpy(), hiçbir ifade bir işlenen değildir sizeofveya &bu nedenle türleri dolaylı olarak "işaretçi karakterine" dönüştürülür ve değerleri her birindeki ilk öğenin adresine ayarlanır. Nestrcpy() 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çisi ile aynı şey değildir. Örneğin:

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

Hem ptr_to_first_elementve ptr_to_arrayaynısından değeri ; a'nın temel adresi. Bununla birlikte, bunlar farklı tiplerdir 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]

Sentezleme unutmayın a[i]olarak yorumlanır *(a+i)(dizi türü bir işaretçi türüne dönüştürülür yalnızca çalışır), her ikisi de çok a[i]ve ptr_to_first_element[i]çalışma aynı. İfade (*ptr_to_array)[i]olarak yorumlanır *(*a+i). İfadeler *ptr_to_array[i]ve ptr_to_array[i]bağlama bağlı olarak derleyici uyarılarına veya hatalarına yol açabilir; değerlendirmelerini bekliyorsanız kesinlikle yanlış bir şey yapacaklara[i] .

sizeof a == sizeof *ptr_to_array == 80

Yine, bir dizi işlenen olduğunda sizeof , bir iş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çi.


1
Is not "This is a test" is of type "16-element array of char"a "15-element array of char"? (\ 0 için uzunluk 14 + 1)
chux - Monica'yı

16

C'deki dizilerin değeri yoktur.

Bir nesnenin değerinin beklendiği, ancak nesnenin bir dizi olduğu her yerde, türüyle ilk öğesinin adresi kullanılır pointer to (type of array elements).

Bir fonksiyonda, tüm parametreler değere göre geçirilir (diziler istisna değildir). Bir işlevi bir dizi içinde geçirdiğinizde "bir göstergeye bozulur" (sic); bir diziyi başka bir şeyle karşılaştırdığınızda, yine "bir işaretçiye bozulur" (sic); ...

void foo(int arr[]);

Foo işlevi bir dizinin değerini bekler. Ancak, C'de dizilerin bir değeri yoktur! Bunun fooyerine dizinin ilk öğesinin adresini alır.

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

Yukarıdaki karşılaştırmada arrhiçbir değeri yoktur, bu yüzden bir işaretçi haline gelir. İnt işaretçisi olur. Bu işaretçi değişkenle karşılaştırılabilirip .

Dizi endeksleme sözdiziminde görmeye alışkın olduğunuzu, yine, dizi 'bir işaretçiye çürüdü'

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

Bir dizinin işaretçiye bozulmadığı tek zaman, sizeof operatörünün işleneni veya & operatörünün ('operatörünün adresi') ya da bir karakter dizisini başlatmak için kullanılan bir dize değişmezidir.


5
"Dizilerin bir değeri yok" - bunun anlamı ne? Tabii ki dizilerin değeri var ... bunlar nesneler, işaretçiler olabilir ve C ++ 'da bunlara referanslar vb.
Pavel Minaev

2
Kesinlikle, "Değer" in C'de bir nesnenin bitlerinin bir türe göre yorumlanması olarak tanımlandığına inanıyorum. Bir dizi türü ile bunun yararlı bir anlamını bulmak zor bir zaman var. Bunun yerine, bir işaretçiye dönüştürdüğünüzü söyleyebilirsiniz, ancak bu, dizinin içeriğini yorumlamaz, sadece konumunu alır. Elde ettiğiniz şey, bir dizinin değeri değil, bir işaretçinin (ve bir adresin) değeridir ("dize" tanımında kullanılan "bu, içerilen öğelerin değer dizisi" olacaktır). Yani, biri bir işaretçi anlamına gelir "dizi değeri" demek adil olduğunu söyledi.
Johannes Schaub - litb

Neyse, ben hafif bir belirsizlik olduğunu düşünüyorum: Bir nesnenin değeri ve bir ifade değeri ("rvalue" gibi). İkinci yol yorumlanırsa, bir dizi ifadesinin mutlaka bir değeri vardır: Bu, değeri bir değere çürümekten kaynaklanan ve işaretçi ifadesidir. Ancak eski şekilde yorumlanırsa, elbette bir dizi nesnesi için yararlı bir anlam yoktur.
Johannes Schaub - litb

1
Küçük bir düzeltme ile ifade için +1; diziler için bir üçlü bile değil sadece bir beyit [konum, tür]. Dizinin durumunda üçüncü konum için aklınızda başka bir şey var mı? Hiç düşünemiyorum.
legends2k

1
@ legends2k: Sanırım dizileri üçüncü sırada kullandım ve onları sadece bir beyit olması için özel bir durum haline getirmekten kaçındım. Belki [konum, tür, boşluk ] daha iyi olurdu.
pmg

8

O zaman dizi çürüyor ve ;-) işaret ediliyor

Aslında, sadece bir diziyi bir yere geçirmek istiyorsanız, ancak işaretçi yerine geçiyorsa (cehennem sizin için tüm diziyi kimin geçeceği için), insanlar kötü dizinin işaretçiye bozulduğunu söylüyorlar.


Güzel dedi. İşaretçiye ya da çürümesini engelleyen bir diziye bozulmayan güzel bir dizi ne olurdu? C dilinde bir örnek verebilir misiniz? Teşekkürler.
Unheilig

@Unheilig, elbette, bir diziyi yapıya vakumlu olarak paketleyebilir ve yapıyı geçirebilir.
Michael Krelin - hacker

"İş" ile ne demek istediğinden emin değilim. Dizinin ötesine erişmesine izin verilmiyor, ancak gerçekten ne olacağını beklerseniz beklendiği gibi çalışıyor. Bu davranış (yine de, resmi olarak tanımlanmamış olsa da) korunur.
Michael Krelin - hacker

Çürüme ayrıca diziyi hiçbir yerde geçirmeyen birçok durumda da olur (diğer yanıtlarda açıklandığı gibi). Örneğin a + 1,.
MM

3

Dizinin bozulması, bir dizi bir işleve parametre olarak iletildiğinde, bir işaretçiye aynı şekilde ("bozulmasına") işlem yapıldığı anlamına gelir.

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ıdakilerin iki komplikasyonu veya istisnası vardır.

Birincisi, C ve C ++ 'da çok boyutlu dizilerle uğraşırken, sadece ilk boyut kaybolur. Bunun nedeni, dizilerin bellekte bitişik olarak yerleştirilmesidir, bu nedenle derleyici, o bellek bloğundaki ofsetleri hesaplayabilmek için ilk boyut dışındaki 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 saptamak için şablonları kullanabilirsiniz. Microsoft bunu strcpy_s gibi Güvenli CRT işlevlerinin C ++ sürümleri için kullanır ve bir dizideki öğelerin sayısını güvenilir bir şekilde almak için benzer bir hile kullanabilirsiniz .


1
çürüme sadece bir diziyi bir işleve aktarmakla kalmaz, birçok durumda da olur.
MM

0

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

Böylece:

  • Eğer yazarken arr[idx]gerçekten sadece söylüyorsun *(arr + idx).
  • Bir dizi parametresini belirtseniz bile, işlevler dizileri gerçekten parametre olarak almaz, yalnızca işaretçiler olarak alır.

Bu kuralın istisnaları:

  • Sabit uzunluklu dizileri a içindeki işlevlere iletebilirsiniz struct.
  • sizeof() bir işaretçinin boyutunu değil dizi tarafından alınan boyutu verir.

0

İşlev argümanı olarak bir dizi geçmek için dört (4) yol olduğunu düşünmek çok cesur olabilir. Ayrıca, sizin algılamanız için kısa ama çalışma kodu.

#include <iostream>
#include <string>
#include <vector>
#include <cassert>

using namespace std;

// test data
// notice native array init with no copy aka "="
// not possible in C
 const char* specimen[]{ __TIME__, __DATE__, __TIMESTAMP__ };

// ONE
// simple, dangerous and useless
template<typename T>
void as_pointer(const T* array) { 
    // a pointer
    assert(array != nullptr); 
} ;

// TWO
// for above const T array[] means the same
// but and also , minimum array size indication might be given too
// this also does not stop the array decay into T *
// thus size information is lost
template<typename T>
void by_value_no_size(const T array[0xFF]) { 
    // decayed to a pointer
    assert( array != nullptr ); 
}

// THREE
// size information is preserved
// but pointer is asked for
template<typename T, size_t N>
void pointer_to_array(const T (*array)[N])
{
   // dealing with native pointer 
    assert( array != nullptr ); 
}

// FOUR
// no C equivalent
// array by reference
// size is preserved
template<typename T, size_t N>
void reference_to_array(const T (&array)[N])
{
    // array is not a pointer here
    // it is (almost) a container
    // most of the std:: lib algorithms 
    // do work on array reference, for example
    // range for requires std::begin() and std::end()
    // on the type passed as range to iterate over
    for (auto && elem : array )
    {
        cout << endl << elem ;
    }
}

int main()
{
     // ONE
     as_pointer(specimen);
     // TWO
     by_value_no_size(specimen);
     // THREE
     pointer_to_array(&specimen);
     // FOUR
     reference_to_array( specimen ) ;
}

Ben de bu C ++ vs C üstünlüğünü gösterdiğini düşünebilirsiniz.

Tabii ki yığın tahsisi, istisnasız ve std :: lib olmayan son derece katı projeler var. C ++ yerli dizi işleme, kritik bir dil özelliği olduğunu söyleyebiliriz.

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.