#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
ますか?どうやって?
#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つのカテゴリで実行環境を定義します。どちらの実行環境でも、プログラム起動のために環境から関数が呼び出されます。
で自立環境プログラムの起動機能の実装でいる間に定義することができますホストされた環境を、それがあるべき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は番目、1番目、3番目及び2番目の置換リストで使用されるパラメータ。
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
_start()
。または、さらに低レベルで、プログラムの開始を起動後にIPが設定されているアドレスに合わせるようにすることもできます。main()
C標準ライブラリです。C自体はこれに制限を課しません。
decode(a,n,i,m,a,t,e)
なるのm##a##i##n
か説明してもらえますか?文字を置き換えますか?decode
関数のドキュメントへのリンクを提供できますか?ありがとう。
begin
は、decode(a,n,i,m,a,t,e)
以前に定義されたものに置き換えられるように定義されています。この関数は引数s,t,u,m,p,e,d
を受け取り、この形式でそれらを連結しますm##s##u##t
(##
連結を意味します)。つまり、p、e、dの値を無視します。decode
s = a、t = n、u = i、m = mで「呼び出す」と、事実上。に置き換えbegin
られmain
ます。
を使用してみてくださいgcc -E source.c
。出力は次で終わります。
int main()
{
printf("Ha HA see how it is?? ");
}
したがって、main()
関数は実際にはプリプロセッサによって生成されます。
問題のプログラムはありません通話をmain()
伴うマクロ展開に、しかし、あなたの仮定は欠陥がある-それはしません呼び出す必要がありmain()
、すべてに!
厳密に言えば、Cプログラムを作成して、main
シンボルなしでコンパイルすることができます。main
はc library
、独自の初期化が完了した後、ジャンプすることを期待しているものです。通常main
は、として知られているlibcシンボルからジャンプします_start
。メインがなくても、アセンブリを実行するだけの非常に有効なプログラムを持つことは常に可能です。これを見てください:
/* 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ブログをご覧ください。
もう1つの興味深い問題は、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
};
配列の値は、HelloWorldを画面に出力するために必要な命令に対応するバイトです。この特定のプログラムがどのように機能するかについてのより詳細な説明については、このブログ投稿をご覧ください。ここでも最初に読んでいます。
これらのプログラムについて最後にお知らせしたいと思います。C言語の仕様に従って有効なCプログラムとして登録されているかどうかはわかりませんが、仕様自体に違反していても、コンパイルして実行することは確かに可能です。
_start
定義された標準の一部の名前ですか、それとも実装固有ですか?確かに、「配列としてのメイン」はアーキテクチャ固有です。また重要なのは、セキュリティ制限のために実行時に「配列としてのメイン」トリックが失敗することは不合理ではないことです(ただし、const
修飾子を使用しなかった場合はそれが発生する可能性が高く、多くのシステムで許可されます)。
_start
AMD64 psABIが参照含まれても、ELF標準にない_start
で3.4プロセスの初期化。公式には、ELFe_entry
はELFヘッダーのアドレスについてのみ知っており_start
、実装が選択した名前にすぎません。
const
、1ビットは関係ありません-そのバイナリ実行可能ファイルのシンボル名はmain
です。それ以上でもそれ以下でもありません。 const
は、実行時に何も意味しないC構造です。
誰かが魔術師のように振る舞おうとしています。彼は私たちをだますことができると思っています。しかし、cプログラムの実行はmain()
。で始まります。
int begin()
置き換えられますdecode(a,n,i,m,a,t,e)
プリプロセッサステージの1回のパスで。その後、再びdecode(a,n,i,m,a,t,e)
m ## a ## i ## nに置き換えられます。マクロ呼び出しの位置関連付けと同様に、 s
willの値は文字a
です。同様に、u
「i」t
に置き換えられ、「n」に置き換えられます。そして、そういう風にm##s##u##t
なりますmain
##
マクロ展開の記号については、前処理演算子であり、トークンの貼り付けを行います。マクロが展開されると、各「##」演算子の両側にある2つのトークンが1つのトークンに結合され、マクロ展開の「##」と2つの元のトークンが置き換えられます。
私を信じていない場合は、-E
フラグを使用してコードをコンパイルできます。前処理後にコンパイルプロセスを停止し、トークンの貼り付けの結果を確認できます。
gcc -E FILENAME.c
あなたの例では、main()
関数が実際に存在します。begin
これは、コンパイラがdecode
マクロに置き換え、次に式m ## s ## u ## tに置き換えるマクロだからです。マクロ展開を使用##
すると、main
から単語に到達しdecode
ます。これはトレースです:
begin --> decode(a,n,i,m,a,t,e) --> m##parameter1##parameter3##parameter2 ---> main
持つのは単なるトリックですがmain()
、main()
Cプログラミング言語では、プログラムの入力関数の名前を使用する必要はありません。それはあなたのオペレーティングシステムとそのツールの1つとしてのリンカーに依存します。
Windowsでは、常に使用するmain()
わけではなくWinMain
wWinMain
、Microsoftのツールチェーンでも使用できますがmain()
、またはを使用します。Linuxでは、を使用できます_start
。
言語自体ではなく、エントリポイントを設定するのはオペレーティングシステムツールとしてのリンカー次第です。独自のエントリポイントを設定することもでき、実行可能なライブラリを作成することもできます。
main()
関数をCプログラミング言語にバインドする最初の回答を補完/修正するために書いた部分的な回答です。これは正しくありません。