C ++ 'da uygun yığın ve yığın kullanımı?


122

Bir süredir programlama yapıyorum ama çoğunlukla Java ve C # idi. Asla hafızayı kendi başıma yönetmek zorunda kalmadım. Kısa süre önce C ++ ile programlamaya başladım ve bir şeyleri ne zaman yığında depolamam ve onları ne zaman yığın üzerinde depolamam gerektiği konusunda biraz kafam karıştı.

Anladığım kadarıyla, çok sık erişilen değişkenler yığın ve nesnelerde depolanmalı, nadiren kullanılan değişkenler ve büyük veri yapılarının tümü yığın üzerinde depolanmalıdır. Bu doğru mu yoksa hatalı mıyım?


Yanıtlar:


242

Hayır, yığın ve yığın arasındaki fark performanstır. Ömrü: bir işlevin içindeki herhangi bir yerel değişken (malloc () veya yeni olmayan herhangi bir şey) yığında yaşar. İşlevden döndüğünüzde kaybolur. Bir şeyin onu bildiren işlevden daha uzun yaşamasını istiyorsanız, onu yığına ayırmanız gerekir.

class Thingy;

Thingy* foo( ) 
{
  int a; // this int lives on the stack
  Thingy B; // this thingy lives on the stack and will be deleted when we return from foo
  Thingy *pointerToB = &B; // this points to an address on the stack
  Thingy *pointerToC = new Thingy(); // this makes a Thingy on the heap.
                                     // pointerToC contains its address.

  // this is safe: C lives on the heap and outlives foo().
  // Whoever you pass this to must remember to delete it!
  return pointerToC;

  // this is NOT SAFE: B lives on the stack and will be deleted when foo() returns. 
  // whoever uses this returned pointer will probably cause a crash!
  return pointerToB;
}

Yığının ne olduğunu daha net anlamak için, yığının yüksek seviyeli bir dil açısından ne yaptığını anlamaya çalışmak yerine, öbür taraftan gelin - "çağrı yığını" ve "arama düzeni" ne bakın ve ne olduğunu görün bir işlevi çağırdığınızda makine gerçekten yapar. Bilgisayar belleği sadece bir dizi adrestir; "heap" ve "stack" derleyicinin icatlarıdır.


7
Değişken boyuttaki bilgilerin genellikle yığın halinde yer aldığını eklemek güvenli olacaktır. Bildiğim tek istisna, C99'daki (sınırlı desteğe sahip olan) VLA'lar ve C programcıları tarafından bile sıklıkla yanlış anlaşılan alloca () işlevi.
Dan Olson

10
İyi açıklama, sık tahsisleri ve / veya deallocations ile bir çok iş parçacıklı senaryoda da, yığın olduğunu dolayısıyla performansı etkileyen, çekişme noktası. Yine de Kapsam neredeyse her zaman belirleyici faktördür.
peterchen

18
Elbette ve new / malloc () kendi başına yavaş bir işlemdir ve yığının, rastgele bir yığın satırından daha fazla dcache'de olma olasılığı daha yüksektir. Bunlar gerçek düşüncelerdir, ancak genellikle ömür sorusuna ikincildir.
Crashworks

1
"Bilgisayar belleği sadece bir dizi adrestir;" yığın "ve" yığın "derlemenin icatlarıdır"? Bir çok yerde yığının bilgisayarımızın belleğinin özel bir bölgesi olduğunu okudum.
Vineeth Chitteti

2
@kai Bu, onu görselleştirmenin bir yolu, ancak fiziksel olarak doğru olması gerekmez. İşletim sistemi, bir uygulamanın yığınını ve yığınını tahsis etmekten sorumludur. Derleyici de sorumludur, ancak öncelikle bunu yapmak için işletim sistemine güvenir. Yığın sınırlıdır ve yığın değildir. Bunun nedeni, işletim sisteminin bu bellek adreslerini daha yapılandırılmış bir şeye ayırma biçimidir, böylece birden çok uygulama aynı sistem üzerinde çalışabilir. Yığın ve yığın yalnızca bunlar değildir, ancak genellikle çoğu geliştiricinin endişelendiği tek ikisi bunlar.
tsturzl

42

Şöyle söylerdim:

Mümkünse, yığında saklayın.

İHTİYACINIZ VARSA, yığın üzerinde saklayın.

Bu nedenle, yığını yığına tercih edin. Yığın üzerinde bir şey saklayamamanızın bazı olası nedenleri şunlardır:

  • Çok büyük - 32 bit işletim sistemindeki çok iş parçacıklı programlarda, yığın küçük ve sabit (en azından iş parçacığı oluşturma zamanında) boyuta sahiptir (genellikle sadece birkaç mega). Bu, adresi tüketmeden çok sayıda iş parçacığı oluşturabilmeniz içindir. 64 bit programlar veya tek iş parçacıklı (yine de Linux) programlar için bu büyük bir sorun değildir. 32 bit Linux altında, tek iş parçacıklı programlar genellikle yığının tepesine ulaşana kadar büyümeye devam edebilen dinamik yığınlar kullanır.
  • Buna orijinal yığın çerçevesinin dışında erişmeniz gerekir - bu gerçekten ana nedendir.

Mantıklı derleyicilerle, öbek üzerinde sabit boyutlu olmayan nesneler (genellikle derleme zamanında boyutu bilinmeyen diziler) tahsis etmek mümkündür.


1
Birkaç KB'den fazla olan herhangi bir şey genellikle en iyisi yığına yerleştirilir. Ayrıntıları bilmiyorum ama "birkaç mega" olan bir yığınla çalıştığımı hiç hatırlamıyorum.
Dan Olson

2
Bu, başlangıçta bir kullanıcıyla ilgilenmeyeceğim bir şey. Kullanıcı için, vektörler ve listeler, bu STL içeriği yığın üzerinde depolamasa bile yığın üzerinde tahsis edilmiş gibi görünmektedir. Soru daha çok ne zaman açıkça yeni / sil çağrısı yapacağına karar verme doğrultusunda görünüyordu.
David Rodríguez - dribeas

1
Dan: 32bit linux altındaki yığına 2 konser (Evet, GIGS'deki gibi G) koydum. Yığın sınırları işletim sistemine bağlıdır.
Bay Ree

6
mrree: Nintendo DS yığını 16 kilobayttır. Bazı yığın sınırları donanıma bağlıdır.
Ant

Ant: Tüm yığınlar donanıma, işletim sistemine ve ayrıca derleyiciye bağlıdır.
Viliami

24

Diğer yanıtların önerdiğinden daha incelikli. Nasıl bildirdiğinize bağlı olarak yığın üzerindeki veriler ile yığın üzerindeki veriler arasında mutlak bir ayrım yoktur. Örneğin:

std::vector<int> v(10);

vectorYığın üzerinde on tamsayıdan oluşan bir (dinamik dizi) bildiren bir işlevin gövdesinde . Ancak tarafından yönetilen depolama birimi vectoryığın üzerinde değil.

Ah, ama (diğer cevaplar öneriyor) bu depolamanın ömrü vectorkendisinin yaşam süresiyle sınırlıdır , ki burada yığın tabanlı, bu yüzden nasıl uygulandığı fark etmez - onu yalnızca yığın tabanlı bir nesne olarak ele alabiliriz değer semantiği ile.

Öyle değil. İşlevin şöyle olduğunu varsayalım:

void GetSomeNumbers(std::vector<int> &result)
{
    std::vector<int> v(10);

    // fill v with numbers

    result.swap(v);
}

Dolayısıyla, bir swapişleve sahip herhangi bir şey (ve herhangi bir karmaşık değer türünde bir tane olmalıdır), bu verilerin tek bir sahibini garanti eden bir sistem altında bazı yığın verilere yeniden bağlanabilir bir referans olarak hizmet edebilir.

Bu nedenle, modern C ++ yaklaşımı, yığın verilerinin adresini hiçbir zaman çıplak yerel işaretçi değişkenlerinde saklamamaktır. Tüm yığın ayırmaları sınıfların içinde gizlenmelidir.

Bunu yaparsanız, programınızdaki tüm değişkenleri basit değer türleriymiş gibi düşünebilir ve yığını tamamen unutabilirsiniz (alışılmadık olması gereken bazı yığın verileri için yeni bir değer benzeri sarmalayıcı sınıfı yazmanın dışında) .

Optimize etmenize yardımcı olması için yalnızca bir özel bilgi parçasını saklamanız gerekir: mümkün olduğunda, bir değişkeni diğerine şu şekilde atamak yerine:

a = b;

onları şu şekilde değiştirin:

a.swap(b);

çünkü çok daha hızlı ve istisnalar yaratmıyor. Tek şart, baynı değeri tutmaya devam etmenize gerek olmamasıdır (bunun ayerine değerini alacaktır, bu da çöpe atılacaktır a = b).

Olumsuz yanı, bu yaklaşımın sizi gerçek dönüş değeri yerine çıktı parametreleri aracılığıyla işlevlerden değer döndürmeye zorlamasıdır. Ancak bunu C ++ 0x'de rvalue referanslarıyla düzeltiyorlar .

Hepsinden daha karmaşık durumlarda, bu fikri genel uç noktaya shared_ptrtaşır ve zaten tr1'de olduğu gibi akıllı bir işaretçi sınıfı kullanırsınız . (İhtiyacınız varsa, muhtemelen Standart C ++ 'ın tatlı uygulanabilirlik noktasının dışına çıktığınızı iddia etmeme rağmen.)


6

Ayrıca, oluşturulduğu işlevin kapsamı dışında kullanılması gerekiyorsa, öbek üzerinde bir öğe de depolarsınız. Yığın nesneleriyle kullanılan bir deyim RAII olarak adlandırılır - bu, yığın tabanlı nesnenin bir kaynak için bir sarmalayıcı olarak kullanılmasını içerir, nesne yok edildiğinde kaynak temizlenecektir. Yığın tabanlı nesneleri ne zaman istisnalar atıyor olabileceğinizi takip etmek daha kolaydır - bir istisna işleyicide yığın tabanlı bir nesneyi silmekle ilgilenmenize gerek yoktur. Bu nedenle, ham işaretçiler normalde modern C ++ 'da kullanılmaz, yığın tabanlı bir nesneye ham işaretçi için yığın tabanlı bir sarmalayıcı olabilen akıllı bir işaretçi kullanırsınız.


5

Diğer cevaplara eklemek için, en azından biraz da olsa performansla ilgili olabilir. Sizi ilgilendirmediği sürece bu konuda endişelenmenize gerek yok ama:

Yığın içinde tahsis etmek, sabit zamanlı bir işlem olmayan (ve bazı döngüler ve ek yük gerektiren) bir bellek bloğunun izlenmesini gerektirir. Bellek parçalandıkça ve / veya adres alanınızın% 100'ünü kullanmaya yaklaştıkça bu daha yavaş olabilir. Öte yandan, yığın tahsisleri sabit zamanlı, temelde "serbest" işlemlerdir.

Dikkate alınması gereken başka bir şey (yine, gerçekten yalnızca bir sorun haline gelirse önemlidir), tipik olarak yığın boyutunun sabit olması ve yığın boyutundan çok daha düşük olabilmesidir. Dolayısıyla, büyük nesneler veya çok sayıda küçük nesne ayırıyorsanız, muhtemelen yığını kullanmak istersiniz; Yığın alanınız tükenirse, çalışma zamanı siteye özel istisna atar. Genellikle büyük bir anlaşma değil, dikkate alınması gereken başka bir şey.


Hem yığın hem de yığın, sayfalı sanal bellektir. Yığın arama süresi, yeni bellekte haritalamak için gerekenlere kıyasla son derece hızlıdır. 32bit Linux altında, yığınıma> 2gig koyabilirim. Mac'ler altında, yığının 65Meg ile sınırlı olduğunu düşünüyorum.
Bay Ree

3

Yığın daha verimlidir ve kapsamlı verileri yönetmek daha kolaydır.

Ancak yığın, birkaç KB'den daha büyük herhangi bir şey için kullanılmalıdır (C ++ 'da kolaydır, yalnızca boost::scoped_ptrayrılmış belleğe bir işaretçi tutmak için yığın üzerinde bir oluşturun ).

Kendini çağırmaya devam eden yinelemeli bir algoritma düşünün. Toplam yığın kullanımını sınırlamak veya tahmin etmek çok zor! Yığın üzerinde ise, ayırıcı ( malloc()veya new) geri dönerek NULLveya dönerek bellek yetersizliğini gösterebilir throw.

Kaynak : Yığını 8KB'den büyük olmayan Linux Kernel!


Diğer okuyucuların referansı için: (A) Buradaki "gerekir" tamamen kullanıcının kişisel fikridir, çoğu kullanıcının karşılaşmayacağı en iyi ihtimalle 1 alıntı ve 1 senaryodan (özyineleme) alınmıştır. Ayrıca, (B) standart kitaplık sağlar std::unique_ptrki bu, Boost gibi herhangi bir harici kitaplığa tercih edilmelidir (ancak bu, zamanla standartları standarta besler).
underscore_d


1

Yığın üzerinde mi yoksa yığında mı tahsis edeceğiniz, değişkeninizin nasıl tahsis edildiğine bağlı olarak sizin için yapılan bir seçimdir. Bir şeyi dinamik olarak ayırırsanız, "yeni" bir çağrı kullanarak, yığıntan ayırmış olursunuz. Bir şeyi bir global değişken olarak veya bir işlevde bir parametre olarak ayırırsanız, yığın üzerinde tahsis edilir.


4
Bir şeyleri yığına ne zaman koyacağını sorduğunu sanıyorum, nasıl değil.
Steve Rowe

0

Bence iki belirleyici faktör var

1) Scope of variable
2) Performance.

Çoğu durumda yığını kullanmayı tercih ederim, ancak değişken dış kapsama erişmeniz gerekiyorsa, öbek kullanabilirsiniz.

Yığınları kullanırken performansı artırmak için, öbek bloğu oluşturmak için işlevselliği de kullanabilirsiniz ve bu, her değişkeni farklı bellek konumuna ayırmak yerine performans kazanmaya yardımcı olabilir.


0

muhtemelen bu oldukça iyi cevaplanmıştır. Düşük seviyeli ayrıntıları daha derinlemesine anlamak için sizi aşağıdaki makale dizisine yönlendirmek isterim. Alex Darby'nin bir hata ayıklayıcıyla size rehberlik ettiği bir dizi makalesi var. İşte Yığın ile ilgili Bölüm 3. http://www.altdevblogaday.com/2011/12/14/cc-low-level-curriculum-part-3-the-stack/


Bağlantı ölü gibi görünüyor, ancak İnternet Arşivi Wayback Machine'i kontrol etmek, yalnızca yığın hakkında konuştuğunu ve bu nedenle buradaki yığın ve yığınla ilgili belirli soruyu yanıtlamak için hiçbir şey yapmadığını gösterir . -1
underscore_d
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.