Bagaimana cara kerja pointer ke pointer di C?


171

Bagaimana cara kerja pointer ke pointer bekerja di C? Kapan Anda akan menggunakannya?


43
Tidak bukan pekerjaan rumah .... hanya ingin tahu .. karena saya sering melihatnya ketika saya membaca kode C.

1
Sebuah pointer ke pointer bukanlah kasus khusus dari sesuatu, jadi saya tidak mengerti apa yang Anda tidak mengerti tentang kekosongan **.
akappa

untuk array 2D contoh terbaik adalah baris perintah args "prog arg1 arg2" disimpan char ** argv. Dan jika penelepon tidak ingin mengalokasikan memori (fungsi yang dipanggil akan mengalokasikan memori)
resultsway

1
Anda memiliki contoh penggunaan "pointer to pointer" yang bagus di Git 2.0: lihat jawaban saya di bawah ini
VonC

Jawaban:


359

Mari kita asumsikan komputer 8 bit dengan alamat 8 bit (dan dengan demikian hanya 256 byte memori). Ini adalah bagian dari memori itu (angka di atas adalah alamatnya):

  54   55   56   57   58   59   60   61   62   63   64   65   66   67   68   69
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
|    | 58 |    |    | 63 |    | 55 |    |    | h  | e  | l  | l  | o  | \0 |    |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+

Apa yang dapat Anda lihat di sini adalah bahwa pada alamat 63 string "halo" dimulai. Jadi dalam hal ini, jika ini adalah satu-satunya kejadian "halo" dalam memori,

const char *c = "hello";

... mendefinisikan cmenjadi penunjuk ke string (baca-saja) "halo", dan dengan demikian mengandung nilai 63. citu sendiri harus disimpan di suatu tempat: dalam contoh di atas di lokasi 58. Tentu saja kita tidak bisa hanya menunjuk ke karakter , tetapi juga untuk petunjuk lainnya. Misalnya:

const char **cp = &c;

Sekarang cpmenunjuk ke c, yaitu berisi alamat c(yaitu 58). Kita bisa melangkah lebih jauh. Mempertimbangkan:

const char ***cpp = &cp;

Sekarang cppmenyimpan alamat cp. Jadi memiliki nilai 55 (berdasarkan contoh di atas), dan Anda dapat menebaknya: itu sendiri disimpan di alamat 60.


Seperti mengapa seseorang menggunakan pointer ke pointer:

  • Nama array biasanya menghasilkan alamat elemen pertama. Jadi jika array mengandung elemen tipe t, referensi ke array memiliki tipe t *. Sekarang pertimbangkan sebuah array dari array tipe t: secara alami referensi ke array 2D ini akan memiliki type (t *)*= t **, dan karenanya adalah sebuah pointer ke sebuah pointer.
  • Meskipun deretan string terdengar satu dimensi, nyatanya dua dimensi, karena string adalah array karakter. Oleh karena itu: char **.
  • Suatu fungsi fperlu menerima argumen tipe t **jika ingin mengubah variabel tipe t *.
  • Banyak alasan lain yang terlalu banyak untuk disebutkan di sini.

7
ya contoh yang baik..saya mengerti apa yang mereka..tapi bagaimana dan kapan menggunakannya lebih penting..sekarang ..

2
Stephan melakukan pekerjaan yang baik dengan mereproduksi, pada dasarnya, diagram dalam Bahasa Pemrograman Kernighan & Richie. Jika Anda sedang memprogram C, dan tidak memiliki buku ini dan sangat keren dengan dokumentasi kertas, saya sangat menyarankan Anda mendapatkannya, pengeluaran sederhana (wajar) akan membayar sendiri dengan sangat cepat dalam produktivitas. Itu cenderung sangat jelas dalam contoh-contohnya.
J. Polfer

4
char * c = "hello" harus menjadi const char * c = "hello". Juga paling menyesatkan untuk mengatakan bahwa "array disimpan sebagai alamat elemen pertama". Array disimpan sebagai ... sebuah array. Seringkali namanya menghasilkan pointer ke elemen pertama, tetapi tidak selalu. Tentang pointer ke pointer, saya hanya akan mengatakan bahwa mereka berguna ketika suatu fungsi harus memodifikasi pointer yang dikirimkan sebagai parameter (kemudian Anda meneruskan pointer ke pointer sebagai gantinya).
Bastien Léonard

4
Kecuali saya salah menafsirkan jawaban ini, itu kelihatan salah. c disimpan di 58 dan menunjuk ke 63, cp disimpan di 55 dan menunjuk ke 58, dan cpp tidak terwakili dalam diagram.
Thanatos

1
Kelihatan bagus. Dari masalah kecil adalah semua yang menghentikan saya dari mengatakan: Pos yang bagus. Penjelasannya sendiri sangat bagus. Mengubah ke pemungutan suara. (Mungkin stackoverflow kebutuhan ulasan pointer?)
Thanatos

46

Bagaimana cara kerja pointer ke pointer bekerja di C?

Pertama, pointer adalah variabel, seperti variabel lainnya, tetapi yang menyimpan alamat variabel.

Penunjuk ke penunjuk adalah variabel, seperti variabel lainnya, tetapi yang menyimpan alamat variabel. Variabel itu hanya menjadi pointer.

Kapan Anda akan menggunakannya?

Anda dapat menggunakannya ketika Anda perlu mengembalikan pointer ke beberapa memori di heap, tetapi tidak menggunakan nilai kembali.

Contoh:

int getValueOf5(int *p)
{
  *p = 5;
  return 1;//success
}

int get1024HeapMemory(int **p)
{
  *p = malloc(1024);
  if(*p == 0)
    return -1;//error
  else 
    return 0;//success
}

Dan Anda menyebutnya seperti ini:

int x;
getValueOf5(&x);//I want to fill the int varaible, so I pass it's address in
//At this point x holds 5

int *p;    
get1024HeapMemory(&p);//I want to fill the int* variable, so I pass it's address in
//At this point p holds a memory address where 1024 bytes of memory is allocated on the heap

Ada kegunaan lain juga, seperti argumen main () dari setiap program C memiliki pointer ke pointer untuk argv, di mana setiap elemen memegang array karakter yang merupakan opsi baris perintah. Anda harus berhati-hati saat menggunakan pointer pointer untuk menunjuk ke array 2 dimensi, lebih baik menggunakan pointer ke array 2 dimensi sebagai gantinya.

Kenapa itu berbahaya?

void test()
{
  double **a;
  int i1 = sizeof(a[0]);//i1 == 4 == sizeof(double*)

  double matrix[ROWS][COLUMNS];
  int i2 = sizeof(matrix[0]);//i2 == 240 == COLUMNS * sizeof(double)
}

Berikut adalah contoh pointer ke array 2 dimensi yang dilakukan dengan benar:

int (*myPointerTo2DimArray)[ROWS][COLUMNS]

Anda tidak dapat menggunakan pointer ke array 2 dimensi meskipun jika Anda ingin mendukung sejumlah elemen untuk ROWS dan COLUMNS. Tetapi ketika Anda tahu sebelumnya, Anda akan menggunakan array 2 dimensi.


32

Saya suka contoh "dunia nyata" kode penggunaan pointer ke pointer, di Git 2.0, komit 7b1004b :

Linus pernah berkata:

Saya benar-benar berharap lebih banyak orang memahami jenis pengkodean tingkat rendah yang benar-benar inti. Bukan hal besar, rumit seperti pencarian nama tanpa kunci, tetapi cukup baik menggunakan pointer-ke-pointer dll.
Sebagai contoh, saya telah melihat terlalu banyak orang yang menghapus entri daftar yang terhubung secara tunggal dengan melacak entri "sebelumnya" , dan kemudian untuk menghapus entri, lakukan sesuatu seperti

if (prev)
  prev->next = entry->next;
else
  list_head = entry->next;

dan setiap kali saya melihat kode seperti itu, saya hanya pergi "Orang ini tidak mengerti petunjuk". Dan itu sangat umum.

Orang-orang yang mengerti pointer hanya menggunakan " pointer ke entry pointer ", dan menginisialisasi dengan alamat dari list_head. Dan saat mereka melintasi daftar, mereka dapat menghapus entri tanpa menggunakan persyaratan apa pun, dengan hanya melakukan a

*pp =  entry->next

http://i.stack.imgur.com/bpfxT.gif

Menerapkan penyederhanaan itu membuat kita kehilangan 7 baris dari fungsi ini bahkan ketika menambahkan 2 baris komentar.

-   struct combine_diff_path *p, *pprev, *ptmp;
+   struct combine_diff_path *p, **tail = &curr;

Chris menunjukkan dalam komentar untuk video 2016 " Masalah Double Pointer Linus Torvalds " oleh Philip Buuck .


kumar menunjukkan dalam komentar di posting blog " Linus on Understanding Pointers ", di mana Grisha Trubetskoy menjelaskan:

Bayangkan Anda memiliki daftar tautan yang didefinisikan sebagai:

typedef struct list_entry {
    int val;
    struct list_entry *next;
} list_entry;

Anda perlu mengulanginya dari awal hingga akhir dan menghapus elemen tertentu yang nilainya sama dengan nilai to_remove.
Cara yang lebih jelas untuk melakukan ini adalah:

list_entry *entry = head; /* assuming head exists and is the first entry of the list */
list_entry *prev = NULL;

while (entry) { /* line 4 */
    if (entry->val == to_remove)     /* this is the one to remove ; line 5 */
        if (prev)
           prev->next = entry->next; /* remove the entry ; line 7 */
        else
            head = entry->next;      /* special case - first entry ; line 9 */

    /* move on to the next entry */
    prev = entry;
    entry = entry->next;
}

Apa yang kami lakukan di atas adalah:

  • mengulangi daftar sampai entri NULL, yang berarti kita telah mencapai akhir daftar (baris 4).
  • Ketika kita menemukan entri yang ingin kita hapus (baris 5),
    • kami menetapkan nilai pointer berikutnya saat ini ke yang sebelumnya,
    • dengan demikian menghilangkan elemen saat ini (baris 7).

Ada kasus khusus di atas - pada awal iterasi tidak ada entri sebelumnya ( prevadalah NULL), dan untuk menghapus entri pertama dalam daftar Anda harus memodifikasi kepala sendiri (line 9).

Apa yang dikatakan Linus adalah bahwa kode di atas dapat disederhanakan dengan membuat elemen sebelumnya menjadi pointer ke sebuah pointer daripada hanya sebuah pointer .
Kode tersebut kemudian terlihat seperti ini:

list_entry **pp = &head; /* pointer to a pointer */
list_entry *entry = head;

while (entry) {
    if (entry->val == to_remove)
        *pp = entry->next;

    pp = &entry->next;
    entry = entry->next;
}

Kode di atas sangat mirip dengan varian sebelumnya, tetapi perhatikan bagaimana kita tidak perlu lagi memperhatikan kasus khusus dari elemen pertama dari daftar, karena pptidak NULLdi awal. Sederhana dan pintar.

Juga, seseorang di utas itu berkomentar bahwa alasan ini lebih baik adalah karena *pp = entry->nextatom. Ini pasti BUKAN atom .
Ungkapan di atas berisi dua operator dereference ( *dan ->) dan satu penugasan, dan tak satu pun dari ketiga hal itu adalah atom.
Ini adalah kesalahpahaman umum, tetapi sayangnya tidak banyak hal dalam C yang seharusnya dianggap atom (termasuk operator ++dan --)!


4
Ini akan membantu untuk memahami dengan lebih baik - grisha.org/blog/2013/04/02/linus-on-understanding-pointers
kumar

@kumar referensi yang bagus. saya telah memasukkannya dalam jawaban untuk lebih banyak visibilitas.
VonC

Video ini penting bagi saya dalam memahami contoh Anda. Secara khusus saya merasa bingung (dan berkelahi) sampai saya menggambar diagram memori dan melacak kemajuan program. Yang mengatakan, masih terasa agak misterius bagiku.
Chris

@ Chris Video yang luar biasa, terima kasih telah menyebutkannya! Saya telah memasukkan komentar Anda dalam jawaban untuk lebih banyak visibilitas.
VonC

14

Ketika meliput petunjuk tentang kursus pemrograman di universitas, kami diberi dua petunjuk tentang bagaimana mulai belajar tentang mereka. Yang pertama adalah untuk melihat Pointer Fun With Binky . Yang kedua adalah memikirkan tentang bagian Haddocks 'Eyes dari Lewis Carroll's Through the Looking-Glass

"Kamu sedih," kata sang Ksatria dengan nada cemas: "Biarkan aku menyanyikan lagu untuk menghiburmu."

"Apakah ini sangat panjang?" Alice bertanya, karena dia telah mendengar banyak puisi pada hari itu.

"Sudah lama," kata sang Ksatria, "tapi itu sangat, sangat indah. Semua orang yang mendengar saya menyanyikannya - apakah itu membuat mereka menangis, atau - "

"Atau apa?" kata Alice, karena sang Ksatria tiba-tiba terdiam.

"Atau kalau tidak, kamu tahu. Nama lagunya disebut 'Haddocks' Eyes. '”

"Oh, itu nama lagunya, kan?" Kata Alice, berusaha merasa tertarik.

"Tidak, kamu tidak mengerti," kata Knight, tampak sedikit kesal. “Itulah namanya namanya. Nama sebenarnya adalah 'The Aged Aged Man.' ”

"Kalau begitu aku seharusnya mengatakan 'Itu yang disebut lagunya'?" Alice mengoreksi dirinya sendiri.

"Tidak, kamu tidak boleh: itu hal lain! Lagu itu berjudul 'Ways And Means': tapi hanya itu namanya, lho! ”

"Lalu, apa lagunya?" kata Alice, yang pada saat ini benar-benar bingung.

"Aku datang untuk itu," kata Knight. "Lagu itu benar-benar 'A-sitting On A Gate': dan lagu itu adalah penemuan saya sendiri."


1
Saya harus membaca bagian itu beberapa kali ... +1 untuk membuat saya berpikir!
Ruben Steins

Inilah sebabnya mengapa Lewis Carroll bukan penulis biasa.
metarose

1
Jadi ... akan seperti ini? name -> 'The Aged Aged Man' -> disebut -> 'Haddock's Eyes' -> lagu -> 'A-sitting On A Gate'
tisaconundrum


7

Ketika referensi ke pointer diperlukan. Misalnya, ketika Anda ingin mengubah nilai (alamat menunjuk ke) dari variabel pointer dinyatakan dalam ruang lingkup fungsi panggilan di dalam fungsi yang dipanggil.

Jika Anda melewatkan satu penunjuk sebagai argumen, Anda akan memodifikasi salinan penunjuk lokal, bukan penunjuk asli dalam cakupan panggilan. Dengan pointer ke pointer, Anda memodifikasi yang terakhir.


Dijelaskan dengan baik untuk bagian 'Why'
Rana Deep

7

Pointer ke pointer juga disebut pegangan . Satu penggunaan untuk itu sering ketika suatu objek dapat dipindahkan dalam memori atau dihapus. Seseorang sering bertanggung jawab untuk mengunci dan membuka kunci penggunaan objek sehingga tidak akan dipindahkan saat mengaksesnya.

Ini sering digunakan dalam lingkungan terbatas memori, yaitu Palm OS.

computer.howstuffworks.com Tautan >>

www.flippinbits.com Tautan >>


7

Pertimbangkan gambar dan program di bawah ini untuk memahami konsep ini dengan lebih baik .

Diagram penunjuk ganda

Sesuai gambar, ptr1 adalah pointer tunggal yang memiliki alamat variabel num .

ptr1 = #

Demikian pula ptr2 adalah pointer ke pointer (pointer ganda) yang memiliki alamat pointer ptr1 .

ptr2 = &ptr1;

Pointer yang menunjuk ke pointer lain dikenal sebagai pointer ganda. Dalam contoh ini ptr2 adalah penunjuk ganda.

Nilai dari diagram di atas:

Address of variable num has : 1000
Address of Pointer ptr1 is: 2000
Address of Pointer ptr2 is: 3000

Contoh:

#include <stdio.h>

int main ()
{
   int  num = 10;
   int  *ptr1;
   int  **ptr2;

   // Take the address of var 
   ptr1 = &num;

   // Take the address of ptr1 using address of operator &
   ptr2 = &ptr1;

   // Print the value
   printf("Value of num = %d\n", num );
   printf("Value available at *ptr1 = %d\n", *ptr1 );
   printf("Value available at **ptr2 = %d\n", **ptr2);
}

Keluaran:

Value of num = 10
Value available at *ptr1 = 10
Value available at **ptr2 = 10

5

itu adalah penunjuk ke nilai alamat penunjuk. (itu mengerikan saya tahu)

pada dasarnya, ini memungkinkan Anda meneruskan pointer ke nilai alamat pointer lain, sehingga Anda dapat memodifikasi di mana pointer lain menunjuk dari sub fungsi, seperti:

void changeptr(int** pp)
{
  *pp=&someval;
}

maaf, saya tahu itu sangat buruk. Coba baca, erm, ini: codeproject.com/KB/cpp/PtrToPtr.aspx
Luke Schafer

5

Anda memiliki variabel yang berisi alamat sesuatu. Itu sebuah pointer.

Kemudian Anda memiliki variabel lain yang berisi alamat variabel pertama. Itu adalah pointer ke pointer.


3

Sebuah pointer ke pointer adalah, well, pointer ke pointer.

Contoh bermakna dari someType ** adalah array dua dimensi: Anda memiliki satu array, diisi dengan pointer ke array lain, jadi ketika Anda menulis

dpointer [5] [6]

Anda mengakses array yang berisi pointer ke array lain di posisi ke-5, mendapatkan pointer (biarkan fpointer namanya) dan kemudian mengakses elemen ke-6 dari array yang direferensikan ke array itu (jadi, fpointer [6]).


2
pointer ke pointer seharusnya tidak menjadi bingung dengan array peringkat2, misalnya int x [10] [10] di mana Anda menulis x [5] [6] Anda mengakses nilai dalam array.
Pete Kirkham

Ini hanya contoh di mana kekosongan ** sesuai. Sebuah pointer ke pointer hanyalah sebuah pointer yang menunjuk ke, well, sebuah pointer.
akappa

1

Cara kerjanya: Ini adalah variabel yang dapat menyimpan pointer lain.

Kapan Anda akan menggunakannya: Banyak yang menggunakan salah satunya adalah jika fungsi Anda ingin membuat array dan mengembalikannya ke pemanggil.

//returns the array of roll nos {11, 12} through paramater
// return value is total number of  students
int fun( int **i )
{
    int *j;
    *i = (int*)malloc ( 2*sizeof(int) );
    **i = 11;  // e.g., newly allocated memory 0x2000 store 11
    j = *i;
    j++;
    *j = 12; ;  // e.g., newly allocated memory 0x2004 store 12

    return 2;
}

int main()
{
    int *i;
    int n = fun( &i ); // hey I don't know how many students are in your class please send all of their roll numbers.
    for ( int j=0; j<n; j++ )
        printf( "roll no = %d \n", i[j] );

    return 0;
}


0

Ada banyak sekali penjelasan yang bermanfaat, tetapi saya tidak menemukan deskripsi singkat, jadi ..

Pointer pada dasarnya adalah alamat variabel. Kode ringkasan pendek:

     int a, *p_a;//declaration of normal variable and int pointer variable
     a = 56;     //simply assign value
     p_a = &a;   //save address of "a" to pointer variable
     *p_a = 15;  //override the value of the variable

//print 0xfoo and 15 
//- first is address, 2nd is value stored at this address (that is called dereference)
     printf("pointer p_a is having value %d and targeting at variable value %d", p_a, *p_a); 

Juga info yang berguna dapat ditemukan di topik Apa artinya referensi dan referensi

Dan saya tidak begitu yakin, kapan bisa pointer berguna, tetapi secara umum perlu menggunakannya ketika Anda melakukan beberapa alokasi memori manual / dinamis - malloc, calloc, dll.

Jadi saya harap ini juga akan membantu untuk menjelaskan masalah :)

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.