#include <stdio.h>
#define decode(s,t,u,m,p,e,d) m##s##u##t
#define begin decode(a,n,i,m,a,t,e)
int begin()
{
printf("Ha HA see how it is?? ");
}
Bu dolaylı olarak çağırıyor mainmu? Nasıl?
#include <stdio.h>
#define decode(s,t,u,m,p,e,d) m##s##u##t
#define begin decode(a,n,i,m,a,t,e)
int begin()
{
printf("Ha HA see how it is?? ");
}
Bu dolaylı olarak çağırıyor mainmu? Nasıl?
Yanıtlar:
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.
int begin() -> int decode(a,n,i,m,a,t,e)() -> int m##a##i##n() -> int 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.
s, t, u, m, p, e, d
1 2 3 4 5 6 7
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.
m --> m
s --> a
u --> i
t --> n
_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.
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.
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.
Kullanmayı deneyin gcc -E source.c, çıktı şununla biter:
int main()
{
printf("Ha HA see how it is?? ");
}
Yani bir main()fonksiyon aslında önişlemci tarafından üretilir.
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:
/* This must be compiled with the flag -nostdlib because otherwise the
* linker will complain about multiple definitions of the symbol _start
* (one here and one in glibc) and a missing reference to symbol main
* (that the libc expects to be linked against).
*/
void
_start ()
{
/* calling the write system call, with the arguments in this order:
* 1. the stdout file descriptor
* 2. the buffer we want to print (Here it's just a string literal).
* 3. the amount of bytes we want to write.
*/
asm ("int $0x80"::"a"(4), "b"(1), "c"("Hello world!\n"), "d"(13));
asm ("int $0x80"::"a"(1), "b"(0)); /* calling exit syscall, with the argument to be 0 */
}
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.
/* These values are extracted from the decimal representation of the instructions
* of a hello world program written in asm, that gdb provides.
*/
const int main[] = {
-443987883, 440, 113408, -1922629632,
4149, 899584, 84869120, 15544,
266023168, 1818576901, 1461743468, 1684828783,
-1017312735
};
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.
_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 ).
_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.
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.
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.
gcc -E FILENAME.c
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.
Ö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:
begin --> decode(a,n,i,m,a,t,e) --> m##parameter1##parameter3##parameter2 ---> main
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 !
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.