Stanford öğreticisi ile GCC arasındaki çelişki


82

Bu filme göre (yaklaşık 38 dakika), aynı yerel değişkenlere sahip iki fonksiyonum varsa, aynı alanı kullanacaklar. Yani aşağıdaki program yazdırılmalıdır 5. gccSonuçlarla derlemek -1218960859. neden?

Program:

talep edildiği gibi, sökücünün çıktısı:


41
"aynı alanı iyi kullanıyorlar" - bu yanlış. Olabilirler. Ya da olmayabilir. Ve bu şekilde de güvenemezsiniz.
Mat

17
Bunun bir alıştırma olarak ne faydası olduğunu merak ediyorum, eğer biri bunu üretim kodunda kullanırsa vurulur.
AndersK

12
@claptrap Belki çağrı yığınının nasıl çalıştığını öğrenmek ve bilgisayarın kaputun altında ne yaptığını anlamak için? İnsanlar bu yolu çok ciddiye alıyor.
Jonathan Reinhart

9
@claptrap Yine, bu bir öğrenme alıştırması . Montaj seviyesinde neler olup bittiğini anlarsanız, "atlamanız gereken çemberler" hepsi mantıklıdır. Ben ciddiye OP "gerçek" bir programda böyle bir şey kullanmanın herhangi niyet vardır şüphe (Eğer yaparsa, o başladı olmalıdır!)
Jonathan Reinhart

12
İki yerel değişken aynı ada sahip olduğu için örnek, şüpheli olmayanlara yanıltıcıdır; ancak bu, olup bitenle alakasız: Yalnızca değişkenlerin sayısı ve türü önemlidir. Farklı isimler tamamen aynı şekilde çalışmalıdır.
alexis

Yanıtlar:


130

Evet, evet, bu tanımlanmamış bir davranış çünkü ilklendirilmemiş 1 değişkenini kullanıyorsunuz .

Ancak, x86 mimarisine 2 , bu deney çalışması gerekir . Değer yığından "silinmez" ve içinde başlatılmadığından B(), yığın çerçevelerinin aynı olması koşuluyla aynı değer hala orada olmalıdır.

Beri, tahmin girişim ediyorum int adeğildir kullanılan iç bölgesinin void B(), bu kod dışarı optimize derleyici ve 5 yığın bu konuma yazılı değildi. Bir printfgiriş eklemeyi B()de deneyin - işe yarayabilir.

Ayrıca, derleyici işaretleri - yani optimizasyon seviyesi - bu deneyi de etkileyecektir. -O0Gcc'ye geçerek optimizasyonları devre dışı bırakmayı deneyin .

Düzenleme: Kodunuzu gcc -O0(64-bit) ile derledim ve aslında, program çağrı yığınına aşina birinin bekleyeceği gibi 5 yazdırır. Aslında, onsuz bile çalıştı -O0. 32 bitlik yapı farklı davranabilir.

Sorumluluk reddi: Asla, asla "gerçek" kodda böyle bir şey kullanmayın!

1 - Aşağıda bunun resmi olarak "UB" olup olmadığı veya sadece tahmin edilemez olup olmadığı hakkında bir tartışma var .

2 - Ayrıca x64 ve muhtemelen bir çağrı yığını kullanan diğer tüm mimariler (en azından bir MMU'ya sahip olanlar)


Bunun neden işe yaramadığına bir bakalım . Bu en iyi 32 bit'te görülür, bu yüzden -m32.

$ gcc --version
gcc (GCC) 4.7.2 20120921 (Red Hat 4.7.2-2)

$ gcc -m32 -O0 test.c(Optimizasyonlar devre dışı bırakıldı) ile derledim . Bunu çalıştırdığımda, çöp yazdırıyor.

Bakmak $ objdump -Mintel -d ./a.out:

080483ec <A>:
 80483ec:   55                      push   ebp
 80483ed:   89 e5                   mov    ebp,esp
 80483ef:   83 ec 28                sub    esp,0x28
 80483f2:   8b 45 f4                mov    eax,DWORD PTR [ebp-0xc]
 80483f5:   89 44 24 04             mov    DWORD PTR [esp+0x4],eax
 80483f9:   c7 04 24 c4 84 04 08    mov    DWORD PTR [esp],0x80484c4
 8048400:   e8 cb fe ff ff          call   80482d0 <printf@plt>
 8048405:   c9                      leave  
 8048406:   c3                      ret    

08048407 <B>:
 8048407:   55                      push   ebp
 8048408:   89 e5                   mov    ebp,esp
 804840a:   83 ec 10                sub    esp,0x10
 804840d:   c7 45 fc 05 00 00 00    mov    DWORD PTR [ebp-0x4],0x5
 8048414:   c9                      leave  
 8048415:   c3                      ret    

BDerleyicinin 0x10 baytlık yığın alanı ayırdığını ve int adeğişkenimizi [ebp-0x4]5 olarak başlattığını görüyoruz .

Olarak A, ancak derleyici yerleştirilmiş int aen [ebp-0xc]. Yani bu durumda yerel değişkenlerimiz aynı yerde olmadı ! Bir ekleyerek printf()aramayı Ade için yığın çerçeveleri neden olarak Ave Baynı olması ve baskı 55.


5
Bir kez çalışsa bile, bazı mimarilerde güvenilir olmayacaktır - herhangi bir zamanda yığın işaretçisinin altındaki her şeyi patlatmak için bir kesme başlangıcı.
Martin James

4
@JonathonReinhart - bir örnek, bellek yönetim biriminin olmadığı, kullanıcı / lernel modu dağılımının olmadığı ve dolayısıyla kesinti sırasında farklı bir yığına donanım geçişinin olmadığı durumlardır. Çekirdek kesinti yığınına geçiş yapılmadan önce, bazı verilerin kesintiye uğramış görev yığınına aktarılması gereken başkaları da vardır.
Martin James

6
"Tanımlanmamış davranıştan" bahsetmeyen bir yanıt için çok fazla oy var. Üstelik o da kabul ediliyor.
BЈовић

25
Ayrıca, soruyu gerçekten cevapladığı için kabul edildi .
slebetman

8
@ BЈовић Videodan herhangi birini izlediniz mi? Bakın, herkes ve kardeşleri bunu gerçek kodda yapmamanız gerektiğini biliyor ve bu tanımlanmamış davranışlara neden oluyor . Konu o değil. Buradaki önemli nokta, bilgisayarın iyi tanımlanmış, öngörülebilir bir makine olmasıdır. Bir x86 kutusunda (ve muhtemelen diğer mimarilerin çoğunda), mantıklı bir derleyici ve potansiyel olarak biraz kod / bayrak masajı ile, bu beklendiği gibi çalışacaktır . Videoyla birlikte bu kod, sadece bir olan gösteri çağrı yığını nasıl çalıştığının. Seni bu kadar rahatsız ediyorsa, başka yere gitmeni öneririm. Bazılarımız meraklı türleri bir şeyleri anlamaktan hoşlanır.
Jonathan Reinhart

36

Bu var tanımsız davranış . İlklendirilmemiş bir yerel değişkenin belirsiz bir değeri vardır ve onu kullanmak tanımsız davranışa yol açar.


6
Daha kesin olmak gerekirse, adresi hiçbir zaman alınmayan birimleştirilmiş bir değişken kullanmak tanımsız bir davranıştır.
Jens Gustedt

@JensGustedt Güzel yorum. Blog.frama-c.com/index.php?post/2013/03/13/… adresindeki “Sonraki örnek” bölümü hakkında söyleyeceğiniz herhangi bir şey var mı?
Pascal Cuoq

@ PascalCuoq, bu standartlar komitesinde devam eden bir tartışma gibi görünüyor. Bir işaretçiden geçirdiğiniz belleği incelemenin, başlatılıp başlatılmadığını bilemeseniz bile mantıklı olduğu durumlar vardır. Her durumda basitçe tanımsız hale getirmek çok kısıtlayıcıdır.
Jens Gustedt

@JensGustedt: Adresini almak, onu kullanmanın tanımlanmış davranışa sahip olmasına nasıl neden oluyor: { int uninit; &uninit; printf("%d\n", uninit); }hala tanımsız davranışa sahip. Öte yandan, herhangi bir nesneyi bir dizi olarak ele alabilirsiniz unsigned char; aklında olan bu muydu?
Keith Thompson

@KeithThompson, hayır tam tersi. Adresi hiçbir zaman alınmayacak ve ilklendirilmemiş bir değişkene sahip olmak UB'ye yol açar. Belirsiz bir değeri kendi başına okumak tanımsız bir davranış değildir, içeriği tahmin edilemez. 6.3.2.1'den p2: Eğer
Jens Gustedt

12

Bir önemli şey hatırlamak - yok hiç böyle bir şeye güvenmek ve asla gerçek kodda bunu kullanın! Bu sadece ilginç bir şeydir (ki bu her zaman doğru değildir), bir özellik ya da onun gibi bir şey değil. Kendinizi bu tür bir "özellik" - kabus tarafından üretilen bir hatayı bulmaya çalıştığınızı hayal edin.

Btw. - C ve C ++ bu tür "özelliklerle" doludur, işte BÜYÜK slayt gösterisi: http://www.slideshare.net/olvemaudal/deep-c Yani daha benzer "özellikler" görmek istiyorsanız, ne olduğunu anlayın Kaputun altında ve nasıl çalıştığını sadece bu slayt gösterisini izleyin - pişman olmayacaksınız ve eminim ki deneyimli c / c ++ programcılarının çoğu bile bundan çok şey öğrenebilir.


7

Fonksiyonda A, değişken abaşlatılmaz, değerinin yazdırılması tanımsız davranışa yol açar.

Bazı derleyici olarak, değişken aiçinde Ave ade Bo yazdırabilirsiniz böylece, aynı adrese içindedir 5, ama yine sen tanımsız davranış güvenemez.


1
Eğitim% 100 doğrudur, ancak orijinal poster s machine will be the same depends on the assembly generated by the compiler. As @JonathonReinhart pointed out the call to B () `' deki sonuçların optimize edilmiş olup olmadığı .
Lloyd Crawley

1
"Bu öğretici yanlış" laf kalabalığı ile ilgili bir sorunum var. Gerçekten öğreticiyi izlemeye gittin mi? Size böyle çılgınca bir şeyi nasıl yapacağınızı öğretmeye değil, çağrı yığınının nasıl çalıştığını göstermeye çalışıyor. Bu durumda eğitim tamamen doğrudur.
Jonathan Reinhart

@JonathonReinhart Öğreticiyi izlemedim, bu örneğin öğreticiden olduğunu düşündüm, bu kısmı kaldıracağım.
Yu Hao

@LloydCrawley Eğitimle ilgili kısmı kaldırdım. Bunun yığın mimarisi ile ilgili olduğunu biliyorum, basıldığında aynı adreste olduklarını kastetmiştim 5, ama görünüşe göre Jonathon Reinhart'ın çok daha iyi bir açıklaması var.
Yu Hao

7

Kodunuzu ile derleyin gcc -Wall filename.cBu uyarıları göreceksiniz.

C Başlatılmamış değişkeni yazdırma Tanımsız davranışa yol açar.

Bölüm 6.7.8 C99 standardının başlatılması diyor ki

Otomatik depolama süresi olan bir nesne açıkça başlatılmazsa, değeri belirsizdir. Statik depolama süresine sahip bir nesne açıkça başlatılmadıysa, o zaman:

Düzenle1

@Jonathon Reinhart olarak -OBayrak kullanarak optimizasyonu devre dışı bırakırsanız gcc-O0 çıktı 5 alabilirsiniz.

Ancak bu hiç de iyi bir fikir değil, bunu asla üretim kodunda kullanmayın.

-Wuninitialized bu değerli uyarılardan biridir Bunu göz önünde bulundurmalısınız Arka plan programlarını çalıştırırken çökmelere neden olmak gibi üretimde büyük hasara neden olan bu uyarıyı devre dışı bırakmamalı veya atlamamalısınız.


Düzenle2

Derin C slaytları, sonuç neden 5 / çöp olduğunu açıkladı.

Durum 1: optimizasyon olmadan

Belki de bu derleyicinin yeniden kullandığı bir adlandırılmış değişken havuzu vardır. Örneğin a değişkeni kullanıldı ve yayınlandı B(), daha sonra A()bir tamsayı ismine ihtiyaç duyduğunda adeğişken aynı bellek konumunu alacak. İçinde değişkeni yeniden adlandırırsanız B(), diyelim ki b, o zaman alacak sanmıyorum 5.

Durum 2: optimizasyonla

Optimizer devreye girdiğinde birçok şey olabilir. Bu durumda B(), herhangi bir yan etkisi olmadığı için çağrının atlanabileceğini tahmin ediyorum . Ayrıca, A()satır içi olarak main(), yani işlev çağrısı yoksa şaşırmam . (Ancak A ()bağlayıcı görünürlüğü olduğundan, işlevin nesne kodu, başka bir nesne dosyasının işlevle bağlantı kurmak istemesi durumunda yine de oluşturulmalıdır). Her neyse, kodu optimize ederseniz yazdırılan değerin başka bir şey olacağından şüpheleniyorum.

Çöp!


1
Düzenleme 2, Durum 1'deki mantığınız tamamen yanlış. Bu hiç de böyle çalışmıyor. Yerel değişkenin adı kesinlikle hiçbir şey ifade etmiyor.
Jonathon Reinhart

@JonathonReinhart Cevapta da belirttiğim gibi bunu deepc slaytlarından ekledim, lütfen hangi temelde yanlış olduğunu açıklayın.
Gangadhar

3
Yığın alanı ve değişken isimleri arasında herhangi bir ilişki yoktur. Örnek, kavramsal olarak ikinci işlev çağrısındaki yığın çerçevesinin, ikinci işlev çağrısının yığın çerçevesini basitçe kaplayacağı gerçeğine dayanır. İsimlerin ne olduğu önemli değil, her iki yöntem imzası da aynı olduğu sürece aynı şey olabilir. Diğerlerinin de belirttiği gibi, eğer gömülü bir sistemde olsaydı ve A () ve B () çağrıları arasında bir donanım kesintisi servis edilmiş olsaydı, yığın rastgele değerler içerecekti. Code Guard for Borland gibi eski araçlar, her aramadan önce sıfır yazılmasına izin veriyordu.
Dan Haynes

@DanHaynes Yorumunuz beni ikna ediyor. İkinci işlev çağrısındakistack çerçevesi, değişken türü ve işlev prototipi kadar İlk işlev çağrısının yığın çerçevesini kaplayabilir.Evet, değişken adlarıyla bir ilgisi olmadığı konusunda da aynı fikirdeyim.
Gangadhar
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.