Bu gizlenmiş C kodu, main () olmadan çalıştığını iddia ediyor, ancak gerçekten ne yapıyor?


84

Bu dolaylı olarak çağırıyor mainmu? Nasıl?


146
Tanımlanan makrolar "ana" demeye başlar. Bu sadece bir numara. İlginç bir şey yok.
rghome

10
Araç zinciriniz, önceden işlenmiş kodu bir dosyada bırakma seçeneğine sahip olmalıdır - derlenen gerçek dosya - burada göreceksiniz, gerçekten de bir main ()

@rghome Neden cevap olarak göndermiyorsunuz? Olumlu oyların sayısı göz önüne alındığında, açıkça ilginç.
Matsemann

3
@Matsemann Vay canına! Ek oyları fark etmedim. Bunu bir yanıt olarak değiştirebilirim ve yorum ek oyları yanıt oyları olsaydı, açık farkla en iyi puanım olurdu, ancak zaten ayrıntılı bir yanıt var. Bence yorumumun amacı, gerçekten ilginç olmadığı ve bu nedenle cevabı yükseltmek istemeyen insanlar için bir alternatif olarak hareket ettiği. Yine de gösterdiğin için teşekkürler.
rghome

Çocuklar, giriş noktasını ayarlamak, dilin kendisine değil, bir işletim sistemi aracı olarak bağlayıcıya bağlıdır. Hatta kendi giriş noktamızı belirleyebilir ve aynı zamanda çalıştırılabilir bir kitaplık yapabilirsiniz! unix.stackexchange.com/a/223415/37799
Ho1

Yanıtlar:


194

C dili, yürütme ortamını iki kategoride tanımlar: bağımsız ve barındırılan . Her iki yürütme ortamında da programın başlatılması için ortam tarafından bir işlev çağrılır.
Bir de müstakil ortamı programın başlangıç fonksiyonu uygulaması sırasında tanımlanabilir barındırılan ortamı olması gerektiği main. C'deki hiçbir program, tanımlanan ortamlarda program başlatma işlevi olmadan çalışamaz.

Sizin durumunuzda, mainönişlemci tanımları tarafından gizlenmiştir. begin()genişleyecek decode(a,n,i,m,a,t,e)ayrıca genişletilecek olan main.


decode(s,t,u,m,p,e,d)7 parametreli parametreli bir makrodur. Bu makronun değiştirme listesi m##s##u##t. m, s, uve t4 olan inci , 1 st , 3 rd ve 2 nci yedek listesinde kullanılan parametresi.

Geri kalanın hiçbir faydası yoktur ( sadece şaşırtmak için ). Aktarılan decodebağımsız değişken " a , n , i , m , a, t, e" olduğundan, tanımlayıcılar m, s, uve sırasıyla tbağımsız değişkenlerle m, a, ive değiştirilir n.


11
@GrijeshChauhan tüm C derleyicileri makroları işler, C89'dan beri tüm C standartları için gereklidir.
jdarthenay

17
Bu kesinlikle yanlış. Linux'ta kullanabilirim _start(). Ya da daha düşük düzeyde, programımın başlangıcını, önyüklemeden sonra IP'nin ayarlandığı adresle hizalamayı deneyebilirim. main()C Standard kütüphanesidir . C'nin kendisi buna kısıtlama getirmez.
ljrk

1
@haccks Standart kitaplık bir giriş noktası tanımlar. Dilin kendisi umursamıyor
ljrk

3
Nasıl açıklayabilir misiniz decode(a,n,i,m,a,t,e)haline m##a##i##n? Karakterlerin yerini alıyor mu? decodeİşlevin belgelerine bir bağlantı verebilir misiniz? Teşekkürler.
AL

1
@AL İlk önce begintanımlanmış olan ile değiştirilecek decode(a,n,i,m,a,t,e)şekilde tanımlanır. Bu işlev, bağımsız değişkenleri alır s,t,u,m,p,e,dve bunları bu biçimde birleştirir m##s##u##t( ##birleştirme anlamına gelir). Yani, p, e ve d'nin değerlerini göz ardı eder. Eğer "çağrısı" olarak decodes = a, t = n, u i = ile, m = m etkili değiştirir beginile main.
ljrk

71

Kullanmayı deneyin gcc -E source.c, çıktı şununla biter:

Yani bir main()fonksiyon aslında önişlemci tarafından üretilir.


37

Söz konusu program, yaptığı çağrıyı main()nedeniyle makro genişleme, ancak varsayım kusurludur - bu değil çağrısına sahip main()hiç!

Açıkçası, bir C programınız olabilir ve onu bir mainsembol olmadan derleyebilirsiniz . kendi başlatmayı bitirdikten sonra atlamayı beklediği bir mainşeydir c library. Genellikle mainlibc sembolünden atlarsınız _start. Bir main olmadan basitçe assembly işlemini gerçekleştiren çok geçerli bir programa sahip olmak her zaman mümkündür. Şuna bir bak:

Yukarıdakileri ile derleyin ve satır içi montajda yalnızca sistem çağrıları (kesintiler) yayınlayarak ekranda gcc -nostdlib without_main.cyazdırıldığını görün Hello World!.

Bu özel sorun hakkında daha fazla bilgi için ksplice bloguna göz atın

Bir başka ilginç konu da, mainsembolün bir C fonksiyonuna karşılık gelmesine gerek kalmadan derleyen bir programa sahip olabilmenizdir. Örneğin, çok geçerli bir C programı olarak aşağıdakilere sahip olabilirsiniz, bu da derleyicinin yalnızca Uyarılar düzeyini yükselttiğinizde sızlanmasını sağlar.

Dizideki değerler, ekranda Hello World yazdırmak için gereken talimatlara karşılık gelen baytlardır. Bu özel programın nasıl çalıştığına dair daha ayrıntılı bir açıklama için, ilk olarak okuduğum bu blog gönderisine bir göz atın .

Bu programlar hakkında son bir bildirimde bulunmak istiyorum. C dili spesifikasyonuna göre geçerli C programları olarak kayıt olup olmadıklarını bilmiyorum, ancak bunları derlemek ve çalıştırmak, spesifikasyonun kendisini ihlal etseler bile kesinlikle çok mümkün.


1
_startTanımlanmış bir standardın bir kısmının adı mı yoksa bu sadece uygulamaya özel mi? Kesinlikle "ana diziniz" mimariye özgüdür. Ayrıca önemli olan, "ana dizi olarak" numaranızın güvenlik kısıtlamaları nedeniyle çalışma zamanında başarısız olması mantıksız olmayacaktır ( constniteleyiciyi kullanmasaydınız ve yine de birçok sistem buna izin verseydi bu daha olasıdır ).
mah

1
@mah: _startAMD64 psABI bir referans içerir da, ELF standart değil _startde 3.4 Proses Başlangıç . Resmi olarak ELF, yalnızca e_entryELF başlığındaki adresi bilir _start, yalnızca uygulamanın seçtiği bir addır.
ninjalj

1
@mah Ayrıca önemli, "main as an array" hilenizin güvenlik kısıtlamaları nedeniyle çalışma zamanında başarısız olması mantıksız olmayacaktır (yine de const niteleyicisini kullanmasanız ve yine de birçok sistem buna izin verirse bu daha olasıdır. o). Yalnızca son çalıştırılabilir dosya bir şekilde güvensiz bir şey olarak ayırt edilebilirse - bir ikili yürütülebilir dosya, oraya nasıl geldiğine bakılmaksızın çalıştırılabilir bir ikili dosyadır. Ve constbir bit önemli değil - bu ikili yürütülebilir dosyadaki sembol adı main. Ne fazla ne az. constyürütme sırasında hiçbir şey ifade etmeyen bir C yapısıdır.
Andrew Henle

1
@Stewart: ARMv6l'de kesinlikle başarısız oluyor (segmentasyon hatası). Ancak herhangi bir x86-64 mimarisinde çalışması gerekir.
soltaroundabout

@AndrewHenle bir ikili yürütülebilir dosya, oraya nasıl ulaşırsa ulaşsın , çalıştırılabilir bir ikili dosyadır - tam olarak doğru değil. İkili bir yürütülebilir dosya, tek bir yürütülebilir talimat bloğu değildir; bazıları talimat, bazıları salt okunur, bazıları da okuma-yazma verilerine başlatılacak veriler olan dikkatlice eşlenmiş bir bölüm bloğudur. (Bazı) güvenlik donanımı MMU'ları, bu şekilde işaretlenmemiş sayfalardan yürütmeyi engelleyebilir ve bu, örneğin yığın üzerinde kodun yürütülmesine yol açan yığın taşmalarını önlemek için iyi bir özelliktir, ancak ne yazık ki bu bazen yasaldır veya genellikle etkinleştirilmez.
mah

30

Birisi Sihirbaz gibi davranmaya çalışıyor. Bizi kandırabileceğini düşünüyor. Ama hepimiz biliyoruz ki, c programının yürütülmesi ile başlar main().

int begin()İle değiştirilir decode(a,n,i,m,a,t,e)önişlemci aşamasının bir geçiş ile. Sonra tekrar decode(a,n,i,m,a,t,e)m ## a ## i ## n ile değiştirilecektir. Makro çağrının konumsal ilişkisinde olduğu gibi, swill bir karakter değerine sahiptir a. Aynı şekilde, u"i" tile değiştirilecek ve "n" ile değiştirilecektir. Ve işte böyle m##s##u##tolacakmain

##Makro genişletmedeki sembol ile ilgili olarak, ön işleme operatörüdür ve token yapıştırma işlemini gerçekleştirir. Bir makro genişletildiğinde, her "##" operatörünün her iki tarafındaki iki simge tek bir jetonda birleştirilir ve bu daha sonra makro genişletmedeki "##" ve iki orijinal jetonun yerini alır.

Bana inanmıyorsanız, kodunuzu -Ebayrak ile derleyebilirsiniz . Ön işlemden sonra derleme işlemini durdurur ve token yapıştırmanın sonucunu görebilirsiniz.


11

decode(a,b,c,d,[...])ilk dört argümanı karıştırır ve sırayla yeni bir tanımlayıcı almak için onları birleştirir dacb. (Kalan üç argüman yok sayılır.) Örneğin decode(a,n,i,m,[...]), tanımlayıcıyı verir main. Bu nedir unutmayın beginmakro olarak tanımlanır.

Bu nedenle, beginmakro basitçe olarak tanımlanır main.


2

Örneğinizde main()işlev gerçekte mevcuttur, çünkü beginderleyicinin decodemakro ile değiştirdiği ve daha sonra m ## s ## u ## t ifadesi ile değiştirildiği bir makrodur. Makro genişleme kullanarak ##kelimesini ulaşacak maindan decode. Bu bir izdir:

Bu sadece sahip olunması gereken bir numara main(), ancak main()C programlama dilinde programın giriş işlevi için ad kullanmak gerekli değildir. İşletim sistemlerinize ve araçlarından biri olarak bağlayıcıya bağlıdır.

Windows, her zaman kullanmayın main()ama, doğrusu WinMainyawWinMain rağmen kullanabilirsiniz main()hatta Microsoft'un toolchain ile . Linux'ta kullanılabilir _start.

Giriş noktasını ayarlamak, dilin kendisine değil, bir işletim sistemi aracı olarak bağlayıcıya bağlıdır. Hatta kendi giriş noktamızı belirleyebilir ve aynı zamanda çalıştırılabilir bir kitaplık yapabilirsiniz !


@vaxquis Haklısın, ama bu, main()işlevi C programlama diline bağlayan ilk yanıtı tamamlamak / düzeltmek için yazdığım kısmi bir yanıt , ki bu doğru değil.
Ho1

@vaxquis "main () işlevinin C programlarında gerekli olmadığını" açıklamanın kısmi bir cevap olacağını varsaydım. Cevabı tamamlamak için bir paragraf ekledim. - Ho1 16 dakika önce
Ho1
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.