Kullanılmayan bir üye değişkeni hafızayı alır mı?


92

Bir üye değişkeni başlatmak ve onu referans göstermemek / kullanmamak, çalışma zamanı sırasında RAM alır mı, yoksa derleyici bu değişkeni yok sayar mı?

struct Foo {
    int var1;
    int var2;

    Foo() { var1 = 5; std::cout << var1; }
};

Yukarıdaki örnekte, 'var1' üyesi daha sonra konsolda görüntülenen bir değer alır. Ancak 'Var2' hiç kullanılmamaktadır. Bu nedenle, çalışma zamanı sırasında belleğe yazmak kaynak israfı olur. Derleyici bu tür durumları bir hesaba katıp kullanılmayan değişkenleri yok sayıyor mu, yoksa Foo nesnesi, üyelerinin kullanılıp kullanılmadığına bakılmaksızın her zaman aynı boyutta mı?


25
Bu, derleyiciye, mimariye, işletim sistemine ve kullanılan optimizasyona bağlıdır.
Baykuş

16
Donanım veri çerçevesi boyutlarını eşleştirmek için dolgu için ve istenen bellek hizalamasını elde etmek için bir hack olarak özel olarak hiçbir şey yapmayan yapı üyeleri ekleyen bir metrik ton düşük seviye sürücü kodu var. Bir derleyici bunları optimize etmeye başlarsa, çok fazla kırılma olur.
Andy Brown

2
@Andy, aşağıdaki veri üyelerinin adresi değerlendirildiği için gerçekten hiçbir şey yapmıyorlar. Bu, bu dolgu üyelerinin varlığının programda gözlemlenebilir bir davranışa sahip olduğu anlamına gelir. Burada var2değil.
YSC

4
Böyle bir yapıya hitap eden herhangi bir derleme biriminin aynı yapıyı kullanan başka bir derleme birimine bağlanabileceği ve derleyicinin ayrı derleme biriminin üyeye hitap edip etmediğini bilemeyeceği göz önüne alındığında, derleyici bunu optimize edebilirse şaşırırdım.
Galik

2
@geza sizeof(Foo)tanım gereği azalmaz - yazdırırsanız sizeof(Foo)verimi gerekir 8(ortak platformlarda). Derleyiciler , LTO veya tüm program optimizasyonu olmasa bile makul buldukları herhangi bir bağlamda (yığın var2üzerinden newveya yığın üzerinde veya işlev çağrılarında ...) tarafından kullanılan alanı optimize edebilir . Bunun mümkün olmadığı durumlarda, diğer optimizasyonlarda olduğu gibi bunu yapmazlar. Kabul edilen yanıtta yapılan düzenlemenin, yanıt tarafından yanlış yönlendirilme olasılığını önemli ölçüde azalttığına inanıyorum.
Max Langhof

Yanıtlar:


107

"As-eğer" kuralı altın C ++ 1 , devletler eğer gözlemlenebilir davranış bir programın kullanılmayan veri üyeli varlığına bağlı değildir uzağa optimize etmek, derleyici izin verilir .

Kullanılmayan bir üye değişkeni hafızayı alır mı?

Hayır (eğer "gerçekten" kullanılmamışsa).


Şimdi akılda iki soru var:

  1. Gözlemlenebilir davranış ne zaman bir üyenin varlığına bağlı olmaz?
  2. Gerçek hayat programlarında bu tür durumlar oluyor mu?

Bir örnekle başlayalım.

Misal

#include <iostream>

struct Foo1
{ int var1 = 5;           Foo1() { std::cout << var1; } };

struct Foo2
{ int var1 = 5; int var2; Foo2() { std::cout << var1; } };

void f1() { (void) Foo1{}; }
void f2() { (void) Foo2{}; }

Gcc'den bu çeviri birimini derlemesini istersek , çıktı:

f1():
        mov     esi, 5
        mov     edi, OFFSET FLAT:_ZSt4cout
        jmp     std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
f2():
        jmp     f1()

f2ile aynıdır f1ve gerçeği tutmak için hiçbir bellek kullanılmaz Foo2::var2. ( Clang benzer bir şey yapar ).

Tartışma

Bazıları bunun iki nedenden dolayı farklı olduğunu söyleyebilir:

  1. bu çok önemsiz bir örnek
  2. yapı tamamen optimize edilmiştir, sayılmaz.

İyi bir program, karmaşık şeylerin basit bir şekilde yan yana dizilmesinden ziyade basit şeylerin akıllı ve karmaşık bir birleşimidir. Gerçek hayatta, derleyicinin optimize ettiğinden daha basit yapıları kullanarak tonlarca basit işlev yazarsınız. Örneğin:

bool insert(std::set<int>& set, int value)
{
    return set.insert(value).second;
}

Bu, bir veri üyesinin (burada std::pair<std::set<int>::iterator, bool>::first) kullanılmamış olmasının gerçek bir örneğidir . Bil bakalım ne oldu? Optimize edilmiştir ( bu montaj sizi ağlatırsa bir kukla set ile daha basit bir örnek ).

Şimdi , Max Langhof'un mükemmel cevabını okumak için mükemmel bir zaman (lütfen benim için oy verin). Sonuçta yapı kavramının derleyicinin çıkardığı montaj düzeyinde neden anlamsız olduğunu açıklar.

"Ancak, X yaparsam, kullanılmayan üyenin optimize edilmiş olması bir sorun olur!"

Bu cevabın yanlış olması gerektiğini savunan çok sayıda yorum yapılmıştır çünkü bazı işlemler (benzeri assert(sizeof(Foo2) == 2*sizeof(int))) bir şeyi bozabilir.

X, program 2'nin gözlemlenebilir davranışının bir parçasıysa , derleyicinin şeyleri optimize etmesine izin verilmez. "Kullanılmayan" bir veri üyesi içeren bir nesne üzerinde, program üzerinde gözlemlenebilir bir etkiye sahip olan birçok işlem vardır. Böyle bir işlem gerçekleştirilirse veya derleyici hiçbirinin gerçekleştirilmediğini kanıtlayamazsa, bu "kullanılmayan" veri üyesi, programın gözlemlenebilir davranışının bir parçasıdır ve optimize edilemez .

Gözlemlenebilir davranışı etkileyen işlemler aşağıdakileri içerir, ancak bunlarla sınırlı değildir:

  • bir tür nesnenin boyutunu almak ( sizeof(Foo)),
  • "Kullanılmamış" olandan sonra beyan edilen veri üyesinin adresini almak,
  • gibi bir işlev ile bir nesne kopyalama memcpy,
  • nesnenin temsilini değiştirmek (ile olduğu gibi memcmp),
  • bir nesneyi uçucu olarak nitelendirmek ,
  • vb .

1)

[intro.abstract]/1

Bu belgedeki anlamsal açıklamalar, parametreleştirilmiş, kesin olmayan bir soyut makineyi tanımlar. Bu belge, uyumlu uygulamaların yapısına herhangi bir gereklilik getirmemektedir. Özellikle, soyut makinenin yapısını kopyalamaları veya taklit etmeleri gerekmez. Daha ziyade, uyumlu uygulamaların aşağıda açıklandığı gibi soyut makinenin gözlemlenebilir davranışını taklit etmesi (sadece) gereklidir.

2) Bir iddianın geçmesi veya kalması gibi.


Yanıtla ilgili iyileştirmeler öneren yorumlar sohbette arşivlendi .
Cody Grey

1
Hatta assert(sizeof(…)…)aslında sınırlamak değil derleyici-it sağlamak zorunda olan bir sizeofbenzeri şeyleri kullanarak kod izin verdiğini memcpyişe, ama bu böyle a maruz kalabileceği sürece derleyici nasılsa baytlarca kullanmak için gereklidir anlamına gelmez memcpybu can 't yeniden yazma zaten doğru değeri elde edilir.
Davis Herring

@Davis Kesinlikle.
YSC

64

Derleyicinin ürettiği kodun veri yapılarınız hakkında gerçek bir bilgisi olmadığını (çünkü montaj düzeyinde böyle bir şey yoktur) ve optimize edicinin de olmadığını anlamak önemlidir. Derleyici , veri yapıları değil, yalnızca her işlev için kod üretir .

Tamam, aynı zamanda sabit veri bölümleri yazıyor.

Buna dayanarak, optimize edicinin üyeleri "kaldırmayacağını" veya "ortadan kaldırmayacağını" söyleyebiliriz, çünkü veri yapılarını çıktı vermez. Bu çıkarır kodu veya olmayabilir, kullanmak üyeden ve kendi hedefler arasında anlamsız ortadan kaldırarak bellek veya döngüleri kaydediyor kullanımlarını (yani yazıyor / okur) üyelerinin.


İşin özü şudur: "Eğer derleyici bir işlev kapsamında ( içine yerleştirilen işlevler dahil) kullanılmayan üyenin işlevin nasıl çalıştığı (ve ne döndürdüğü) konusunda hiçbir fark yaratmadığını kanıtlayabilirse, o zaman şudur: üyenin varlığı ek yüke neden olmaz ".

Bir işlevin dış dünya ile etkileşimlerini derleyici için daha karmaşık / belirsiz hale getirirken (daha karmaşık veri yapılarını al / döndür, örneğin a std::vector<Foo>, farklı bir derleme biriminde bir işlevin tanımını gizleme, satır içi yazmayı yasakla / engelleme vb.) , derleyicinin kullanılmayan üyenin hiçbir etkisinin olmadığını kanıtlayamaması gittikçe daha muhtemel hale geliyor.

Burada katı kurallar yoktur çünkü her şey derleyicinin yaptığı optimizasyonlara bağlıdır, ancak önemsiz şeyler yaptığınız sürece (YSC'nin cevabında gösterildiği gibi), karmaşık şeyler yaparken (örneğin geri dönmek bir std::vector<Foo>satır içine alma için çok büyük bir işlevinden) muhtemelen yükü tabi olacaktır.


Bu noktayı açıklamak için şu örneği düşünün :

struct Foo {
    int var1 = 3;
    int var2 = 4;
    int var3 = 5;
};

int test()
{
    Foo foo;
    std::array<char, sizeof(Foo)> arr;
    std::memcpy(&arr, &foo, sizeof(Foo));
    return arr[0] + arr[4];
}

Burada önemsiz olmayan şeyler yapıyoruz (adresleri alıyoruz, bayt gösteriminden baytları inceliyor ve ekliyoruz ) ve yine de optimizer sonucun bu platformda her zaman aynı olduğunu anlayabilir:

test(): # @test()
  mov eax, 7
  ret

Sadece üyeleri Foohiçbir hafızayı işgal Fooetmedi, hatta var olmadı! Optimize edilemeyen başka kullanımlar varsa, o zaman sizeof(Foo)önemli olabilir - ancak yalnızca bu kod segmenti için! Tüm kullanımlar bu şekilde optimize edilebilirse, o zaman örneğin varlığı var3üretilen kodu etkilemez. Ancak başka bir yerde kullanılsa bile test()optimize edilmiş olarak kalır!

Kısaca: Her kullanımı Foobağımsız olarak optimize edilir. Bazıları gereksiz bir üye yüzünden daha fazla hafıza kullanabilir, bazıları kullanmayabilir. Daha fazla ayrıntı için derleyici kılavuzunuza bakın.


6
Mic drop "Daha fazla ayrıntı için derleyici kılavuzunuza bakın." : D
YSC

22

Derleyici, yalnızca, değişkeni kaldırmanın hiçbir yan etkisi olmadığını ve programın hiçbir bölümünün Fooaynı boyuta bağlı olmadığını kanıtlayabiliyorsa, kullanılmayan bir üye değişkeni (özellikle genel bir değişkeni) optimize edecektir .

Yapı gerçekten kullanılmadığı sürece mevcut derleyicilerin bu tür optimizasyonları gerçekleştirdiğini düşünmüyorum. Bazı derleyiciler en azından kullanılmayan özel değişkenler hakkında uyarabilir, ancak genellikle genel olanlar için değil.


1
Ve yine de yapar: godbolt.org/z/UJKguS + hiçbir derleyici kullanılmayan bir veri üyesi için uyarmaz .
YSC

@YSC clang ++, kullanılmayan veri üyeleri ve değişkenler hakkında uyarıda bulunur.
Maxim Egorushkin

3
@YSC Bence bu biraz farklı bir durum, yapıyı tamamen optimize etti ve sadece 5'i doğrudan yazdırdı
Alan Birtles

4
@AlanBirtles Nasıl farklı olduğunu anlamıyorum. Derleyici, programın gözlemlenebilir davranışı üzerinde hiçbir etkisi olmayan nesneden gelen her şeyi optimize etti. Yani ilk cümleniz "derleyicinin kullanılmayan bir üye değişkeni optimize etme olasılığı çok düşüktür" yanlış.
YSC

2
@YSC, yapının sadece yan etkileri için inşa edilmek yerine aslında kullanıldığı gerçek kodda muhtemelen daha düşük bir olasılıkla optimize edilmeyecektir
Alan Birtles

7

Genel olarak, istediğinizi aldığınızı varsaymanız gerekir, örneğin, "kullanılmayan" üye değişkenleri vardır.

Örneğinizde her iki üye de publicolduğu için, derleyici bazı kodların (özellikle diğer çeviri birimlerinden = diğer * .cpp dosyalarından, ayrı olarak derlenen ve sonra bağlanan) "kullanılmayan" üyeye erişip erişmeyeceğini bilemez.

YSC'nin cevabı , sınıf türünün yalnızca otomatik depolama süresinin bir değişkeni olarak kullanıldığı ve bu değişkene hiçbir göstericinin alınmadığı çok basit bir örnek verir. Orada, derleyici tüm kodu satır içi yapabilir ve ardından tüm ölü kodu ortadan kaldırabilir.

Farklı çeviri birimlerinde tanımlanan işlevler arasında arabirimleriniz varsa, genellikle derleyici hiçbir şey bilmez. Arayüzler tipik olarak önceden tanımlanmış bazı ABI'leri (bunun gibi ) takip eder, böylece farklı nesne dosyaları sorunsuz bir şekilde birbirine bağlanabilir. Tipik olarak ABI'ler, bir üyenin kullanılıp kullanılmadığı konusunda bir fark yaratmaz. Bu nedenle, bu gibi durumlarda, ikinci üyenin fiziksel olarak hafızada olması gerekir (daha sonra bağlayıcı tarafından ortadan kaldırılmadıkça).

Ve dilin sınırları içinde olduğunuz sürece, herhangi bir eleme olduğunu gözlemleyemezsiniz. Eğer sizeof(Foo)ararsan alacaksın 2*sizeof(int). Bir dizi dizisi oluşturursanız Foo, ardışık iki nesnenin başlangıcı arasındaki mesafe Fooher zaman sizeof(Foo)bayttır.

Türünüz standart bir düzen tipidir , yani derleme zamanı hesaplanan ofsetlere ( offsetofmakro ile karşılaştırınız) göre üyelere de erişebilirsiniz . Ayrıca, bir dizi kopyalama ile nesnenin bayt-bayt temsil incelenebilmektedir charkullanılarak std::memcpy. Tüm bu durumlarda, ikinci üyenin orada olduğu gözlemlenebilir.


Yorumlar uzun tartışmalar için değildir; bu konuşma sohbete taşındı .
Cody Grey

2
+1: yalnızca agresif tüm program optimizasyonu, yerel bir yapı nesnesinin tamamen optimize edilmediği durumlar için veri düzenini (derleme zamanı boyutları ve ofsetler dahil) muhtemelen ayarlayabilir. gcc -fwhole-program -O3 *.cteorik olarak yapabilirdi, ancak pratikte muhtemelen yapmayacak. (örneğin, programın sizeof()bu hedefte tam olarak ne değere sahip olduğu konusunda bazı varsayımlar yapması ve programcıların isterlerse elle yapması gereken gerçekten karmaşık bir optimizasyon olması durumunda.)
Peter Cordes

6

Bu soruya verilen diğer cevaplarla sağlanan örnekler var2tek bir optimizasyon tekniğine dayanmaktadır: sürekli yayılma ve tüm yapının daha sonra seçilmesi (adaletin seçilmesi değil var2). Bu basit bir durumdur ve optimize eden derleyiciler bunu uygular.

Yönetilmeyen C / C ++ kodları için cevap, derleyicinin genel olarak göz ardı edilmeyeceğidir var2. Bildiğim kadarıyla, hata ayıklama bilgilerinde böyle bir C / C ++ yapı dönüşümü için destek yoktur ve yapı bir hata ayıklayıcıda bir değişken olarak erişilebilirse var2, bu durumda elenemez. Bildiğim kadarıyla, hiçbir geçerli C / C ++ derleyicisi işlevleri seçimine göre özelleştiremez var2, bu nedenle yapı satır içi olmayan bir işleve geçirilir veya var2bu işlevden döndürülürse bu durumda elenemez.

JIT derleyicili C # / Java gibi yönetilen diller için, derleyici güvenli bir şekilde eleyebilir, var2çünkü kullanılıp kullanılmadığını ve yönetilmeyen koda kaçıp kaçmadığını tam olarak izleyebilir. Yönetilen dillerdeki yapının fiziksel boyutu, programcıya bildirilen boyutundan farklı olabilir.

Yıl 2019 C / C ++ derleyicileri var2, tüm yapı değişkeni atlanmadığı sürece yapıdan çıkarılamaz. Yapıdan çıkarılmasıyla var2ilgili ilginç durumlar için cevap: Hayır.

Gelecekteki bazı C / C ++ derleyicileri yapıdan var2çıkarılabilecek ve derleyiciler etrafında inşa edilen ekosistemin, derleyiciler tarafından oluşturulan işlem seçimleri bilgisine adapte olması gerekecektir.


1
Hata ayıklama bilgileriyle ilgili paragrafınız "hata ayıklamayı zorlaştıracaksa onu optimize edemeyiz" şeklinde özetlenebilir ki bu tamamen yanlıştır. Ya da yanlış okuyorum. Açıklayabilir misin?
Max Langhof

Derleyici yapı hakkında hata ayıklama bilgisi yayınlarsa, var2'yi eleyemez. Seçenekler şunlardır: (1)
Yapının

Belki daha genel olarak, Agregaların Skaler Değişimi (ve daha sonra ölü depoların seçilmesi, vb. )
Davis Herring

4

Derleyicinize ve optimizasyon seviyesine bağlıdır.

Gcc'de, belirtirseniz -O, aşağıdaki optimizasyon bayraklarını açacaktır :

-fauto-inc-dec 
-fbranch-count-reg 
-fcombine-stack-adjustments 
-fcompare-elim 
-fcprop-registers 
-fdce
-fdefer-pop
...

-fdceÖlü Kod Eliminasyonu anlamına gelir .

__attribute__((used))Gcc'nin kullanılmayan bir değişkeni statik depolamayla ortadan kaldırmasını önlemek için kullanabilirsiniz :

Statik depolamaya sahip bir değişkene eklenen bu öznitelik, değişkene başvurulmamış gibi görünse bile değişkenin yayınlanması gerektiği anlamına gelir.

Bir C ++ sınıf şablonunun statik veri üyesine uygulandığında, öznitelik aynı zamanda sınıfın kendisi başlatılırsa üyenin başlatıldığı anlamına gelir.


Bu, statik veri üyeleri içindir, örnek başına kullanılmayan üyeler için değil (tüm nesne yapmadıkça optimize edilmez). Ama evet, sanırım bu önemli. BTW, kullanılmayan statik değişkenleri ortadan kaldırmak, GCC terimi esnetmediği sürece ölü kod eliminasyonu değildir .
Peter Cordes
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.