C # Referansı ile İşaretçi arasındaki fark nedir?


86

C # başvurusu ile işaretçi arasındaki farkı tam olarak anlamıyorum. İkisi de hafızada bir yeri işaret ediyor değil mi? Anlayabildiğim tek fark, işaretçilerin o kadar akıllı olmaması, yığın üzerindeki hiçbir şeyi gösterememesi, çöp toplamadan muaf olması ve yalnızca yapılara veya temel türlere başvurabilmesidir.

Sormamın nedenlerinden biri, insanların iyi bir programcı olmak için işaretçileri (C'den) iyi anlamaları gerektiğine dair bir algı olması. Daha yüksek seviyeli dilleri öğrenen birçok insan bunu kaçırır ve bu nedenle bu zayıflığa sahiptir.

Bir işaretçi hakkında bu kadar karmaşık olanı anlamadım mı? Temelde hafızadaki bir yere atıfta bulunuyor, değil mi? Konumunu geri döndürebilir ve o konumdaki nesne ile doğrudan etkileşime girebilir mi?

Büyük bir noktayı kaçırdım mı?


1
Kısa cevap, evet, makul derecede önemli bir şeyi kaçırdınız ve "... insanların işaretçileri anlamaları gerektiği algısı" nın nedeni budur. İpucu: C # mevcut tek dil değil.
jdigital

Yanıtlar:


51

C # referansları çöp toplayıcı tarafından yeniden konumlandırılabilir ve yerleştirilecektir, ancak normal işaretçiler statiktir. Bu nedenle fixed, bir dizi elemanına bir işaretçi alırken hareket etmesini önlemek için anahtar kelimeyi kullanırız.

DÜZENLEME: Kavramsal olarak, evet. Aşağı yukarı aynıdırlar.


Bir C # referansının referans olduğu nesnenin GC tarafından taşınmasını engelleyen başka bir komut yok mu?
Richard

Üzgünüm, bunun başka bir şey olduğunu düşündüm çünkü gönderi bir işaretçiye atıfta bulundu.
Richard

Evet, bir GCHandle.Alloc veya bir Marshal.AllocHGlobal (
sabitin

C #, pin_ptr'de C ++ / CLI'de düzeltildi
mmx

Marshal.AllocHGlobal, yönetilen yığın içinde bellek ayırmaz ve doğal olarak çöp toplamaya tabi değildir.
mmx

133

İşaretçi ve referans arasında küçük ama son derece önemli bir ayrım vardır. Bir işaretçi bellekteki bir yere işaret ederken, bir referans bellekteki bir nesneye işaret eder. Göstericiler, gösterdikleri hafızanın doğruluğunu garanti edemeyecekleri için "güvenli tip" değildirler.

Örneğin aşağıdaki kodu alın

int* p1 = GetAPointer();

Bu, GetAPointer'ın int * ile uyumlu bir tür döndürmesi gerektiği anlamında tür güvenlidir. Yine de, * p1'in aslında bir int'i işaret edeceğinin garantisi yoktur. Bir karakter, çift veya rastgele belleğe bir işaretçi olabilir.

Ancak bir referans, belirli bir nesneye işaret eder. Nesneler bellekte hareket ettirilebilir ancak referans geçersiz kılınamaz (güvenli olmayan kod kullanmadığınız sürece). Referanslar bu açıdan işaretçilerden çok daha güvenlidir.

string str = GetAString();

Bu durumda str, iki durumdan birine sahiptir 1) hiçbir nesneye işaret etmez ve dolayısıyla boştur veya 2) geçerli bir dizgeye işaret eder. Bu kadar. CLR, durumun böyle olmasını garanti eder. Bir işaretçi için olamaz ve olmayacak.


13

Referans, "soyut" bir göstericidir: bir referansla aritmetik yapamazsınız ve değeriyle herhangi bir düşük seviyeli numara oynayamazsınız.


8

Bir referans ve bir işaretçi arasındaki en büyük fark, bir göstericinin, içeriği yalnızca bir işaretçi olarak aktif olarak kullanıldığında önemli olan bitlerin bir toplamı olmasıdır, oysa bir referans yalnızca bir bit kümesini değil, aynı zamanda varlığından haberdar olan temel çerçeve. Bellekte bir nesne için bir işaretçi varsa ve bu nesne silinmiş ancak işaretçi silinmemişse, işaretçinin varlığını sürdürmesi, işaret ettiği belleğe erişme girişiminde bulunulmadıkça veya yapılıncaya kadar herhangi bir zarar vermez. İşaretçiyi kullanmak için herhangi bir girişimde bulunulmazsa, hiçbir şey onun varlığı ile ilgilenmez. Buna karşılık, .NET veya JVM gibi referans tabanlı çerçeveler, sistemin var olan her nesne referansını tanımlamasının her zaman mümkün olmasını gerektirir ve var olan her nesne referansı her zamannull ya da uygun türde bir nesneyi tanımlayın.

Her nesne referansının aslında iki tür bilgiyi kapsadığına dikkat edin: (1) tanımladığı nesnenin alan içeriği ve (2) aynı nesneye yönelik diğer referanslar kümesi. Sistemin bir nesneye yönelik var olan tüm referansları hızlı bir şekilde tanımlayabileceği herhangi bir mekanizma olmamasına rağmen, bir nesneye yönelik var olan diğer referanslar kümesi, genellikle bir referans tarafından kapsüllenen en önemli şey olabilir (bu özellikle tür Objectşeyler, kilit jetonları gibi şeyler olarak kullanılır). Sistem, içinde kullanılmak üzere her nesne için birkaç bit veri saklasa da GetHashCode, nesnelerin kendileri için var olan referanslar kümesinin ötesinde gerçek bir kimliği yoktur. Bir Xnesnenin mevcut tek referansını tutuyorsa,Xaynı alan içeriklerine sahip yeni bir nesneye yapılan bir referans, tarafından döndürülen bitleri değiştirmek dışında tanımlanabilir bir etkiye sahip olmayacaktır GetHashCode()ve bu etki bile garanti edilmez.


5

İşaretçiler, bellek adres alanındaki bir konuma işaret eder. Referanslar bir veri yapısına işaret eder. Veri yapılarının tümü çöp toplayıcı tarafından (bellek alanını sıkıştırmak için) her zaman (pekala, o kadar sık ​​değil, ama ara sıra) taşındı. Ayrıca dediğin gibi referanssız veri yapıları bir süre sonra çöp toplanacak.

Ayrıca, işaretçiler yalnızca güvenli olmayan bağlamda kullanılabilir.


5

Bence geliştiriciler için işaretçi kavramını , yani dolaylılığı anlamanın önemli olduğunu düşünüyorum . Bu, mutlaka işaretçiler kullanmaları gerektiği anlamına gelmez. Bu anlamak da önemlidir referans kavramı değişmesidir pointer kavramına , referans uygulanması hemen hemen her zaman her ne kadar sadece ustaca, ama bu olan bir işaretçi.

Yani, bir referansı tutan bir değişken, nesneye bir işaretçi tutan işaretçi boyutunda bir bellek bloğudur. Bununla birlikte, bu değişken, bir işaretçi değişkeninin kullanılabileceği şekilde kullanılamaz. C # (ve C ve C ++, ...) 'de, bir işaretçi bir dizi gibi dizine alınabilir, ancak bir başvuru olamaz. C # 'da, çöp toplayıcı tarafından bir başvuru izlenir, bir işaretçi olamaz. C ++ 'da, bir işaretçi yeniden atanabilir, bir referans atanamaz. Sözdizimsel ve anlamsal olarak işaretçiler ve referanslar oldukça farklıdır, ancak mekanik olarak aynıdırlar.


Dizi olayı kulağa ilginç geliyor, temelde işaretçiye, bunu bir referansla yapamazken bellek konumunu bir dizi gibi kaydırmasını söyleyebileceğiniz yer? Ne zaman faydalı olacağını ama ilginç olacağını düşünemiyorum.
Richard

P bir int * ise (bir int işaretçisi), bu durumda (p + 1) p + 4 bayt (bir int boyutu) ile tanımlanan adrestir. Ve p [1], * (p + 1) ile aynıdır (yani, p'den 4 baytı geçen adresi "referans alır"). Buna karşılık, bir dizi başvurusuyla (C # 'da), [] operatörü bir işlev çağrısı gerçekleştirir.
P Baba

5

Öncelikle, sematiğinizde bir "İşaretçi" tanımlamanız gerektiğini düşünüyorum. Güvenli olmayan kodda sabit ile oluşturabileceğiniz işaretçiyi mi kastediyorsunuz ? Yerel bir aramadan veya Marshal.AllocHGlobal'dan aldığınız bir IntPtr'yi mi kastediyorsunuz ? GCHandle mı demek istiyorsun ? Hepsi aslında aynı şeydir - bir şeyin depolandığı bir hafıza adresinin temsili - bir sınıf, sayı, yapı, her neyse. Ve kayıt için, kesinlikle yığında olabilirler.

Bir işaretçi (yukarıdaki tüm sürümler) sabit bir öğedir. GC'nin bu adreste ne olduğu hakkında hiçbir fikri yoktur ve bu nedenle nesnenin belleğini veya yaşamını yönetme yeteneği yoktur. Bu, çöp toplama sisteminin tüm faydalarını kaybedeceğiniz anlamına gelir. Nesne belleğini manuel olarak yönetmelisiniz ve sızıntı potansiyeline sahipsiniz.

Öte yandan bir referans, hemen hemen GC'nin bildiği bir "yönetilen işaretçi" dir. Hala bir nesnenin adresidir, ancak artık GC hedefin ayrıntılarını bilir, böylece onu hareket ettirebilir, sıkıştırma yapabilir, sonlandırabilir, atabilir ve yönetilen bir ortamın yaptığı diğer tüm güzel şeyleri yapabilir.

Asıl fark, gerçekten, onları nasıl ve neden kullanacağınızdır. Yönetilen bir dildeki vakaların büyük çoğunluğu için bir nesne referansı kullanacaksınız. İşaretçiler, birlikte çalışma yapmak için kullanışlı hale gelir ve gerçekten hızlı çalışmaya nadiren ihtiyaç duyulur.

Düzenleme: Aslında , yönetilen kodda ne zaman bir "işaretçi" kullanabileceğinize dair güzel bir örnek - bu durumda bu bir GCHandle'dır, ancak aynı şey AllocHGlobal ile veya bir bayt dizisi veya yapı üzerinde sabit kullanılarak da yapılabilirdi. Bana daha fazla ".NET" hissettirdiği için GCHandle'ı tercih etme eğilimindeyim.


Belki de burada - korkutucu alıntılarla bile - "yönetilen işaretçi" dememelisiniz, çünkü bu, IL'deki bir nesne referansından oldukça farklı bir şeydir. C ++ / CLI'de yönetilen işaretçiler için sözdizimi olmasına rağmen, bunlara genellikle C # üzerinden erişilemez. IL'de, (ie) ldloca ve ldarga talimatları ile elde edilirler.
Glenn Slayden

5

Bir işaretçi, uygulamanın adres alanındaki herhangi bir baytı işaret edebilir. Bir referans, .NET ortamı tarafından sıkı bir şekilde kısıtlanır ve kontrol edilir ve yönetilir.


1

İşaretçilerle ilgili onları biraz karmaşık yapan şey, ne oldukları değil, onlarla neler yapabileceğinizdir. Ve bir işaretçiye bir işaretçiye sahip olduğunuzda. İşte o zaman gerçekten eğlenmeye başlar.


1

Referansların işaretçiler üzerindeki en büyük avantajlarından biri daha fazla basitlik ve okunabilirliktir. Her zaman olduğu gibi, bir şeyi basitleştirdiğinizde, kullanımını kolaylaştırırsınız, ancak esneklik ve kontrol pahasına, düşük seviyeli şeylerle elde edersiniz (diğer insanların da bahsettiği gibi).

İşaretçiler genellikle 'çirkin' oldukları için eleştirilir.

class* myClass = new class();

Şimdi onu her kullandığınızda, önce ya da

myClass->Method() or (*myClass).Method()

Okunabilirliğin bir kısmını kaybetmesine ve karmaşıklık eklemesine rağmen, insanların yine de, gerçek nesneyi değiştirebilmeniz için (değere göre geçmek yerine) ve büyük nesneleri kopyalamak zorunda kalmadan performans kazanımı için işaretçileri sık sık parametre olarak kullanmaları gerekiyordu.

Bana göre bu, işaretçilerle aynı faydayı sağlamak için, ancak tüm bu işaretçi sözdizimi olmadan referansların 'doğuşunun' nedenidir. Artık gerçek nesneyi (sadece değerini değil) iletebilirsiniz VE nesne ile daha okunaklı, normal bir etkileşim yoluna sahipsiniz.

MyMethod(&type parameter)
{
   parameter.DoThis()
   parameter.DoThat()
}

C ++ referansları, C # / Java referanslarından farklıdır, çünkü ona bir değer atadıktan sonra, onu yeniden atayamazsınız (ve bildirildiğinde atanması gerekir). Bu, bir const işaretçisi (başka bir nesneye yeniden işaret edilemeyen bir işaretçi) kullanmakla aynıydı.

Java ve C #, yıllar boyunca C / C ++ 'da birikmiş birçok karmaşayı temizleyen çok yüksek seviyeli, modern dillerdir ve işaretçiler kesinlikle' temizlenmesi 'gereken şeylerden biriydi.

İşaretçileri bilmek hakkındaki yorumunuz sizi daha güçlü bir programcı yaptığına göre, bu çoğu durumda doğrudur. Bir şeyin sadece bilmeden kullanmak yerine 'nasıl' çalıştığını biliyorsanız, bunun size genellikle bir avantaj sağlayacağını söyleyebilirim. Bir kenarın ne kadarı her zaman değişecektir. Sonuçta, nasıl uygulandığını bilmeden bir şeyi kullanmak, OOP ve Arayüzlerin birçok güzelliğinden biridir.

Bu özel örnekte, işaretçiler hakkında ne bilmek size referanslar konusunda yardımcı olur? Bir C # referansının nesnenin kendisi OLMADIĞINI, ancak nesneyi işaret ettiğini anlamak çok önemli bir kavramdır.

# 1: Değere göre GEÇMİYORSUNUZ Başlangıç ​​olarak, bir işaretçi kullandığınızda işaretçinin sadece bir adres tuttuğunu bilirsiniz, hepsi bu. Değişkenin kendisi neredeyse boş ve bu yüzden argüman olarak geçmek çok güzel. Performans kazancına ek olarak, gerçek nesneyle çalışıyorsunuz, bu nedenle yaptığınız herhangi bir değişiklik geçici değil

# 2: Çok Biçimlilik / Arayüzler Arayüz türü olan ve bir nesneye işaret eden bir referansınız olduğunda, nesnenin daha fazla yeteneği olsa bile, yalnızca o arayüzün yöntemlerini çağırabilirsiniz. Nesneler ayrıca aynı yöntemleri farklı şekilde uygulayabilir.

Bu kavramları iyi anlıyorsanız, işaretçi kullanmadığınız için çok fazla şey kaçırdığınızı sanmıyorum. C ++ genellikle programlama öğrenmek için bir dil olarak kullanılır çünkü bazen ellerinizi kirletmek iyidir. Ayrıca, alt düzey yönlerle çalışmak, modern bir dilin rahatlığını takdir etmenizi sağlar. C ++ ile başladım ve şimdi bir C # programcısıyım ve ham işaretçilerle çalışmanın başlık altında neler olup bittiğini daha iyi anlamama yardımcı olduğunu hissediyorum.

Herkesin işaretçilerle başlamasının gerekli olduğunu sanmıyorum, ancak önemli olan neden değer türleri yerine referansların kullanıldığını anlamaları ve bunu anlamanın en iyi yolu atasına, işaretçiye bakmaktır.


1
Şahsen, C # .kullanılan çoğu yerde daha iyi bir dil olacağını düşünüyorum ->, ancak foo.bar(123)statik yönteme çağrı ile eşanlamlıydı fooClass.bar(ref foo, 123). Bu şu gibi şeylere izin verirdi myString.Append("George"); [olan değişiklikler olur değişken myString ] arasında ve anlamları da o kadar çok büyük değişiklikler myStruct.field = 3;ve myClassObject->field = 3;.
supercat
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.