Kenapa alamat array sama dengan nilainya dalam C?

189

Dalam sedikit kode berikut, nilai penunjuk dan alamat penunjuk berbeda seperti yang diharapkan.

Tetapi nilai array dan alamat tidak!

Bagaimana ini bisa terjadi?

Keluaran

my_array = 0022FF00
&my_array = 0022FF00
pointer_to_array = 0022FF00
&pointer_to_array = 0022FEFC
#include <stdio.h>

int main()
{
  char my_array[100] = "some cool string";
  printf("my_array = %p\n", my_array);
  printf("&my_array = %p\n", &my_array);

  char *pointer_to_array = my_array;
  printf("pointer_to_array = %p\n", pointer_to_array);
  printf("&pointer_to_array = %p\n", &pointer_to_array);

  printf("Press ENTER to continue...\n");
  getchar();
  return 0;
}
Alexandre
sumber
Dari FAQ comp.lang.c: - [Jadi apa yang dimaksud dengan `` kesetaraan pointer dan array '' di C? ] ( c-faq.com/aryptr/aryptrequiv.html ) - [Karena referensi array membusuk menjadi pointer, jika arr adalah array, apa perbedaan antara arr dan & arr? ] ( c-faq.com/aryptr/aryvsadr.html ) Atau baca seluruh bagian Array dan Pointer .
jamesdlin
3
Saya telah menambahkan jawaban dengan diagram untuk pertanyaan ini dua tahun yang lalu di sini. Apa yang sizeof(&array)kembali?
Grijesh Chauhan

Jawaban:

214

Nama array biasanya mengevaluasi ke alamat dari elemen pertama dari array, sehingga arraydan &arraymemiliki nilai yang sama (tapi jenis yang berbeda, sehingga array+1dan &array+1akan tidak sama jika array lebih dari 1 elemen panjang).

Ada dua pengecualian untuk ini: ketika nama array adalah operan sizeofatau unary &(address-of), namanya merujuk ke objek array itu sendiri. Dengan demikian sizeof arraymemberi Anda ukuran dalam byte dari seluruh array, bukan ukuran pointer.

Untuk array yang didefinisikan sebagai T array[size], ia akan memiliki tipe T *. Ketika / jika Anda menambahkannya, Anda mendapatkan elemen berikutnya dalam array.

&arraymengevaluasi ke alamat yang sama, tetapi diberi definisi yang sama, itu menciptakan pointer dari tipe T(*)[size]- yaitu, itu adalah pointer ke array, bukan ke elemen tunggal. Jika Anda menambah pointer ini, itu akan menambah ukuran seluruh array, bukan ukuran elemen tunggal. Misalnya, dengan kode seperti ini:

char array[16];
printf("%p\t%p", (void*)&array, (void*)(&array+1));

Kita dapat mengharapkan pointer kedua menjadi 16 lebih besar dari yang pertama (karena ini adalah array dari 16 karakter). Karena% p biasanya mengonversi pointer dalam heksadesimal, itu mungkin terlihat seperti:

0x12341000    0x12341010
Jerry Coffin
sumber
3
@Alexandre: &arrayadalah penunjuk ke elemen pertama array, di mana arraymengacu pada seluruh array. Perbedaan mendasar juga dapat diamati dengan membandingkan sizeof(array), dengan sizeof(&array). Namun perhatikan bahwa jika Anda meneruskan arraysebagai argumen ke suatu fungsi, hanya &arrayfaktanya yang berlalu. Anda tidak dapat melewatkan array dengan nilai kecuali jika dienkapsulasi dengan a struct.
Clifford
14
@Clifford: Jika Anda melewatkan array ke suatu fungsi, ia meluruh ke sebuah pointer ke elemen pertama sehingga secara efektif &array[0]dilewatkan, bukan &arrayyang akan menjadi pointer ke array. Ini mungkin nit-pick tapi saya pikir penting untuk menjelaskannya; kompiler akan memperingatkan jika fungsi memiliki prototipe yang cocok dengan jenis pointer yang disahkan.
CB Bailey
2
@ Jererry Coffin Sebagai contoh int * p = & a, jika saya ingin alamat memori dari pointer int p, saya dapat melakukan & p. Sejak & array dikonversi ke alamat seluruh array (yang dimulai pada alamat elemen pertama). Lalu bagaimana cara menemukan alamat memori dari pointer array (yang menyimpan alamat elemen pertama dalam array)? Itu pasti di suatu tempat di memori, kan?
John Lee
2
@ JohnLee: Tidak, tidak harus ada pointer ke array di mana saja di memori. Jika Anda membuat pointer, Anda kemudian dapat mengambil alamat: int *p = array; int **pp = &p;.
Jerry Coffin
3
@Clifford komentar pertama salah, mengapa masih menyimpannya? Saya pikir itu mungkin menyebabkan kesalahpahaman bagi mereka yang tidak membaca balasan (@Charles) berikut.
Rick
30

Itu karena nama array ( my_array) berbeda dari satu pointer ke array. Ini adalah alias ke alamat array, dan alamatnya didefinisikan sebagai alamat array itu sendiri.

Pointer adalah variabel C normal pada stack. Dengan demikian, Anda dapat mengambil alamatnya dan mendapatkan nilai berbeda dari alamat yang dimilikinya di dalamnya.

Saya menulis tentang topik ini di sini - silakan lihat.

Eli Bendersky
sumber
Tidakkah seharusnya & my_array menjadi operasi yang tidak valid karena nilai my_array tidak ada di stack, hanya my_array [panjang ...] yang? Maka semuanya akan masuk akal ...
Alexandre
@Alexandre: Saya tidak yakin mengapa itu diizinkan, sebenarnya.
Eli Bendersky
Anda dapat mengambil alamat variabel apa pun (jika tidak ditandai register) berapapun durasi penyimpanannya: statis, dinamis atau otomatis.
CB Bailey
my_arraysendiri adalah pada stack, karena my_array merupakan seluruh array.
caf
3
my_array, ketika bukan subjek operator &atau sizeof, dievaluasi ke pointer ke elemen pertama (mis. &my_array[0]) - tetapi my_arrayitu sendiri bukan pointer itu ( my_arraymasih array). Pointer itu hanya nilai sementara (mis. Diberikan int a;, itu seperti a + 1) - setidaknya secara konseptual "dihitung sesuai kebutuhan". "Nilai" sebenarnya my_arrayadalah isi seluruh array - hanya saja dengan menjabarkan nilai ini dalam C sama seperti mencoba menangkap kabut dalam toples.
caf
28

Dalam C, ketika Anda menggunakan nama array dalam ekspresi (termasuk meneruskannya ke fungsi), kecuali itu adalah operan dari &operator address-of ( ) atau sizeofoperator, ia meluruh ke pointer ke elemen pertama.

Artinya, dalam sebagian besar konteks arraysama dengan &array[0]di kedua jenis dan nilai.

Dalam contoh Anda, my_arraymemiliki tipe char[100]yang meluruh ke char*ketika Anda meneruskannya ke printf.

&my_arraymemiliki tipe char (*)[100](pointer ke array 100 char). Karena ini adalah operan untuk &, ini adalah salah satu kasus yang my_arraytidak segera membusuk ke pointer ke elemen pertama.

Pointer ke array memiliki nilai alamat yang sama dengan pointer ke elemen pertama array sebagai objek array hanya urutan yang berdekatan dari elemen-elemennya, tetapi pointer ke array memiliki tipe yang berbeda dengan pointer ke elemen array itu. Ini penting ketika Anda melakukan pointer aritmatika pada dua jenis pointer.

pointer_to_arraymemiliki tipe char *- diinisialisasi untuk menunjuk pada elemen pertama array karena itulah yang my_arraymeluruh dalam ekspresi initializer - dan &pointer_to_array memiliki tipe char **(pointer ke pointer ke a char).

Dari jumlah ini: my_array(setelah pembusukan ke char*), &my_arraydan pointer_to_arraysemua menunjuk langsung pada array atau elemen pertama array dan memiliki nilai alamat yang sama.

CB Bailey
sumber
3

Alasan mengapa my_arraydan &my_arraymenghasilkan alamat yang sama dapat dengan mudah dipahami ketika Anda melihat tata letak memori suatu array.

Katakanlah Anda memiliki array 10 karakter (bukan 100 dalam kode Anda).

char my_array[10];

Memori untuk my_arrayterlihat seperti:

+---+---+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+---+---+
^
|
Address of my_array.

Di C / C ++, array meluruh ke pointer ke elemen pertama dalam ekspresi seperti

printf("my_array = %p\n", my_array);

Jika Anda memeriksa di mana elemen pertama array terletak Anda akan melihat bahwa alamatnya sama dengan alamat array:

my_array[0]
|
v
+---+---+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+---+---+
^
|
Address of my_array[0].
R Sahu
sumber
3

Dalam bahasa pemrograman B, yang merupakan pendahulu langsung ke C, pointer dan integer bebas dipertukarkan. Sistem akan berperilaku seolah-olah semua memori adalah array raksasa. Setiap nama variabel memiliki alamat global atau stack-relatif yang terkait dengannya, untuk setiap nama variabel satu-satunya hal yang harus dilacak oleh kompiler adalah apakah itu variabel global atau lokal, dan alamatnya relatif terhadap global atau lokal pertama variabel.

Mengingat deklarasi global seperti i;[tidak perlu untuk menentukan jenis, karena semuanya adalah integer / pointer] akan diproses oleh compiler sebagai: address_of_i = next_global++; memory[address_of_i] = 0;dan pernyataan seperti i++akan diproses sebagai: memory[address_of_i] = memory[address_of_i]+1;.

Pernyataan seperti arr[10];akan diproses sebagai address_of_arr = next_global; memory[next_global] = next_global; next_global += 10;. Perhatikan bahwa segera setelah deklarasi itu diproses, kompilator dapat langsung lupa tentang arrmenjadi sebuah array . Pernyataan seperti arr[i]=6;akan diproses sebagai memory[memory[address_of_a] + memory[address_of_i]] = 6;. Kompilator tidak akan peduli apakah arrmewakili array dan iinteger, atau sebaliknya. Memang, tidak akan peduli apakah keduanya array atau keduanya integer; itu akan dengan senang hati menghasilkan kode seperti yang dijelaskan, tanpa memperhatikan apakah perilaku yang dihasilkan kemungkinan akan berguna.

Salah satu tujuan dari bahasa pemrograman C adalah untuk sebagian besar kompatibel dengan B. Dalam B, nama array [disebut "vektor" dalam terminologi B] mengidentifikasi variabel yang memegang pointer yang awalnya ditugaskan untuk menunjuk ke ke elemen pertama dari alokasi ukuran yang diberikan, jadi jika nama itu muncul dalam daftar argumen untuk suatu fungsi, fungsi tersebut akan menerima pointer ke vektor. Meskipun C menambahkan tipe array "nyata", yang namanya secara kaku dikaitkan dengan alamat alokasi alih-alih variabel pointer yang awalnya akan menunjuk ke alokasi, memiliki array yang terurai menjadi pointer membuat kode yang menyatakan array tipe-C berperilaku identik. ke kode B yang menyatakan vektor dan kemudian tidak pernah memodifikasi variabel yang menyimpan alamatnya.

supercat
sumber
1

Sebenarnya &myarraydan myarraykeduanya adalah alamat dasar.

Jika Anda ingin melihat perbedaannya daripada menggunakan

printf("my_array = %p\n", my_array);
printf("my_array = %p\n", &my_array);

menggunakan

printf("my_array = %s\n", my_array);
printf("my_array = %p\n", my_array);
Ravi Bisla
sumber