#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?? ");
}
これは間接的にmain
を呼び出しますか?どうやって?
C言語は、2つのカテゴリで実行環境を定義します:freestandingおよびhosted 両方の実行環境で、関数はプログラムの起動のために環境によって呼び出されます。
自立環境では、hosted環境はmain
である必要があります。 Cのプログラムは、定義された環境でプログラム起動機能なしでは実行できません。
あなたの場合、main
はプリプロセッサ定義によって隠されています。 begin()
はdecode(a,n,i,m,a,t,e)
に展開され、さらに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つのパラメーターを持つパラメーター化されたマクロです。このマクロの置換リストは_m##s##u##t
_です。 _m, s, u
_およびt
は4番目、1st、3rd および2nd 置換リストで使用されるパラメーター。
_s, t, u, m, p, e, d
1 2 3 4 5 6 7
_
残りは役に立たない(単に難読化する)。 decode
に渡される引数は "a、n、i、m、a、t 、e "ですので、識別子_m, s, u
_とt
は、それぞれ引数_m, a, i
_とn
に置き換えられます。
_ m --> m
s --> a
u --> i
t --> n
_
_gcc -E source.c
_を使用してみてください。出力は次で終わります:
_int main()
{
printf("Ha HA see how it is?? ");
}
_
したがって、main()
関数は実際にはプリプロセッサによって生成されます。
問題のプログラムdoesはマクロ展開のためにmain()
を呼び出しますが、あなたの仮定に誤りがあります-itmain()
を呼び出す必要はまったくありません!
厳密に言うと、Cプログラムを使用して、main
シンボルなしでコンパイルできます。 main
は、c library
が独自の初期化を完了した後、ジャンプするものです。通常、_start
として知られるlibcシンボルからmain
にジャンプします。メインがなくても、単にアセンブリを実行するだけの非常に有効なプログラムを作成することは常に可能です。これを見てください:
/* 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 */
}
上記をgcc -nostdlib without_main.c
でコンパイルし、インラインアセンブリでシステムコール(割り込み)を発行するだけで、画面にHello World!
が出力されるのを確認します。
この特定の問題の詳細については、 ksplice blog をご覧ください。
別の興味深い問題は、main
シンボルをC関数に対応させずにコンパイルするプログラムを使用できることです。たとえば、非常に有効なCプログラムとして次のものを使用できます。これにより、警告レベルを上げたときにのみコンパイラーが泣き叫びます。
/* 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
};
配列内の値は、画面にHello Worldを印刷するために必要な指示に対応するバイトです。この特定のプログラムがどのように機能するかについてのより詳細な説明については、この ブログ投稿 をご覧ください。
これらのプログラムについて最後にお知らせしたいと思います。 C言語仕様に従って有効なCプログラムとして登録されているかどうかはわかりませんが、仕様自体に違反していても、これらをコンパイルして実行することは確かに非常に可能です。
誰かがマジシャンのように行動しようとしています。彼は私たちをだますことができると考えています。しかし、cプログラムの実行はmain()
で始まります。
int begin()
は、プリプロセッサステージの1つのパスによってdecode(a,n,i,m,a,t,e)
に置き換えられます。次に、decode(a,n,i,m,a,t,e)
がm ## a ## i ## nに置き換えられます。マクロ呼び出しの位置関連付けと同様に、s
の値は文字a
になります。同様に、u
は 'i'に置き換えられ、t
は 'n'に置き換えられます。そして、それがどのように、m##s##u##t
はmain
になります
に関して、##
記号はマクロ展開で使用され、前処理演算子であり、トークンの貼り付けを実行します。マクロが展開されると、各「##」演算子の両側にある2つのトークンが単一のトークンに結合され、マクロ展開の「##」と2つの元のトークンが置き換えられます。
あなたが私を信じないなら、あなたは-E
国旗。前処理後にコンパイルプロセスを停止し、トークンの貼り付けの結果を確認できます。
gcc -E FILENAME.c
decode(a,b,c,d,[...])
は最初の4つの引数をシャッフルし、それらを結合してdacb
の順序で新しい識別子を取得します。 (残りの3つの引数は無視されます。)たとえば、decode(a,n,i,m,[...])
は識別子main
を提供します。これはbegin
マクロが定義されているものであることに注意してください。
したがって、begin
マクロは、単にmain
として定義されます。
あなたの例では、main()
関数が実際に存在します。なぜなら、begin
は、コンパイラーがdecode
マクロに置き換えるマクロであり、これは式m ## s ## u#に置き換えられるためです。 #t。マクロ展開##
を使用すると、main
からWord decode
に到達します。これはトレースです:
begin --> decode(a,n,i,m,a,t,e) --> m##parameter1##parameter3##parameter2 ---> main
main()
を使用するのは単なるトリックですが、Cプログラミング言語では、プログラムのエントリ関数に名前main()
を使用する必要はありません。ツールの1つとして、オペレーティングシステムとリンカーに依存します。
Windowsでは、常にmain()
を使用するわけではありませんが、- -WinMain
またはwWinMain
を使用しますが、 main()
、Microsoftのツールチェーンでも 。 Linuxでは、_start
を使用できます。
言語そのものではなく、エントリポイントを設定するのはオペレーティングシステムツールとしてのリンカ次第です。 独自のエントリポイントを設定し、実行可能なライブラリを作成することもできます !