#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 main
mu? 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 main
mu? 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, u
ve t
4 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 decode
bağımsız değişken " a , n , i , m , a, t, e" olduğundan, tanımlayıcılar m, s, u
ve sırasıyla t
bağımsız değişkenlerle m, a, i
ve 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.
begin
tanı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,d
ve 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 decode
s = a, t = n, u i = ile, m = m etkili değiştirir begin
ile 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 main
sembol olmadan derleyebilirsiniz . kendi başlatmayı bitirdikten sonra atlamayı beklediği bir main
şeydir c library
. Genellikle main
libc 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.c
yazdı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, main
sembolü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.
_start
Tanı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 ( const
niteleyiciyi kullanmasaydınız ve yine de birçok sistem buna izin verseydi bu daha olasıdır ).
_start
AMD64 psABI bir referans içerir da, ELF standart değil _start
de 3.4 Proses Başlangıç . Resmi olarak ELF, yalnızca e_entry
ELF başlığındaki adresi bilir _start
, yalnızca uygulamanın seçtiği bir addır.
const
bir bit önemli değil - bu ikili yürütülebilir dosyadaki sembol adı main
. Ne fazla ne az. const
yü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, s
will bir karakter değerine sahiptir a
. Aynı şekilde, u
"i" t
ile değiştirilecek ve "n" ile değiştirilecektir. Ve işte böyle m##s##u##t
olacakmain
##
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 -E
bayrak 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 begin
makro olarak tanımlanır.
Bu nedenle, begin
makro basitçe olarak tanımlanır main
.
Örneğinizde main()
işlev gerçekte mevcuttur, çünkü begin
derleyicinin decode
makro 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 main
dan 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 WinMain
yawWinMain
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.