İşaretçiler / özyineleme hakkında bu kadar zor olan nedir? [kapalı]


20

Java okullarının tehlikelerinde Joel , Penn'deki deneyimini ve "segmentasyon hatalarının" zorluğunu tartışıyor. Diyor

[siz devam edene kadar segfaultlar zordur] “derin bir nefes alın ve zihninizi aynı anda iki farklı soyutlama seviyesinde çalışmaya zorlamaya çalışın.”

Segfaultların yaygın nedenlerinin bir listesi verildiğinde , 2 soyutlama seviyesinde nasıl çalışmamız gerektiğini anlamıyorum.

Nedense Joel bu kavramları programcıların soyutlama yeteneğinin çekirdeği olarak görüyor. Çok fazla varsaymak istemiyorum. Peki, işaretçiler / özyineleme hakkında bu kadar zor olan ne? Örnekler iyi olurdu.


31
Joel'in senin hakkında ne düşündüğü konusunda endişelenmeyi bırak. Özyinelemeyi kolay bulursanız, bu iyi. Herkes yapmaz.
FrustratedWithFormsDesigner

6
Özyineleme tanım gereği kolaydır (kendini çağıran işlev), ancak onu ne zaman kullanacağını ve nasıl çalıştıracağını bilmek zor kısımdır.
JeffO

9
Fog Creek'te bir iş başvurusu yapın ve bunun nasıl gittiğini bize bildirin. Hepiniz kendi tanıtımınızla çok ilgileniyoruz.
Joel Etherton

4
@ P.Brian.Mackey: Yanlış anlamadık. Soru gerçekten hiçbir şey sormuyor. Açıkça kendini tanıma. Joel'in işaretçiler / özyineleme hakkında ne istediğini öğrenmek istiyorsanız, ona sorun: team@stackoverflow.com
Joel Etherton

19
Bu sorunun kopyası mı?
ozz

Yanıtlar:


38

Öncelikle üniversitede işaretçilerin ve özyinelemenin zor olduğunu fark ettim. Birkaç tipik ilk yıl ders almıştı (biri C ve Assembler, diğeri Scheme idi). Her iki ders de, birçoğu yıllarca lise düzeyinde programlama deneyimi olan yüzlerce öğrenci ile başladı (o günlerde BASIC ve Pascal). Ancak C kursunda işaretçiler tanıtıldığında ve Scheme kursunda özyineleme başlatıldığında, çok sayıda öğrenci - belki de çoğunluğu - tamamen flummoxed edildi. Bunlar daha önce bir sürü LOT kodu yazmış ve hiç problem yaşamamış çocuklardı, ancak işaretçiler ve özyineleme isabet ettiklerinde, bilişsel yetenekleri açısından bir duvara da çarptılar.

Benim hipotezim, işaretçilerin ve özyinelemenin aynı olması, çünkü aynı anda kafanızda iki soyutlama seviyesi tutmanızı gerektiriyor. Bazı insanların asla sahip olamayacakları bir tür zihinsel yetenek gerektiren soyutlamanın çoklu seviyeleri hakkında bir şeyler var.

  • İşaretçilerle "iki soyutlama düzeyi", "veri, veri adresi, veri adresi vb." Veya geleneksel olarak "değer ve referans" olarak adlandırdığımız şeydir. Eğitimsiz öğrenci için, x ve x'in adresi arasındaki farkı görmek çok zordur .
  • Özyineleme ile, "iki soyutlama düzeyi" bir fonksiyonun kendisini çağırmasının nasıl mümkün olduğunu anlar. Özyinelemeli algoritma, bazen insanların "arzulu düşünerek programlama" dediği şeydir ve bir problemi çözmek için izlediğiniz adımların daha doğal "yerine" temel durum + endüktif durum "açısından bir algoritma düşünmek çok doğal değildir. ." Eğitimsiz öğrenciye özyinelemeli bir algoritmaya bakan algoritma soruya yalvarmaktadır .

Ayrıca, kimseye işaretçiler ve / veya özyineleme öğretmenin mümkün olduğunu kabul etmek için mükemmel bir şekilde istekli olurum ... Herhangi bir şekilde kanıtım yok. Deneysel olarak, bu iki kavramı gerçekten anlayabilmenin, genel programlama yeteneğinin çok ama çok iyi bir öngörücüsü olduğunu ve lisans CS eğitiminin normal seyrinde bu iki kavramın en büyük engellerden bazıları olduğunu biliyorum.


4
"" temel vaka + endüktif vaka "açısından bir algoritma düşünmek çok, çok doğal değil - Bence bu hiçbir şekilde doğal değil, sadece çocuklar buna göre eğitilmiyor.
Ingo

14
doğal olsaydı, eğitim almanıza gerek kalmazdı. : P
Joel Spolsky

1
İyi bir nokta :), ama hepsi daha geniş anlamda en doğal olan matematik, mantık, fizik vb. İlginçtir, az sayıda programcının dil sözdizimi ile ilgili herhangi bir sorunu vardır, ancak özyineleme ile doludur.
Ingo

1
Üniversitemde ilk ders, hemen hemen, mutasyon ve benzerlerini uygulamadan hemen önce fonksiyonel programlama ve özyineleme ile başladı. Ben bazı öğrenciler tespit etmeden tecrübe daha iyi olanlara göre Özyinelemeyi anlaşılan bazı tecrübe. Bununla birlikte, sınıfın en üst noktası çok fazla deneyime sahip insanlardan oluşuyordu .
Tikhon Jelvis

2
İşaretçileri ve özyinelemeyi anlamadaki yetersizliğin a) genel IQ düzeyi ve b) kötü matematik eğitimiyle bağlantılı olduğunu düşünüyorum.
quant_dev

23

Özyineleme sadece "kendini çağıran bir işlev" değildir. Özyinelemeli iniş ayrıştırıcısında neyin yanlış gittiğini anlamak için yığın kareleri çizene kadar özyinelemenin neden zor olduğunu gerçekten anlamayacaksınız. Çoğunlukla karşılıklı özyinelemeli işlevleriniz olur (A işlevi B işlevini çağırır, C işlevini çağırır, bu işlev A işlevini çağırabilir). Karşılıklı olarak yinelenen bir işlev dizisinde N yığın kareleri olduğunuzda neyin yanlış gittiğini anlamak çok zor olabilir.

Göstergelere gelince, yine işaretçiler kavramı oldukça basittir: bir bellek adresini saklayan bir değişken. Ancak yine, void**farklı düğümlere işaret eden karmaşık veri işaretçilerinizle ilgili bir şeyler ters gittiğinde, işaretçilerinizden birinin neden bir çöp adresini işaret ettiğini anlamaya çalışırken neden zorlanabileceğini göreceksiniz.


1
Özyinelemeli iyi bir ayrıştırıcı uygulamak gerçekten özyineleme üzerinde bir kavrama olduğunu hissettim oldu. İşaretçilerin söylediğiniz gibi üst düzeyde anlaşılması kolaydır; işaretçilerle ilgili uygulamaların somun ve cıvatalarına girene kadar neden karmaşık olduklarını görmezsiniz.
Chris

Birçok fonksiyon arasındaki karşılıklı özyineleme esasen aynıdır goto.
starblue

2
@starblue, gerçekten değil - çünkü her yığın çerçevesi yeni yerel değişken örnekleri oluşturur.
Charles Salvia

Haklısın, sadece kuyruk özyineleme aynıdır goto.
starblue

3
@wnoise int a() { return b(); }özyinelemeli olabilir, ancak tanımına bağlıdır b. Yani göründüğü kadar basit değil ...
alternatif

14

Java işaretçileri destekler (bunlara referans denir) ve özyinelemeyi destekler. Yani yüzeyde, argümanı anlamsız görünüyor.

Gerçekten bahsettiği şey hata ayıklama yeteneğidir. Java işaretçisinin (err, başvuru) geçerli bir nesneyi göstermesi garanti edilir. AC işaretçisi değil. Ve c programlamadaki hile, valgrind gibi araçları kullanmadığınızı varsayarsak, tam olarak bir işaretçiyi nerede vidaladığınızı bulmaktır (nadiren bir yığın izinde bulunan noktadadır).


5
Göstericiler kendi başına bir ayrıntıdır. Java'da referansları kullanmak, C'de yerel değişkenleri kullanmaktan daha karmaşık değildir. Lisp uygulamalarının yaptığı gibi karıştırmak bile (bir atom, sınırlı büyüklükte bir tamsayı veya bir karakter veya bir işaretçi olabilir) zor değildir. Dil aynı tür verilerin yerel veya referanslı olmasına, farklı sözdizimine izin verdiğinde daha zorlaşır ve dil işaretçi aritmetiğine izin verdiğinde gerçekten kıllı olur.
David Thornley

@David - um, bunun cevabımla ne ilgisi var?
Anon

1
Java destekleme işaretçileri hakkındaki yorumunuz.
David Thornley

"bir işaretçiyi vidaladığınız yer (nadiren bir yığın izinde bulunan noktadır)." Eğer bir yığın izleme almak için yeterince şanslıysanız.
Omega Centauri

5
David Thornley ile aynı fikirdeyim; Bir işaretçi için bir işaretçi bir işaretçi bir int işaretçi yapamaz sürece Java işaretçileri desteklemiyor. Belki her biri başka bir şeye atıfta bulunan 4-5 sınıf gibi yapabilirim, ama bu gerçekten işaretçiler mi yoksa çirkin bir çözüm mü?
alternatif

12

İşaretçiler ve özyineleme ile ilgili problem, anlaşılması zor olmaları değil, özellikle C veya C ++ gibi dillerle ilgili olarak kötü bir şekilde öğretilmesidir (esas olarak dillerin kendilerine kötü öğretildiği için). Ne zaman duyduğumda (veya okuduğumda) birileri "bir dizi sadece bir işaretçi" derken biraz içeride ölürüm.

Benzer şekilde, birisi Fibonacci fonksiyonunu her tekrarladığında göstermek için çığlık atmak istiyorum. Kötü bir örnek çünkü yinelemeli sürümü yazmak daha zor değil ve bu özyinelemeli birden az veya bunlardan daha iyi gerçekleştirir ve bu size bir özyinelemeli çözüm yararlı veya arzu edilir neden içine hiçbir gerçek fikir verir. Quicksort, ağaç geçişi, vb., Özyineleme neden ve nasıl için çok daha iyi örneklerdir.

İşaretçilerle uğraşmak, onları ortaya çıkaran bir programlama dilinde çalışmanın bir eseridir. Nesil Fortran programcıları, özel bir işaretçi türüne (veya dinamik bellek ayırmaya) ihtiyaç duymadan listeler, ağaçlar, yığınlar ve kuyruklar oluşturuyordu ve hiç kimsenin Fortran'ı oyuncak dili olmakla suçladığını duymadım.


Kabul edeceğim, asıl işaretçileri görmeden önce yıllarca / on yıllarca Fortran'ım vardı, bu yüzden lanquage / derleyicinin benim için yapmasına izin verme şansı verilmeden önce aynı şeyi kendi yolumu kullanıyordum. Ayrıca, bir adreste depolanan bir değer kavramı çok basit olsa da, işaretçiler / adreslerle ilgili C sözdiziminin çok kafa karıştırıcı olduğunu düşünüyorum.
Omega Centauri

Fortran IV'te uygulanan Quicksort'a bir bağlantınız varsa, bunu görmek isterim. Bunun yapılamayacağını söylemiyorum - aslında, yaklaşık 30 yıl önce BASIC'te uyguladım - ama görmek isterim.
Anon

Fortran IV'te hiç çalışmadım, ancak Fortran 77'nin VAX / VMS uygulamasında bazı özyinelemeli algoritmalar uyguladım (bir goto hedefini özel bir değişken olarak kaydetmenize izin veren bir kanca vardı, böylece yazabilirsiniz GOTO target) . Yine de kendi çalışma zamanı yığınlarımızı oluşturmak zorunda olduğumuzu düşünüyorum. Bu, detayları artık hatırlayamayacağım kadar uzun zaman önceydi.
John Bode

8

İşaretçilerle ilgili çeşitli zorluklar vardır:

  1. Örtüşme Farklı nesneler / değişkenler kullanarak bir nesnenin değerini değiştirme olasılığı.
  2. Yöresel olmama Bir nesne değerini, bildirildiği değerden farklı bir bağlamda değiştirme olasılığı (bu aynı zamanda referansla iletilen argümanlarda da olur).
  3. Ömür Boyu Uyumsuzluk Bir işaretçinin ömrü, işaret ettiği nesnenin ömründen farklı olabilir ve geçersiz referanslara (SEGFAULTS) veya çöplere yol açabilir.
  4. İşaretçi Aritmetiği . Bazı programlama dilleri, işaretçilerin tamsayı olarak değiştirilmesine izin verir ve bu, işaretçilerin herhangi bir yeri gösterebileceği anlamına gelir (bir hata olduğunda en beklenmedik yerler dahil). İşaretçi aritmetiğini doğru şekilde kullanmak için, bir programcı işaret edilen nesnelerin bellek boyutlarının farkında olmalıdır ve bu daha düşünülmesi gereken bir şeydir.
  5. Yazım Türleri Bir işaretçiyi bir türden diğerine yayınlama yeteneği, bir nesnenin belleğinin amaçlanandan farklı bir belleğinin üzerine yazılmasına olanak tanır.

Bu yüzden bir programcı işaretçiler kullanırken daha iyi düşünmelidir ( iki soyutlama seviyesi hakkında bilmiyorum ). Bu bir acemi tarafından yapılan tipik hataların bir örneğidir:

Pair* make_pair(int a, int b)
{
    Pair p;
    p.a = a;
    p.b = b;
    return &p;
}

Yukarıdaki gibi bir kodun, işaretçi kavramına sahip olmayan, ancak isimler (referanslar), nesneler ve değerlerden biri olan işlevsel programlama dilleri ve çöp toplama (Java, Python) içeren diller için mükemmel bir şekilde makul olduğunu unutmayın. .

Özyinelemeli işlevlerle ilgili zorluk, yeterli matematiksel altyapıya sahip olmayan (özyinelemenin yaygın ve gerekli bilginin olduğu yerlerde) , daha önce çağrıldığına bağlı olarak , işlevin farklı davranacağını düşünerek onlara yaklaşmaya çalıştığında ortaya çıkar . Bu sorun ağırlaşmaktadır çünkü özyinelemeli işlevler gerçekten de bu şekilde düşünmeniz gereken şekillerde oluşturulabilir onları anlamak için .

Veri yapısının yerinde değiştirildiği bir Kırmızı-Siyah Ağacın prosedürel bir uygulamasında olduğu gibi, işaretçiler geçirilen yinelemeli işlevleri düşünün ; düşünmek daha zor bir şeyişlevsel bir meslektaşından .

Soruda bahsedilmiyor, ancak acemilerin zorluk çektiği diğer önemli sorun eşzamanlılıktır .

Diğerlerinin de belirttiği gibi, bazı programlama dili yapılarında ek, kavramsal olmayan bir sorun vardır: anlasak bile, bu yapılarla ilgili basit ve dürüst hataların hatalarını ayıklamak son derece zor olabilir.


Bu işlevi kullanmak geçerli bir işaretçi döndürür, ancak değişken işlevi çağıran kapsamdan daha yüksek bir kapsamdadır;
rightfold

4
@Radek S: Hayır, olmayacak. Bazı ortamlarda başka bir şey üzerine yazana kadar bir süre çalıştığı geçersiz bir işaretçi döndürür . (Uygulamada, bu yığın olacak, yığın değil. malloc()Bunu yapmak için diğer herhangi bir işlevden daha olası değildir.)
wnoise

1
@Radeck Örnek işlevde, işaretçi, programlama dilinin (bu durumda C) garanti ettiği belleğe, işlev geri döndüğünde serbest bırakılacağını gösterir. Böylece, döndürülen işaretçi çöpü gösterir . Çöp toplama özelliğine sahip diller, herhangi bir bağlamda başvurulduğu sürece nesneyi canlı tutar.
Apalala

Bu arada, Rust'un işaretçileri var, ancak bu problemleri yok. (güvensiz bağlamda değilken)
Sarge Borsch

2

İşaretçiler ve özyineleme iki ayrı canavardır ve her birini "zor" olarak nitelendiren farklı nedenler vardır.

Genel olarak, işaretçiler saf değişken atamadan farklı bir zihinsel model gerektirir. Bir işaretçi değişkeni olduğunda, sadece şudur: başka bir nesneye bir işaretçi, içerdiği tek veri işaret ettiği bellek adresidir. Yani örneğin bir int32 işaretçim varsa ve buna doğrudan bir değer atarsam, int değerini değiştirmiyorum, yeni bir bellek adresine işaret ediyorum (bununla yapabileceğiniz çok düzgün hileler var) ). Daha da ilginç olan bir işaretçiye bir işaretçiye sahip olmaktır (bu, bir Ref değişkenini işlev olarak C # 'da geçirdiğinizde olur, işlev Parametreye tamamen farklı bir nesne atayabilir ve bu değer, işlev çıkışlar.

Özyineleme, ilk öğrenirken hafif bir zihinsel sıçrayış gerektirir çünkü bir işlevi kendi başına tanımlıyorsunuz. İlk karşılaştığınız zaman vahşi bir kavramdır, ancak fikri kavradığınızda ikinci doğa olur.

Ama eldeki konuya geri dönelim. Joel'in argümanı, kendi içinde işaretçiler veya özyineleme ile ilgili değildir, aksine öğrencilerin bilgisayarların gerçekte nasıl çalıştığından daha fazla uzaklaştırıldığı gerçeğidir. Bu Bilgisayar Biliminde Bilimdir. Programlamayı öğrenmek ile programların nasıl çalıştığını öğrenmek arasında belirgin bir fark vardır. Pek çok CS programının ticaret okulları yüceltildiğini savunarak bunun "Bu şekilde öğrendim, bu yüzden herkesin bu şekilde öğrenmesi gerekiyor" diye çok fazla bir mesele olduğunu sanmıyorum.


1

P. Brian'a +1 veriyorum, çünkü öyle hissediyorum: Özyineleme o kadar temel bir kavram ki, onunla en ufak zorlukları olan kişi mac donalds'da bir iş aramayı daha iyi düşünmeli, ama sonra bile özyineleme var:

make a burger:
   put a cold burger on the grill
   wait
   flip
   wait
   hand the fried burger over to the service personel
   unless its end of shift: make a burger

Şüphesiz, anlama eksikliği okullarımızla da ilgilidir. Burada Peano, Dedekind ve Frege gibi doğal sayılar kullanılmalı, bu yüzden daha sonra çok fazla zorluk çekmeyeceğiz.


6
Bu, kuyruk döngüsünün tartışmalı bir döngü içinde olduğu.
Michael K

6
Üzgünüm, benim için, döngü tartışmalı kuyruk özyineleme :)
Ingo

3
@Ingo: :) İşlevsel fanatik!
Michael K

1
@Michael - hehe, gerçekten !, ama bence özyineleme daha temel bir kavramdır.
Ingo

@Ingo: Gerçekten de (örneğiniz bunu iyi gösteriyor). Bununla birlikte, bir nedenden dolayı insanlar programlamada zorlanırlar - goto topbir sebepten ötürü IME'yi daha fazla istiyoruz .
Michael K

1

Joel'e, sorunun kendiliğinden çok sayıda soyutlama düşüncesinden biri olduğuna katılmıyorum, bence işaretçiler ve özyineleme, insanların programların çalışma biçiminde zihinsel modelde bir değişiklik gerektiren sorunlara iki iyi örnek.

İşaretçiler, bence, daha basit bir örnek. İşaretçilerle başa çıkmak, programların gerçekte bellek adresleri ve verilerle çalışma şeklini açıklayan zihinsel bir program yürütme modeli gerektirir. Deneyimlerim, programcıların işaretçiler hakkında bilgi edinmeden önce bunu düşünmediği sık sık oldu. Soyut bir anlamda bilseler bile, bir programın nasıl çalıştığına dair bilişsel modellerine adapte olmamışlardır. İşaretçiler tanıtıldığında, kodun nasıl çalıştığı hakkında düşünme biçiminde köklü bir değişim gerektirir.

Özyineleme sorunludur, çünkü anlaşılması gereken iki kavramsal blok vardır. Birincisi makine düzeyinde ve işaretçiler gibi, programların gerçekte nasıl saklandığını ve yürütüldüğünü iyi anlayarak geliştirilebilir. Özyineleme ile ilgili diğer bir sorun, bence, insanların özyinelemeli bir sorunu özyinelemesiz bir soruna özümsemeye çalışmak için doğal bir eğilime sahip olmalarıdır, bu da özyinelemeli bir fonksiyonun bir gestalt olarak anlaşılmasını çamurlaştırır. Bu ya matematiksel altyapısı yetersiz olan insanlarda ya da matematiksel teoriyi programların gelişimine bağlamayan zihinsel bir modeldir.

Mesele şu ki, işaretçilerin ve özyinelemenin yetersiz bir zihinsel modelde sıkışmış insanlar için sorunlu olan sadece iki alan olduğunu düşünmüyorum. Paralellik, bazı insanların takıldıkları ve zihinsel modellerini hesaba katmakta güçlük çektiği başka bir alan gibi görünüyor, sadece bir röportajda işaretçilerin ve özyinelemenin test edilmesi kolaydır.


1
  DATA    |     CODE
          |
 pointer  |   recursion    SELF REFERENTIAL
----------+---------------------------------
 objects  |   macro        SELF MODIFYING
          |
          |

Kendinden referanslı veri ve kod kavramı, sırasıyla işaretçilerin ve özyineleme tanımının temelini oluşturur. Ne yazık ki, zorunlu programlama dillerine yaygın maruz kalma, bilgisayar bilimi öğrencilerinin, bu gizemin dilin işlevsel yönüne güvenmesi gerektiğinde, çalışma zamanlarını operasyonel davranışları yoluyla uygulamayı anlamaları gerektiğine inanmalarına neden olmuştur. Yüze kadar tüm sayıların toplanması, birinden başlamak ve sıradaki bir sonrakine eklemek ve dairesel kendinden referans fonksiyonları yardımıyla geriye doğru yapmak basit bir mesele gibi görünüyor. saf fonksiyonlar.

Kendini değiştiren veri ve kod kavramı, sırasıyla nesnelerin (yani akıllı veriler) ve makroların tanımının temelini oluşturur. Bunlardan bahsetmek istiyorum, çünkü özellikle çalışma zamanının dört kavramın bir kombinasyonundan operasyonel bir anlayış beklendiğinde daha da zorlaşıyorlar - örneğin, bir işaretçi ağacı yardımıyla özyinelemeli iyi bir ayrıştırıcı uygulayan bir nesne kümesi üreten bir makro. . Zorunlu programcıların, programın durumunun tüm işleyişini adım adım her bir katman boyunca adım adım izlemek yerine, değişkenlerinin yalnızca saf işlevler içinde bir kez atandığına ve aynı saf işlevin tekrarlanan çağrılarına Java gibi saf olmayan işlevleri de destekleyen bir dilde bile, aynı argümanlar her zaman aynı sonucu verir (yani referans şeffaflığı). Çalışma süresinden sonra çevrelerde koşmak meyvesiz bir çabadır. Soyutlama basitleştirilmelidir.


-1

Anon'un cevabına çok benzer.
Yeni başlayanlar için bilişsel zorlukların yanı sıra, hem işaretçiler hem de özyineleme çok güçlüdür ve şifreli şekillerde kullanılabilir.

Büyük gücün dezavantajı, programınızı ince yollarla sıkıştırmak için size büyük güç veriyorlar.
Sahte bir değeri normal bir değişkene kaydetmek yeterince kötüdür, ancak sahte bir şeyi bir işaretçiye kaydetmek, her türlü gecikmeli felaket olayının gerçekleşmesine neden olabilir.
Ve daha da kötüsü, tuhaf program davranışının nedenini teşhis etmeye / hata ayıklamaya çalıştığınızda bu etkiler değişebilir. Ancak, bir şey incelikle yanlış yapılırsa, neler olduğunu anlamak zor olabilir.

Benzer şekilde özyineleme ile. Gizli veri yapısına (yığın) hileyi doldurarak zor şeyleri organize etmenin çok güçlü bir yolu olabilir.

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.