İşaretçiye işaretçi C'de nasıl çalışır?


171

C işaretçileri için işaretçiler nasıl çalışır? Onları ne zaman kullanırsın?


43
Hayır ödev değil .... sadece bilmek istedim ... çünkü C kodu okuduğumda ben çok görmek.

1
İşaretçiye işaretçi bir şeyin özel bir durumu değildir, bu yüzden boşluk ** hakkında ne anlamadığınızı anlamıyorum.
akappa

2D diziler için en iyi örnek komut satırı argümanları "prog arg1 arg2" saklanır char ** argv. Ve bellek tahsis etmek istiyorum doesnt arayan (denilen fonksiyon bellek tahsis edecek) eğer
resultsway

1
Git 2.0'da "pointer to pointer" kullanımı için güzel bir örnek var: aşağıdaki cevabımı gör
VonC

Yanıtlar:


359

8 bit adresli bir 8 bit bilgisayar (ve sadece 256 bayt bellek) olduğunu varsayalım. Bu, belleğin bir parçasıdır (üstteki sayılar adreslerdir):

  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 |    |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+

Burada görebileceğiniz şey, adres 63'te "merhaba" dizesinin başlamasıdır. Yani bu durumda, eğer hafızadaki "merhaba" nın tek oluşumu buysa,

const char *c = "hello";

... c(salt okunur) "merhaba" dizesine bir işaretçi olarak tanımlar ve bu nedenle 63 değerini içerir. ckendisi bir yerde saklanmalıdır: yukarıdaki örnekte 58 numaralı konumda. Elbette yalnızca karakterleri gösteremeyiz , aynı zamanda diğer göstergelere de. Örneğin:

const char **cp = &c;

Şimdi cpişaret colduğunu, bu adresini içerir c(58 olan). Daha da ileri gidebiliriz. Düşünmek:

const char ***cpp = &cp;

Şimdi cppadresini saklar cp. Yani 55 değerine sahiptir (yukarıdaki örneğe dayanarak) ve tahmin ettiniz: kendisi 60 adresinde saklanıyor.


Neden işaretçileri işaretçiler kullandığına gelince :

  • Bir dizinin adı genellikle ilk öğesinin adresini verir. Bu nedenle, dizi tür öğeler içeriyorsa, diziye yapılan tbir başvuruda tür vardır t *. Şimdi bir dizi diziyi düşünün t: doğal olarak bu 2D diziye yapılan bir başvuruda type (t *)*= olur t **ve dolayısıyla bir işaretçiye işaretçi olur.
  • Bir dizi dizi tek boyutlu görünse de, aslında iki boyutludur, çünkü dizeler karakter dizileridir. Dolayısıyla: char **.
  • Bir işlevin ftür t **değişkenini değiştirmesi gerekiyorsa, tür argümanını kabul etmesi gerekir t *.
  • Burada listelenemeyecek kadar çok sayıda başka neden.

7
evet iyi bir örnek .. ne olduklarını anlıyorum ... ama nasıl ve ne zaman kullanılacağı daha önemlidir .. şimdi ..

2
Stephan temel olarak Kernighan & Richie'nin The C Programming Language'deki şemasını yeniden üreterek iyi bir iş çıkardı. C'yi programlıyorsanız ve bu kitaba sahip değilseniz ve kağıt belgelerinizden hoşlanıyorsanız, almanızı şiddetle tavsiye ederim, (oldukça) mütevazı masraflar üretkenlikte çok hızlı bir şekilde ödeyecektir. Örneklerinde çok açık olma eğilimindedir.
J. Polfer

4
char * c = "merhaba" const olmalıdır char * c = "merhaba". Ayrıca, "bir dizinin ilk elemanın adresi olarak saklandığını" söylemek en yanıltıcıdır. Bir dizi ... bir dizi olarak saklanır. Genellikle adı ilk öğesine bir işaretçi verir, ancak her zaman değil. İşaretçilerden işaretçiler hakkında, basitçe, bir işlev parametre olarak geçirilen bir işaretçiyi değiştirmek zorunda olduklarında yararlı olduklarını söyleyebilirim (daha sonra bunun yerine işaretçiye bir işaretçi iletirsiniz).
Bastien Léonard

4
Bu cevabı yanlış yorumlamadığım sürece yanlış görünüyor. c 58'de saklanır ve 63'ü gösterir, cp 55'te ve 58'i gösterir ve cpp diyagramda gösterilmez.
Thanatos

1
İyi görünüyor. Daha küçük bir mesele beni söylemekten alıkoyan tek şey buydu: Büyük yazı. Açıklamanın kendisi mükemmel. Yukarı oylamaya geçiliyor. (Belki de stackoverflow'un işaretçileri gözden geçirmesi gerekir?)
Thanatos

46

C işaretçileri için işaretçiler nasıl çalışır?

İlk olarak bir işaretçi, diğer değişkenler gibi bir değişkendir, ancak değişkenin adresini tutar.

İşaretçiye işaretçi, diğer değişkenler gibi bir değişkendir, ancak değişkenin adresini tutar. Bu değişken sadece bir işaretçi olur.

Onları ne zaman kullanırsın?

Yığın üzerindeki bir belleğe bir işaretçi döndürmeniz gerektiğinde ancak dönüş değerini kullanmamanız durumunda bunları kullanabilirsiniz.

Misal:

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
}

Ve buna şöyle diyorsun:

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

Her C programının main () argümanının argv için bir işaretçiye işaretçisi olması gibi başka kullanımlar da vardır, burada her öğe komut satırı seçenekleri olan karakter dizisini içerir. Dikkatli olmalısınız, ancak 2 boyutlu dizileri işaret etmek için işaretçiler kullandığınızda, bunun yerine 2 boyutlu diziye bir işaretçi kullanmak daha iyidir.

Neden tehlikeli?

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

Düzgün yapılmış 2 boyutlu bir dizi için bir işaretçi örneği:

int (*myPointerTo2DimArray)[ROWS][COLUMNS]

ROWS ve COLUMNS için değişken sayıda elemanı desteklemek istiyorsanız, 2 boyutlu bir dizi için bir işaretçi kullanamazsınız. Ama önceden bildiğiniz zaman 2 boyutlu bir dizi kullanırsınız.


32

Git 2.0, pointer 7b1004b , işaretçi işaretçi kullanımı bu "gerçek dünya" kod örneği gibi :

Linus bir keresinde şöyle dedi:

Aslında daha fazla insanın gerçekten temel düşük seviyeli kodlamayı anlamasını diliyorum. Kilitsiz isim araması gibi büyük, karmaşık şeyler değil, sadece işaretçilerden işaretleyicilere vb. İyi bir şekilde kullanılır.
Örneğin, "önceki" girişi izleyerek tek bağlantılı bir liste girişini silen çok fazla insan gördüm ve ardından girişi silmek için

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

ve böyle bir kod gördüğümde, "Bu kişi işaretçileri anlamıyor" diye gidiyorum. Ve ne yazık ki oldukça yaygın.

İşaretçileri anlayan insanlar sadece bir " giriş işaretçisine işaretçi " kullanırlar ve bunu list_head adresiyle başlatırlar. Ve sonra listeden geçerken, herhangi bir koşul kullanmadan girdiyi kaldırabilirler.

*pp =  entry->next

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

Bu basitleştirmeyi uygulamak, 2 yorum satırı eklerken bile bu işlevden 7 satır kaybetmemizi sağlar.

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

Chris işaret yorumlarında 2016 video "için Torvalds adlı Çift Pointer Sorunu tarafından" Philip Buuck .


kumar işaret yorumlarında "blog yazısı anlama İşaretçiler üzerinde Linus ," Grisha Trubetskoy açıklıyor:

Bağlantılı bir listeniz olduğunu düşünün:

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

Başından sonuna kadar yinelemeniz ve değeri to_remove değerine eşit olan belirli bir öğeyi kaldırmanız gerekir.
Bunu yapmanın en belirgin yolu:

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

Yukarıda yaptığımız şey:

  • giriş olana kadar liste üzerinde yineleme yapmak, yani listenin NULLsonuna ulaştığımız anlamına gelir (satır 4).
  • Kaldırılmasını istediğimiz bir girişle karşılaştığımızda (satır 5),
    • mevcut bir sonraki işaretçinin değerini bir öncekine atarız,
    • böylece mevcut elemanın ortadan kaldırılması (hat 7).

Yukarıda özel bir durum vardır - yinelemenin başında önceki bir giriş ( previs NULL) yoktur ve bu nedenle listedeki ilk girişi kaldırmak için başın kendisini değiştirmeniz gerekir (satır 9).

Linus'un söylediği şey , yukarıdaki kodun, bir önceki öğeyi yalnızca bir işaretçi yerine bir işaretçi için bir işaretçi haline getirerek basitleştirilebileceğidir .
Kod daha sonra şöyle görünür:

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

Yukarıdaki kod bir önceki varyanta çok benzer, ancak başlangıçta ppolmadığı için listenin ilk öğesinin özel durumunu nasıl izlememiz gerektiğine dikkat edin NULL. Basit ve akıllı.

Ayrıca, bu konudaki biri bunun daha iyi olmasının *pp = entry->nextatomik olmasından kaynaklandığını söyledi . Kesinlikle atomik DEĞİLDİR .
Yukarıdaki ifade iki dereference operatörü ( *ve ->) ve bir ödev içerir ve bu üç şeyden hiçbiri atomik değildir.
Bu yaygın bir yanlış anlamadır, ancak ne yazık ki C'deki hiçbir şeyin atomik olduğu varsayılmamalıdır ( ++ve --operatörler dahil )!


4
Bu daha iyi anlamanıza yardımcı olacaktır - grisha.org/blog/2013/04/02/linus-on-understanding-pointers
kumar

@kumar iyi referans. daha fazla görünürlük için cevaba ekledim.
VonC

Bu video , örneğinizi anlamada benim için çok önemliydi. Özellikle bir bellek diyagramı çizip programın ilerleyişini takip edene kadar kafam karıştı (ve kavgacı). Bununla birlikte, hala benim için biraz gizemli görünüyor.
Chris

@Chris Harika video, bahsettiğiniz için teşekkür ederiz! Daha fazla görünürlük için yorumunuzu cevaba ekledim.
VonC

14

Üniversitedeki bir programlama kursuna ilişkin göstergeleri ele alırken, bunları öğrenmeye nasıl başlayacağımız konusunda iki ipucu verildi. Birincisi Binky ile Pointer Fun'ı görmekti . İkinci düşünmek oldu Haddocks' Gözler Lewis Carroll gelen geçit Looking-Glass sayesinde

“Üzgünsün,” dedi Knight endişeli bir ses tonuyla: “Seni rahatlatmak için sana bir şarkı söyleyeyim.”

“Çok mu uzun?” Diye sordu Alice, çünkü o gün çok fazla şiir duymuştu.

“Uzun,” dedi Şövalye, “ama çok, çok güzel. Beni duyduğunu duyan herkes - ya gözyaşlarını gözlerine getiriyor, ya da başka - ”

"Ya da ne?" dedi Alice, Şövalye ani bir duraklama yapmıştı.

“Yoksa öyle değil, biliyorsun. Şarkının adı 'Haddocks' Eyes 'olarak adlandırılıyor. ”

“Ah, bu şarkının adı, değil mi?” Dedi Alice, ilgilenmeye çalışarak.

"Hayır, anlamıyorsun," dedi Şövalye biraz endişeli görünerek. “İsmin adı bu. Adı gerçekten 'Yaşlı Yaşlı Adam'. ”

“O zaman 'Şarkının adı bu mu?' Demeliydim.” Alice kendini düzeltti.

“Hayır, yapmamalısın: bu başka bir şey! Şarkının adı 'Ways And Means': ama sadece buna denir, bilirsiniz! ”

“Peki, şarkı nedir o zaman?” dedi Alice, o zamana kadar tamamen şaşkına dönmüştü.

"Ben de buna gelecektim," dedi Şövalye. “Şarkı gerçekten 'Bir Kapıda A-Oturmak': ve şarkı benim icadım.”


1
Bu pasajı birkaç kez okumalıydım ... +1 beni düşündürdüğü için!
Ruben Steins

Bu yüzden Lewis Carroll sıradan bir yazar değil.
metarose

1
Yani ... böyle mi gidecek? name -> 'Yaşlı Yaşlı Adam' -> denilen -> 'Mezgit'in Gözleri' -> şarkı -> 'Bir Kapıda A-oturma'
tisaconundrum


7

İşaretçiye başvuru gerektiğinde. Örneğin, çağrılan bir işlevin kapsamı içinde çağrılan bir işlevin içinde bildirilen bir işaretçi değişkeninin değerini (adrese işaret eder) değiştirmek istediğinizde.

Bağımsız değişken olarak tek bir işaretçiyi iletirseniz, çağıranın kapsamındaki orijinal işaretçiyi değil, işaretçinin yerel kopyalarını değiştirirsiniz. İşaretçiye bir işaretçiyle, ikincisini değiştirirsiniz.


'Neden' kısmı için iyi açıklandı
Rana Deep

7

İşaretçiye işaretçi de tanıtıcı olarak adlandırılır . Bunun için bir kullanım genellikle bir nesnenin bellekte taşınabilmesi veya kaldırılabilmesidir. Kişi genellikle nesnenin kullanımını kilitlemek ve kilidini açmaktan sorumludur, böylece nesneye erişilirken taşınmaz.

Genellikle bellek sınırlı ortamlarda, yani Palm OS'de kullanılır.

computer.howstuffworks.com Bağlantı >>

www.flippinbits.com Bağlantı >>


7

Bu kavramı daha iyi anlamak için aşağıdaki şekli ve programı düşünün .

Çift işaretçi diyagramı

Şekil göre, ptr1 a, tek işaretçi değişken adresini sahip num .

ptr1 = #

Benzer şekilde ptr2 a, işaretçi (çift işaretçi) işaretçi işaretçi adresini sahip ptr1 .

ptr2 = &ptr1;

Başka bir işaretçiyi gösteren işaretçi çift işaretçi olarak bilinir. Bu örnekte ptr2 bir çift işaretleyicidir.

Yukarıdaki diyagramdan değerler:

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

Misal:

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

Çıktı:

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

5

işaretçinin adres değerinin bir göstergesidir. (bu korkunç bir şey biliyorum)

temel olarak, bir işaretçiyi başka bir işaretçinin adresinin değerine geçirmenize izin verir, böylece aşağıdaki gibi bir alt işlevden başka bir işaretçinin işaret ettiği yeri değiştirebilirsiniz:

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

üzgünüm, çok kötü olduğunu biliyorum. Şunu
Luke Schafer

5

Bir şeyin adresini içeren bir değişkeniniz var. Bu bir işaretçi.

Sonra ilk değişkenin adresini içeren başka bir değişkeniniz var. Bu bir işaretçi işaretçisi.


3

İşaretçiye işaretçi, işaretçiye bir işaretçidir.

SomeType ** 'ın anlamlı bir örneği iki boyutlu bir dizidir: diğer dizilere işaretçilerle dolu bir diziniz vardır, bu nedenle

DPointer [5] [6]

5. konumunda diğer dizilere işaretçiler içeren diziye erişirsiniz, işaretçiyi alırsınız (adını fpointer olarak gösterir) ve sonra diziye başvurulan dizinin 6. öğesine erişirsiniz (yani, fpointer [6]).


2
işaretçilerden işaretçiler rank2 dizileriyle karıştırılmamalıdır; örneğin, x [5] [6] yazdığınız dizideki değere eriştiğiniz int x [10] [10].
Pete Kirkham

Bu sadece bir boşluğun ** uygun olduğu bir örnektir. İşaretçiye işaretçi yalnızca bir işaretçiyi işaret eden bir işaretçidir.
akappa

1

Nasıl çalışır: Başka bir işaretçiyi saklayabilen bir değişkendir.

Bunları ne zaman kullanırsınız: Birçoğu bunlardan birini kullanır, işleviniz bir dizi oluşturmak ve onu arayana geri döndürmek istiyorsa.

//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

Yararlı açıklamalar çok var, ama ben sadece kısa bir açıklama bulunamadı, bu yüzden ..

Temelde işaretçi değişkenin adresidir. Kısa özet kodu:

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

Ayrıca yararlı bilgiler referans ve başvurunun ne anlama geldiği konusunda bulunabilir.

Ve emin değilim, ne zaman yararlı işaretçiler olabilir, ancak ortak olarak bazı manuel / dinamik bellek ayırma - malloc, calloc, vb.

Bu yüzden umarım sorunlu olanı açıklığa kavuşturmaya yardımcı olur :)

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.