Apa itu array to pointer decay?

384

Apa itu array to pointer decay? Apakah ada kaitannya dengan pointer array?

Vamsi
sumber
73
sedikit diketahui: Operator plus unary dapat digunakan sebagai "operator peluruhan": Diberikan int a[10]; int b(void);, maka +aadalah penunjuk int dan +bpenunjuk fungsi. Berguna jika Anda ingin meneruskannya ke templat yang menerima referensi.
Johannes Schaub - litb
3
@ litb - parens akan melakukan hal yang sama (misalnya, (a) harus berupa ekspresi yang mengevaluasi ke sebuah pointer), kan ?.
Michael Burr
21
std::decaydari C ++ 14 akan menjadi cara yang kurang jelas dari peluruhan array di atas unary +.
legends2k
21
@ JohannesSchaub-litb karena pertanyaan ini ditandai C dan C ++, saya ingin mengklarifikasi bahwa meskipun +adan +blegal di C ++, itu ilegal di C (C11 6.5.3.3/1 "Operand unary +atau -operator harus memiliki tipe aritmatika ")
MM
5
@ Tepat. Tapi saya kira itu tidak sedikit dikenal sebagai trik dengan + unary. Alasan saya menyebutkan itu bukan hanya karena meluruh tetapi karena itu adalah beberapa hal yang menyenangkan untuk dimainkan;)
Johannes Schaub - litb

Jawaban:

283

Dikatakan bahwa array "membusuk" menjadi pointer. Array C ++ dinyatakan sebagai int numbers [5]tidak dapat diarahkan kembali, yaitu Anda tidak bisa mengatakan numbers = 0x5a5aff23. Lebih penting lagi, istilah peluruhan menandakan hilangnya jenis dan dimensi; numberspembusukan int*dengan kehilangan informasi dimensi (hitungan 5) dan tipenya tidak int [5]lagi. Lihat di sini untuk kasus di mana pembusukan tidak terjadi .

Jika Anda melewatkan array berdasarkan nilai, apa yang sebenarnya Anda lakukan adalah menyalin pointer - pointer ke elemen pertama array akan disalin ke parameter (yang tipenya juga harus menjadi pointer tipe elemen array). Ini berfungsi karena sifat peluruhan array; sekali membusuk, sizeoftidak lagi memberikan ukuran array yang lengkap, karena pada dasarnya menjadi pointer. Inilah sebabnya mengapa lebih disukai (antara alasan lain) untuk melewati referensi atau pointer.

Tiga cara untuk lulus dalam array 1 :

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])

Dua yang terakhir akan memberikan sizeofinfo yang tepat , sedangkan yang pertama tidak akan karena argumen array telah meluruh untuk ditetapkan ke parameter.

1 U yang konstan harus diketahui pada waktu kompilasi.

matahari
sumber
8
Bagaimana nilai pertama yang lewat?
rlbond
10
by_value meneruskan sebuah pointer ke elemen pertama array; dalam konteks parameter fungsi, T a[]identik dengan T *a. by_pointer melewatkan hal yang sama, kecuali nilai pointer sekarang memenuhi syarat const. Jika Anda ingin meneruskan sebuah pointer ke array (berbeda dengan pointer ke elemen pertama array), sintaksnya adalah T (*array)[U].
John Bode
4
"dengan pointer eksplisit ke array itu" - ini tidak benar. Jika aarray char, maka aadalah tipe char[N], dan akan meluruh ke char*; tetapi &atipe char(*)[N], dan tidak akan membusuk.
Pavel Minaev
5
@FredOverflow: Jadi, jika Uperubahan Anda tidak harus ingat untuk mengubahnya di dua tempat, atau berisiko bug diam ... Otonomi!
Lightness Races in Orbit
4
"Jika Anda melewati sebuah array dengan nilai, apa yang sebenarnya Anda lakukan adalah menyalin sebuah pointer" Itu tidak masuk akal, karena array tidak bisa dilewati oleh nilai, titik.
juanchopanza
103

Array pada dasarnya sama dengan pointer di C / C ++, tetapi tidak cukup. Setelah Anda mengonversi array:

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

menjadi pointer (yang berfungsi tanpa casting, dan karenanya dapat terjadi secara tak terduga dalam beberapa kasus):

const int* p = a;

Anda kehilangan kemampuan sizeofoperator untuk menghitung elemen dalam array:

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

Kemampuan yang hilang ini disebut sebagai "pembusukan".

Untuk detail lebih lanjut, lihat artikel ini tentang peluruhan array .

sistem PAUSE
sumber
51
Array pada dasarnya tidak sama dengan pointer; mereka adalah binatang yang sama sekali berbeda. Dalam kebanyakan konteks, sebuah array dapat diperlakukan seolah-olah itu adalah sebuah pointer, dan sebuah pointer dapat diperlakukan seolah-olah itu sebuah array, tapi itu sedekat yang mereka dapatkan.
John Bode
20
@ John, tolong maafkan bahasa saya yang tidak tepat. Saya sedang berusaha mendapatkan jawaban tanpa terjebak dalam cerita panjang yang panjang, dan "pada dasarnya ... tapi tidak cukup" adalah penjelasan yang baik seperti yang pernah saya dapatkan di perguruan tinggi. Saya yakin siapa pun yang tertarik bisa mendapatkan gambar yang lebih akurat dari komentar Anda yang telah di-upgrade.
system PAUSE
"berfungsi tanpa casting" artinya sama dengan "terjadi secara implisit" ketika berbicara tentang konversi jenis
MM
47

Inilah yang dikatakan standar (C99 6.3.2.1/3 - Operan lainnya - Lvalues, array, dan function designators):

Kecuali ketika itu adalah operan dari operator sizeof atau unary & operator, atau string literal digunakan untuk menginisialisasi array, ekspresi yang memiliki tipe '' array of type '' dikonversi menjadi ekspresi dengan tipe '' pointer ke ketik '' yang menunjuk ke elemen awal objek array dan bukan merupakan nilai.

Ini berarti bahwa kapan saja nama array digunakan dalam ekspresi, secara otomatis dikonversi menjadi pointer ke item pertama dalam array.

Perhatikan bahwa nama fungsi bertindak dengan cara yang serupa, tetapi pointer fungsi digunakan jauh lebih sedikit dan dengan cara yang jauh lebih khusus sehingga tidak menyebabkan kebingungan sebanyak konversi otomatis dari nama array ke pointer.

Standar C ++ (4.2 Array-to-pointer konversi) melonggarkan persyaratan konversi ke (penekanan tambang):

Nilai atau nilai tipe "array NT" atau "array batas tidak diketahui T" dapat dikonversi ke nilai tipe "pointer to T."

Jadi konversi tidak harus terjadi seperti biasanya di C (ini memungkinkan fungsi kelebihan atau templat cocok dengan jenis array).

Ini juga mengapa di C Anda harus menghindari menggunakan parameter array dalam prototipe fungsi / definisi (menurut saya - saya tidak yakin apakah ada kesepakatan umum). Mereka menyebabkan kebingungan dan fiksi - menggunakan parameter pointer dan kebingungan mungkin tidak hilang sepenuhnya, tetapi setidaknya deklarasi parameter tidak bohong.

Michael Burr
sumber
2
Apa yang dimaksud dengan contoh baris kode di mana "ekspresi yang memiliki tipe 'array of type'" adalah "string literal yang digunakan untuk menginisialisasi array"?
Garrett
4
@Garrett char x[] = "Hello";. Array 6 elemen "Hello"tidak membusuk; bukannya xmendapatkan ukuran 6dan elemennya diinisialisasi dari elemen "Hello".
MM
30

"Decay" mengacu pada konversi tersirat dari ekspresi dari tipe array ke tipe pointer. Dalam sebagian besar konteks, ketika kompiler melihat ekspresi array, ia mengubah tipe ekspresi dari "N-element array of T" menjadi "pointer to T" dan menetapkan nilai ekspresi ke alamat elemen pertama dari array. . Pengecualian untuk aturan ini adalah ketika sebuah array adalah operan dari baik sizeofatau &operator, atau array adalah string literal digunakan sebagai initializer dalam deklarasi.

Asumsikan kode berikut:

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

Ekspresi aadalah tipe "80-elemen array char" dan ekspresi "Ini adalah tes" adalah tipe "16-elemen array char" (dalam C; dalam string C ++ string literal adalah array dari const char). Namun, dalam panggilan ke strcpy(), tidak ada ekspresi yang merupakan operan dari sizeofatau &, jadi tipe mereka secara implisit dikonversi menjadi "pointer ke char", dan nilainya diatur ke alamat elemen pertama di masing-masing. Apa yang strcpy()diterima bukan array, tetapi pointer, seperti yang terlihat dalam prototipe:

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

Ini bukan hal yang sama dengan pointer array. Sebagai contoh:

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

Kedua ptr_to_first_elementdan ptr_to_arraymemiliki yang sama nilai ; alamat dasar a. Namun, mereka berbeda jenis dan diperlakukan berbeda, seperti yang ditunjukkan di bawah ini:

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

Ingat bahwa ekspresi a[i]ditafsirkan sebagai *(a+i)(yang hanya berfungsi jika tipe array dikonversi ke tipe pointer), jadi keduanya a[i]dan ptr_to_first_element[i]berfungsi sama. Ekspresi (*ptr_to_array)[i]diartikan sebagai *(*a+i). Ekspresi *ptr_to_array[i]dan ptr_to_array[i]dapat menyebabkan peringatan kompiler atau kesalahan tergantung pada konteksnya; mereka pasti akan melakukan hal yang salah jika Anda mengharapkan mereka untuk mengevaluasi a[i].

sizeof a == sizeof *ptr_to_array == 80

Sekali lagi, ketika sebuah array adalah operan sizeof, itu tidak dikonversi ke tipe pointer.

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 adalah pointer sederhana ke char.

John Bode
sumber
1
Bukankah "This is a test" is of type "16-element array of char"sebuah "15-element array of char"? (panjang 14 + 1 untuk \ 0)
chux - Reinstate Monica
16

Array, dalam C, tidak memiliki nilai.

Di mana pun nilai suatu objek diharapkan tetapi objeknya adalah sebuah array, alamat elemen pertamanya digunakan sebagai gantinya, dengan tipe pointer to (type of array elements).

Dalam suatu fungsi, semua parameter dilewatkan oleh nilai (array tidak terkecuali). Ketika Anda melewatkan sebuah array dalam suatu fungsi, ia "meluruh menjadi sebuah pointer" (sic); ketika Anda membandingkan sebuah array dengan sesuatu yang lain, sekali lagi itu "meluruh menjadi sebuah pointer" (sic); ...

void foo(int arr[]);

Function foo mengharapkan nilai array. Tetapi, dalam C, array tidak memiliki nilai! Jadi, fooalih-alih mendapatkan alamat elemen pertama array.

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

Dalam perbandingan di atas, arrtidak memiliki nilai, sehingga menjadi pointer. Ini menjadi pointer ke int. Pointer itu dapat dibandingkan dengan variabel ip.

Dalam sintaks pengindeksan array Anda terbiasa melihat, sekali lagi, arr 'decayed to a pointer'

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

Satu-satunya kali array tidak membusuk menjadi pointer adalah ketika itu adalah operan dari operator sizeof, atau operator & (operator alamat), atau sebagai string string yang digunakan untuk menginisialisasi array karakter.

pmg
sumber
5
"Array tidak memiliki nilai" - apa artinya itu? Tentu saja array memiliki nilai ... mereka benda sedang, Anda dapat memiliki pointer, dan, di C ++, referensi kepada mereka, dll
Pavel Minaev
2
Saya percaya, secara ketat, "Nilai" didefinisikan dalam C sebagai interpretasi bit suatu objek menurut suatu tipe. Saya mengalami kesulitan mencari tahu makna yang berguna dengan tipe array. Sebagai gantinya, Anda bisa mengatakan bahwa Anda mengonversi ke sebuah pointer, tetapi itu tidak menafsirkan konten array, itu hanya mendapatkan lokasinya. Apa yang Anda dapatkan adalah nilai pointer (dan itu alamat), bukan nilai array (ini akan menjadi "urutan nilai dari item yang terkandung", seperti yang digunakan dalam definisi "string"). Yang mengatakan, saya pikir itu adil untuk mengatakan "nilai array" ketika satu berarti mendapat satu pointer.
Johannes Schaub - litb
Lagi pula, saya pikir ada sedikit ambiguitas: Nilai suatu objek, dan nilai ekspresi (seperti dalam, "rvalue"). Jika ditafsirkan dengan cara yang terakhir, maka ekspresi array pasti memiliki nilai: Ini adalah hasil dari pembusukan ke nilai, dan merupakan ekspresi pointer. Tetapi jika ditafsirkan dengan cara sebelumnya, maka tentu saja tidak ada arti yang berguna untuk objek array.
Johannes Schaub - litb
1
+1 untuk frasa dengan perbaikan kecil; untuk array itu bahkan bukan triplet hanya bait [lokasi, tipe]. Apakah Anda memiliki sesuatu yang lain dalam pikiran untuk lokasi ketiga dalam kasus array? Saya tidak bisa memikirkan apa pun.
legends2k
1
@ legends2k: Saya pikir saya menggunakan lokasi ketiga di array untuk menghindari membuat mereka kasus khusus hanya memiliki bait. Mungkin [lokasi, tipe, kekosongan ] akan lebih baik.
sore
8

Saat itulah array membusuk dan sedang diarahkan ;-)

Sebenarnya, hanya saja jika Anda ingin melewatkan array di suatu tempat, tetapi pointernya malah dilewatkan (karena siapa yang akan melewati seluruh array untuk Anda), orang mengatakan bahwa array miskin membusuk ke pointer.

Michael Krelin - hacker
sumber
Kata dengan baik. Apa yang akan menjadi array yang bagus yang tidak membusuk ke pointer atau yang dicegah membusuk? Bisakah Anda mengutip contoh dalam C? Terima kasih.
Unheilig
@Unheilig, tentu, seseorang dapat mengosongkan array ke struct dan meneruskan struct.
Michael Krelin - hacker
Saya tidak yakin apa yang Anda maksud dengan "bekerja". Itu tidak diizinkan untuk mengakses melewati array, meskipun berfungsi seperti yang diharapkan jika Anda mengharapkan apa yang sebenarnya terjadi. Perilaku itu (meskipun, sekali lagi, secara resmi tidak terdefinisi) dipertahankan.
Michael Krelin - hacker
Peluruhan juga terjadi dalam banyak situasi yang tidak melewatkan array di mana pun (seperti dijelaskan oleh jawaban lain). Sebagai contoh a + 1,.
MM
3

Array decaying berarti bahwa, ketika sebuah array dilewatkan sebagai parameter ke suatu fungsi, array diperlakukan secara identik dengan ("decay to") sebuah pointer.

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

Ada dua komplikasi atau pengecualian di atas.

Pertama, ketika berhadapan dengan array multidimensi dalam C dan C ++, hanya dimensi pertama yang hilang. Ini karena array diletakkan secara bersebelahan dalam memori, sehingga kompiler harus mengetahui semua kecuali dimensi pertama untuk dapat menghitung offset ke blok memori itu.

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

Kedua, di C ++, Anda bisa menggunakan templat untuk menyimpulkan ukuran array. Microsoft menggunakan ini untuk versi C ++ fungsi CRT Aman seperti strcpy_s , dan Anda bisa menggunakan trik serupa untuk secara andal mendapatkan jumlah elemen dalam array .

Josh Kelley
sumber
1
pembusukan terjadi dalam banyak situasi lain, tidak hanya meneruskan array ke suatu fungsi.
MM
0

tl; dr: Saat Anda menggunakan array yang telah Anda tentukan, Anda akan benar-benar menggunakan pointer ke elemen pertamanya.

Jadi:

  • Ketika Anda menulis arr[idx]Anda benar-benar hanya mengatakan*(arr + idx) .
  • fungsi tidak pernah benar-benar mengambil array sebagai parameter, hanya pointer, bahkan ketika Anda menentukan parameter array.

Sortir pengecualian untuk aturan ini:

  • Anda dapat meneruskan array panjang tetap ke fungsi dalam a struct.
  • sizeof() memberikan ukuran yang diambil oleh array, bukan ukuran pointer.
einpoklum
sumber
0

Saya mungkin sangat berani berpikir ada empat (4) cara untuk melewatkan array sebagai argumen fungsi. Juga di sini adalah kode singkat tapi berfungsi untuk pembacaan Anda.

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

Saya mungkin juga berpikir ini menunjukkan keunggulan C ++ vs C. Setidaknya dalam referensi (pun intended) melewati array dengan referensi.

Tentu saja ada proyek yang sangat ketat tanpa alokasi tumpukan, tanpa pengecualian dan tanpa std :: lib. C ++ penanganan array asli adalah fitur bahasa misi kritis, bisa dikatakan.

Chef Gladiator
sumber