这个混淆的C代码声称没有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语言定义了两种types的执行环境: 独立式托pipe式 。 在两个执行环境中,一个函数被程序启动的环境调用。
独立的环境中,程序启动function可以在托pipe环境中实现定义,而应该是main 。 C中的程序无法在定义的环境中运行程序启动function。

在你的情况下, 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个参数的参数化macros。 这个macros的replace列表是m##s##u##tm, s, ut是replace列表中使用的 4, 1, 3和第2个参数。

 s, t, u, m, p, e, d 1 2 3 4 5 6 7 

rest是没有用的( 只是为了混淆 )。 传递给decode参数是“ anim ,a,t,e”,所以标识符m, s, ut分别被参数m, a, in所取代。

  m --> ms --> au --> it --> n 

尝试使用gcc -E source.c ,输出结束于:

 int main() { printf("Ha HA see how it is?? "); } 

所以main()函数实际上是由预处理器生成的。

有问题的程序由于macros扩展而调用main() ,但是你的假设是有缺陷的 – 它根本不需要调用main()

严格来说,你可以有一个C程序,并能够编译它,而不需要一个main符号。 mainc 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编译上面的gcc -nostdlib without_main.c ,并看到它打印出Hello World! 只需在内联汇编中发出系统调用(中断)即可。

有关此特定问题的更多信息,请查看ksplice博客

另一个有趣的问题是,你也可以有一个程序编译而不需要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所需的指令相对应的字节。 有关这个特定程序如何工作的更详细的信息,请看这个博客文章 ,这也是我第一次阅读的地方。

我想就这些scheme做最后的通知。 我不知道他们是否按照C语言规范注册为有效的C程序,但编译并运行它们当然是非常有可能的,即使它们违反了规范本身。

有人正在试图像魔术师一样行事。 他认为他可以欺骗我们。 但是我们都知道,c程序的执行从main()开始。

通过预处理器阶段的一遍, int begin()将被replace为decode(a,n,i,m,a,t,e) 。 然后再decode(a,n,i,m,a,t,e)将被replace为m ## a ## i ## n。 如通过macros调用的位置关联, s将具有字符a的值。 同样, u将被'我'取代, t将被'n'取代。 而且,这就是如何, m##s##u##t将成为main

关于macros扩展中的##符号,它是预处理运算符,它执行令牌粘贴。 当一个macros展开时,每个'##'操作符两边的两个令牌被合并成一个单独的令牌,然后代替macros扩展中的'##'和两个原始令牌。

如果你不相信我,你可以用-E标志编译你的代码。 它将在预处理后停止编译过程,您可以看到令牌粘贴的结果。

 gcc -E FILENAME.c 

decode(a,b,c,d,[...])将前四个参数进行dacb ,并按照dacb的顺序join它们以获得新的标识符。 (其余三个参数被忽略。)例如, decode(a,n,i,m,[...])给出标识符main 。 请注意,这是beginmacros被定义为。

因此, beginmacros被简单地定义为main

在你的例子中, main()函数实际上是存在的,因为begin是一个macros,编译器用decodemacros代替macros,而macros又被expression式m ## s ## u ## t替代。 使用macros扩展## ,您将达到decode main字。 这是一个痕迹:

 begin --> decode(a,n,i,m,a,t,e) --> m##parameter1##parameter3##parameter2 ---> main 

使用main()只是一个窍门,但在C编程语言中,使用名称main()作为程序的入口函数是不必要的。 这取决于您的操作系统和连接器作为其工具之一。

在Windows中,不一定使用main() , 而是使用WinMainwWinMain ,尽pipe可以使用main() ,即使使用了Microsoft的工具链 。 在Linux中,可以使用_start

链接器作为一个操作系统工具来设置入口点,而不是语言本身。 你甚至可以设置我们自己的入口点,并且可以创build一个也是可执行的库 。