İşaretçinin “kaydı silme” ne demektir?


540

Lütfen açıklamaya bir örnek ekleyin.


bu size yardımcı olabilir: stackoverflow.com/questions/2795575/…
Harry Joy


24
int *p;bir tamsayıya bir işaretçi tanımlar ve *pbu işaretçiyi kullanmaz, yani p'nin işaret ettiği veriyi alır.
Peyman

4
Binky'nin İşaretçi Eğlencesi ( cslibrary.stanford.edu/104 ), şeyleri netleştirebilecek işaretçiler hakkında BÜYÜK bir video. @ Erik- Stanford CS Kütüphanesi bağlantısını kurduğunuz için sallanıyorsunuz. Orada çok fazla güzellik var ...
templatetypedef

6
Harry'nin yanıtı burada yardımcı olanın tam tersidir.
Jim Balter

Yanıtlar:


731

Temel terminolojinin gözden geçirilmesi

Bu var genellikle yeterince iyi - Eğer montaj programlama sürece - bir tahayyül etmek işaretçi 1 2, üçüncü 3 dördüncü ve böylece sürecin belleğinde ikinci bayt atıfta ile sayısal bellek adresini içeren ....

  • 0'a ve ilk bayta ne oldu? Bunu daha sonra ele alacağız - aşağıdaki boş göstergelere bakın.
  • İşaretçilerin depoladığı ve bellek ile adreslerin birbiriyle nasıl ilişkili olduğuna ilişkin daha doğru bir tanım için, bu cevabın sonunda "Bellek adresleri hakkında daha fazla bilgi ve neden bilmeniz gerekmiyor" konusuna bakın .

Eğer bellekte veri / değer erişmek istediğinizde işaretçi işaret ettiği - o sayısal indeksi adresin içeriğini - o zaman dereference işaretçi.

Farklı bilgisayar dillerinin derleyiciye veya yorumlayıcıya şimdi sivri uçlu nesnenin (geçerli) değeriyle ilgilendiğinizi belirtmek için farklı gösterimleri vardır - Aşağıda C ve C ++ üzerine odaklanıyorum.

İşaretçi senaryosu

pAşağıdaki gibi bir işaretçi verilen C'yi düşünün ...

const char* p = "abc";

... 'a', 'b', 'c' harflerini kodlamak için kullanılan sayısal değerlere sahip dört bayt ve metin verilerinin sonunu belirtmek için 0 bayt bellekte bir yerde saklanır ve bunun sayısal adresi veri depolanır p. Bu şekilde C, bellekteki metni kodlar ASCIIZ olarak bilinir .

Örneğin, dize değişmez değeri 0x1000 adresinde ve p32 bit işaretçi 0x2000'de olsaydı, bellek içeriği şöyle olur:

Memory Address (hex)    Variable name    Contents
1000                                     'a' == 97 (ASCII)
1001                                     'b' == 98
1002                                     'c' == 99
1003                                     0
...
2000-2003               p                1000 hex

Not orada adresinde 0x1000 için hiçbir değişken isim / tanımlayıcı, ama biz dolaylı adresini depolamak bir işaretçi kullanarak değişmez dize ifade ettiğini daha: p.

İşaretçinin kaydının silinmesi

Karakterlere atıfta bulunmak için, bu gösterimlerden birini (tekrar C için) kullanmaktan pvazgeçtik p:

assert(*p == 'a');  // The first character at address p will be 'a'
assert(p[1] == 'b'); // p[1] actually dereferences a pointer created by adding
                     // p and 1 times the size of the things to which p points:
                     // In this case they're char which are 1 byte in C...
assert(*(p + 1) == 'b');  // Another notation for p[1]

İşaretçileri sivri uçlu veriler arasında taşıyabilir ve gittikçe kayıtlarını kaldırabilirsiniz:

++p;  // Increment p so it's now 0x1001
assert(*p == 'b');  // p == 0x1001 which is where the 'b' is...

Eğer yazılabilecek bazı verileriniz varsa, bunun gibi şeyler yapabilirsiniz:

int x = 2;
int* p_x = &x;  // Put the address of the x variable into the pointer p_x
*p_x = 4;       // Change the memory at the address in p_x to be 4
assert(x == 4); // Check x is now 4

Yukarıda derleme zamanında bir değişkene ihtiyaç duyacağınızı xbilmelisiniz ve kod, derleyiciden nerede saklanacağını ayarlamasını ve adresin kullanılabilir olmasını sağlamanızı ister &x.

Bir yapı veri üyesinin kaydının silinmesi ve erişilmesi

C'de, veri üyelerine sahip bir yapıya işaretçi olan bir değişkeniniz varsa, kayıt ->silme işlecini kullanarak bu üyelere erişebilirsiniz :

typedef struct X { int i_; double d_; } X;
X x;
X* p = &x;
p->d_ = 3.14159;  // Dereference and access data member x.d_
(*p).d_ *= -1;    // Another equivalent notation for accessing x.d_

Çok baytlı veri türleri

Bir işaretçi kullanmak için, bir bilgisayar programının da işaret edilen veri türüne ilişkin bir kavrayışa ihtiyacı vardır - bu veri türünün temsil etmek için birden fazla bayta ihtiyacı varsa, işaretçi normalde verilerdeki en düşük numaralı bayta işaret eder.

Yani, biraz daha karmaşık bir örneğe bakmak:

double sizes[] = { 10.3, 13.4, 11.2, 19.4 };
double* p = sizes;
assert(p[0] == 10.3);  // Knows to look at all the bytes in the first double value
assert(p[1] == 13.4);  // Actually looks at bytes from address p + 1 * sizeof(double)
                       // (sizeof(double) is almost always eight bytes)
++p;                   // Advance p by sizeof(double)
assert(*p == 13.4);    // The double at memory beginning at address p has value 13.4
*(p + 2) = 29.8;       // Change sizes[3] from 19.4 to 29.8
                       // Note earlier ++p and + 2 here => sizes[3]

Dinamik olarak ayrılan belleğe işaretçiler

Bazen programınız çalışana kadar ne kadar belleğe ihtiyacınız olduğunu bilmiyorsunuz ve hangi verilerin ona atıldığını görüyorlar ... sonra kullanarak belleği dinamik olarak ayırabilirsiniz malloc. Adresi bir işaretçide saklamak yaygın bir uygulamadır ...

int* p = (int*)malloc(sizeof(int)); // Get some memory somewhere...
*p = 10;            // Dereference the pointer to the memory, then write a value in
fn(*p);             // Call a function, passing it the value at address p
(*p) += 3;          // Change the value, adding 3 to it
free(p);            // Release the memory back to the heap allocation library

C ++ 'da, bellek ayırma normalde newoperatör ile yapılır ve aşağıdakilerle ayrılır delete:

int* p = new int(10); // Memory for one int with initial value 10
delete p;

p = new int[10];      // Memory for ten ints with unspecified initial value
delete[] p;

p = new int[10]();    // Memory for ten ints that are value initialised (to 0)
delete[] p;

Ayrıca aşağıdaki C ++ akıllı işaretçilerine de bakın.

Adresleri kaybetme ve sızdırma

Genellikle bir işaretçi, bazı verilerin veya ara belleğin bellekte nerede bulunduğunun tek göstergesi olabilir. Bu veri / arabellek sürekli olarak kullanılıyorsa free()veya deletehafızayı sızdırma veya aramadan kaçınma yeteneği gerekiyorsa, programlayıcının işaretçinin bir kopyası üzerinde çalışması gerekir ...

const char* p = asprintf("name: %s", name);  // Common but non-Standard printf-on-heap

// Replace non-printable characters with underscores....
for (const char* q = p; *q; ++q)
    if (!isprint(*q))
        *q = '_';

printf("%s\n", p); // Only q was modified
free(p);

... veya herhangi bir değişikliğin geri alınmasını dikkatli bir şekilde düzenleyin ...

const size_t n = ...;
p += n;
...
p -= n;  // Restore earlier value...
free(p);

C ++ akıllı işaretçiler

C ++ 'da, işaretçileri saklamak ve yönetmek için akıllı işaretçi nesnelerini kullanmak , akıllı işaretçilerin yıkıcıları çalıştığında bunları otomatik olarak yeniden konumlandırmak en iyi uygulamadır . C ++ 11'den beri Standart Kütüphane iki tane sunar, unique_ptrçünkü ayrılan bir nesne için tek bir sahip olduğunda ...

{
    std::unique_ptr<T> p{new T(42, "meaning")};
    call_a_function(p);
    // The function above might throw, so delete here is unreliable, but...
} // p's destructor's guaranteed to run "here", calling delete

... ve shared_ptrhisse sahipliği için ( referans sayımı kullanarak ) ...

{
    auto p = std::make_shared<T>(3.14, "pi");
    number_storage1.may_add(p); // Might copy p into its container
    number_storage2.may_add(p); // Might copy p into its container    } // p's destructor will only delete the T if neither may_add copied it

Boş göstericiler

C'de NULLve 0- ve ek olarak C ++ ' nullptrda, bir işaretçinin bir değişkenin bellek adresini şu anda tutmadığını ve işaretleyicinin aritmetik işleminde kullanılmaması veya kullanılmaması gerektiğini belirtmek için kullanılabilir. Örneğin:

const char* p_filename = NULL; // Or "= 0", or "= nullptr" in C++
int c;
while ((c = getopt(argc, argv, "f:")) != -1)
    switch (c) {
      case f: p_filename = optarg; break;
    }
if (p_filename)  // Only NULL converts to false
    ...   // Only get here if -f flag specified

C ve C ++, dahili sayısal türleri mutlaka varsayılan yok gibi 0, ne boolskadar false, işaretçileri her zaman ayarlı olmayan NULL. Tüm bunlar staticdeğişken olduğunda veya statik nesnelerin veya tabanlarının doğrudan veya dolaylı üye değişkenleri olduğunda veya sıfır başlatma işlemine (örn. new T();Ve new T(x, y, z);işaretçiler dahil T üyelerinde sıfır başlatma gerçekleştirirken ) 0 / false / NULL olarak ayarlanır. new T;değil).

Ayrıca, ve bir işaretçiye atadığınızda 0, işaretçideki bitlerin tümü sıfırlanmayabilir: işaretçi donanım düzeyinde "0" içermeyebilir veya sanal adres alanınızdaki 0 ​​adresine başvurabilir. Derleyici başka orada hiç bir neden varsa mağaza şeye izin ama ne yaparsa edilir - sen geldin ve işaretçiyi karşılaştırırsanız , , ya da bunlardan biri atandı başka işaretçi, karşılaştırma zorunluluk çalışması beklendiği gibi. Yani, derleyici düzeyindeki kaynak kodu altında, "NULL" C ve C ++ dillerinde potansiyel olarak biraz "büyülü" ...NULLnullptr0NULLnullptr

Bellek adresleri ve neden muhtemelen bilmeniz gerekmediği hakkında daha fazla bilgi

Daha kesin olarak, başlatılan işaretçiler ya NULL( ya da genellikle sanal ) bir bellek adresini tanımlayan bir bit kalıbı saklar .

Basit durum, bunun sürecin tüm sanal adres alanına sayısal bir ofset olduğu yerdir; daha karmaşık durumlarda işaretçi, CPU'nun CPU "segment" kayıtlarına veya bit örüntüsünde kodlanan bir segment kimliği türüne göre seçebileceği ve / veya adres kullanarak makine kodu talimatları.

Örneğin, bir değişkene int*işaret etmek için uygun şekilde başlatılan - "GPU" belleğindeki intbir float*erişim belleğine değiştikten intsonra , değişkenin bulunduğu bellekten oldukça farklı olabilir , daha sonra bir kez bir işlev işaretçisi olarak yayınlanır ve bir işlev işaretçisi olarak kullanılırsa program için ayrı bellek tutma makinesi opcodes ( int*bu diğer bellek bölgelerinde etkili bir şekilde rastgele, geçersiz bir işaretçinin sayısal değeri ile ).

C ve C ++ gibi 3GL programlama dilleri bu karmaşıklığı gizleme eğilimindedir, böylece:

  • Derleyici bir değişkene veya işleve bir işaretçi verirse, onu serbestçe serbest bırakabilirsiniz (değişkenin zarar görmediği / dağıtılmadığı sürece) ve derleyicinin sorunu, örneğin belirli bir CPU segmenti kaydının önceden geri yüklenmesi gerekip gerekmediği veya kullanılan farklı makine kodu talimatı

  • Dizideki bir öğeye işaretçi alırsanız, dizideki başka herhangi bir yere gitmek için işaretçi aritmetiği kullanabilir veya hatta dizinin öğelere diğer işaretçilerle karşılaştırılması yasal olan bir adresin sonunu geçecek bir adres oluşturabilirsiniz. dizide (veya benzer şekilde işaretçi aritmetiği tarafından aynı son-son değerine taşınmış); yine C ve C ++ 'da, bu "sadece çalışır" sağlamak derleyiciye kalmış

  • Paylaşılan bellek eşlemesi gibi belirli işletim sistemi işlevleri size işaretçiler verebilir ve onlar için anlamlı olan adres aralığında "çalışır"

  • Yasal işaretçileri bu sınırların ötesine taşıma veya işaretleyicilere rastgele sayılar kullanma veya ilişkisiz türlere atanmış işaretçiler kullanma girişimleri, genellikle tanımlanmamış davranışa sahiptir , bu nedenle daha üst düzey kitaplıklarda ve uygulamalarda kaçınılmalıdır, ancak işletim sistemleri, aygıt sürücüleri vb. C veya C ++ Standardı tarafından tanımlanmamış bırakılmış davranışlara dayanması gerekebilir, bu yine de özel uygulamaları veya donanımları tarafından iyi tanımlanmıştır.


olduğu p[1] ve *(p + 1) aynı ? Yani, aynı talimatları yapar p[1] ve *(p + 1)üretir mi?
Pacerier

2
@Pacerier: N1570 taslak C Standardında 6.5.2.1/2'den (ilk önce çevrimiçi buldum) "Alt simge operatörünün [] tanımı, E1 [E2] 'nin (* ((E1) + (E2)) ile aynı olması )." - Bir derleyicinin derlemenin erken bir aşamasında bunları hemen aynı özdeşliklere dönüştürmemesinin, bundan sonra aynı optimizasyonları uygulamamasının bir nedenini hayal edemiyorum, ancak kodun kesinlikle aynı olduğunu kimsenin kanıtlayabileceğini görmüyorum Yazılan her derleyiciyi incelemeden.
Tony Delroy

3
@ Bal: 1000 hex değeri tek bir bayt (8 bit) bellekte kodlamak için çok büyük: yalnızca 0 ile 255 arasındaki işaretsiz sayıları bir baytta depolayabilirsiniz. Yani, 1000 hex'i sadece 2000 adresinde "sadece" saklayamazsınız. Bunun yerine, 32 bitlik bir sistem, 2000 ile 2003 arasındaki adreslerle birlikte 32 bit (dört bayt) kullanır. 64 bitlik bir sistem 64 Her iki durumda da, temel adresi psadece 2000'dir: başka bir işaretçiniz pvarsa, 2000'i dört veya sekiz baytında saklamanız gerekir. Umarım yardımcı olur! Şerefe.
Tony Delroy

1
@TonyDelroy: Bir birlik ubir dizi içeriyorsa arr, hem gcc hem de clang, değerin u.arr[i]diğer sendika üyeleriyle aynı depolama alanına erişebileceğini tanır, ancak değerin *(u.arr+i)bunu yapabileceğini fark etmez. Bu derleyicilerin yazarlarının, ikincisinin UB'yi çağırdığını mı, yoksa eski UB'yi mi çağırdığını düşünüyor musunuz, ancak yine de yararlı bir şekilde işlemek zorundalar, ancak iki ifadeyi açıkça farklı görüyorlar.
supercat

3
Ben nadiren işaretçiler gördüm ve C / C ++ içinde kullanımı çok kısaca ve basit bir şekilde açıkladı.
kayleeFrye_onDeck

102

İşaretçinin kaydının kaldırılması, işaretçinin işaret ettiği bellek konumunda depolanan değerin alınması anlamına gelir. Operatör * bunu yapmak için kullanılır ve kayıt silme operatörü olarak adlandırılır.

int a = 10;
int* ptr = &a;

printf("%d", *ptr); // With *ptr I'm dereferencing the pointer. 
                    // Which means, I am asking the value pointed at by the pointer.
                    // ptr is pointing to the location in memory of the variable a.
                    // In a's location, we have 10. So, dereferencing gives this value.

// Since we have indirect control over a's location, we can modify its content using the pointer. This is an indirect way to access a.

 *ptr = 20;         // Now a's content is no longer 10, and has been modified to 20.

15
İşaretçi bir değeri göstermez , bir nesneyi gösterir .
Keith Thompson

51
@KeithThompson İşaretçi bir nesneyi göstermez, bir nesnenin (belki de ilkel) bulunduğu bir bellek adresini gösterir.
mg30rg

4
@ mg30rg: Hangi ayrımı yaptığınızdan emin değilim. İşaret değeri olan bir adres. Bir nesne, tanım gereği, "yürütme ortamında, içeriği değerleri temsil edebilen veri depolama bölgesi" dir. Peki "ilkel" ile ne demek istiyorsun? C standardı bu terimi kullanmaz.
Keith Thompson

6
@KeithThompson Zorlukla işaret ediyordum, cevaba gerçekten değer katmadınız, sadece terminolojiyi nitelendiriyordunuz (ve bunu da yanlış yaptınız). İşaretçi değeri kesinlikle bir adrestir, bu bir bellek adresini "gösterir". OOPdriven dünyamızda "nesne" kelimesi yanıltıcı olabilir, çünkü "sınıf örneği" olarak yorumlanabilir (evet, sorunun [C ++] değil [C] olarak etiketlendiğinin farkında değildim) ve kelimeyi kullandım "copmlex" in tersi gibi "ilkel" (bir yapı veya sınıf gibi veri yapısı).
mg30rg

3
Bu yanıta dizi alt simge operatörünün []de bir işaretçi ( a[b]anlam olarak tanımlanmıştır *(a + b)) dereferences olduğunu ekleyelim .
cmaster - eski haline monica

20

İşaretçi bir değere "referans" tır. Kütüphane çağrı numarası gibi bir kitap referansıdır. "Dereferencing" çağrı numarası fiziksel olarak bu kitabı alıyor ve alıyor.

int a=4 ;
int *pA = &a ;
printf( "The REFERENCE/call number for the variable `a` is %p\n", pA ) ;

// The * causes pA to DEREFERENCE...  `a` via "callnumber" `pA`.
printf( "%d\n", *pA ) ; // prints 4.. 

Kitap orada değilse, kütüphaneci bağırmaya başlar, kütüphaneyi kapatır ve birkaç kişi orada olmayan bir kitap bulacak kişinin nedenini araştırmaya hazırdır.


17

Basit bir ifadeyle, kayıt silme, değerin, işaretçinin işaret ettiği belirli bir hafıza konumundan erişilmesi anlamına gelir.


7

İşaretçi Temelleri'nden kod ve açıklama :

Dereference işlemi işaretçiden başlar ve sivrisine erişmek için okunu takip eder. Amaç, sivri devlete bakmak veya sivri devleti değiştirmek olabilir. İşaretçideki dereference işlemi yalnızca işaretçide bir nokta işareti varsa çalışır - nokta işareti tahsis edilmeli ve işaretçi işaret edecek şekilde ayarlanmalıdır. İşaretçi kodundaki en yaygın hata, pointee'yi ayarlamayı unutmaktır. Koddaki bu hata nedeniyle en yaygın çalışma zamanı çökmesi başarısız bir dereference işlemidir. Java'da yanlış dereference çalışma zamanı sistemi tarafından kibarca işaretlenecektir. C, C ++ ve Pascal gibi derlenmiş dillerde, yanlış dereference bazen çökebilir ve diğer zamanlarda hafızayı bazı ince, rastgele bir şekilde bozar.

void main() {   
    int*    x;  // Allocate the pointer x
    x = malloc(sizeof(int));    // Allocate an int pointee,
                            // and set x to point to it
    *x = 42;    // Dereference x to store 42 in its pointee   
}

Aslında x'in işaret etmesi gereken yere bellek ayırmanız gerekir. Örneğin, tanımlanmamış bir davranışı var.
Peyman

3

Önceki tüm cevapların yanlış olduğunu düşünüyorum, çünkü kayıt silme gerçek değere erişmek anlamına geliyor. Wikipedia bunun yerine doğru tanımı verir: https://en.wikipedia.org/wiki/Dereference_operator

Bir işaretçi değişkeni üzerinde çalışır ve işaretçi adresindeki değere eşdeğer bir l değeri döndürür. Buna işaretçiyi "kayıttan çıkarma" denir.

Bununla birlikte, işaret ettiği değere hiç erişmeden işaretçiyi serbest bırakabiliriz. Örneğin:

char *p = NULL;
*p;

NULL işaretçisine değerine erişmeden başvurusunu iptal ettik. Ya da yapabiliriz:

p1 = &(*p);
sz = sizeof(*p);

Yine, kayıttan çıkarma, ancak değere asla erişme. Böyle bir kod çökmez: Verilere gerçekte geçersiz bir işaretçi ile eriştiğinizde çökme meydana gelir . Bununla birlikte, ne yazık ki, standarda göre, gerçek verilere dokunmaya çalışmasanız bile, geçersiz bir işaretçinin silinmesi tanımlanmamış bir davranıştır (birkaç istisna dışında).

Kısacası: işaretçinin kayıttan çıkarılması, kayıttan çıkarma operatörünün ona uygulanması anlamına gelir. Bu operatör, ileride kullanmanız için bir l değeri döndürür.


iyi, segmentasyon hatasına yol açacak bir NULL işaretçisi kaydettiniz.
arjun gaur

bunun üzerine, aslında bir işaretçi tarafından işaret edilen / değerin elde edilmesi / bir işaretçi tarafından işaret edilen bir bellek konumundaki bir değere erişilmesi anlamına gelen 'bir işaretçi elden çıkarılma' değil, 'dereferencing operatörü' için arama yaptınız.
arjun gaur

Denedin mi? Yaptım. Aşağıdakiler çökmez: `#include <stdlib.h> int main () {char * p = NULL; * p; dönüş 0; } ``
stsp

1
@stsp Kodun şimdi çökmediği için, gelecekte veya başka bir sistemde olmayacağı anlamına gelmez.

1
*p;tanımlanmamış davranışa neden olur. Eğer değer erişmez dereferencing doğru olduğunu olmasına rağmen , kendi başına , kod *p; yapar erişim değerini.
MM
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.