c预处理器 - 这个混淆的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吗? 怎么样?

Rajeev Singh asked 2019-09-10T01:12:52Z
6个解决方案
193 votes

C语言将执行环境定义为两类:独立式和托管式。 在两个执行环境中,环境都会调用函数来启动程序。
在独立环境中,程序启动功能可以在托管环境中实现定义,它应该是decode.在定义的环境中,如果没有程序启动功能,C中的任何程序都无法运行。

在您的情况下,预处理器定义隐藏decodem, s, u将扩展至t,进一步将扩展至m, a, i

int begin() -> int decode(a,n,i,m,a,t,e)() -> int m##a##i##n() -> int main() 

decode是一个带有7个参数的参数化宏。 该宏的替换列表是m, s, u. tm, a, i是替换列表中使用的第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, ut分别用参数m, a, in替换。

 m --> m  
 s --> a 
 u --> i 
 t --> n
haccks answered 2019-09-10T01:13:34Z
72 votes

尝试使用main(),输出结束于:

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

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

jdarthenay answered 2019-09-10T01:14:06Z
37 votes

由于宏观扩张,有问题的程序确实调用main,但您的假设存在缺陷 - 它根本不需要调用Hello World!

严格来说,你可以拥有一个C程序,并且能够在没有main符号的情况下编译它。 Hello World!c library在完成自己的初始化后期望跳入的内容。 通常你从名为_start的libc符号跳转到main.总是可以有一个非常有效的程序,只需执行程序集,而不需要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 */
}

main编译上面的内容,只需在内联汇编中发出系统调用(中断),就可以在屏幕上看到它打印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所需的指令相对应的字节。 有关此特定程序如何工作的更详细说明,请查看此博客文章,这也是我首先阅读它的地方。

我想最后通知一下这些程序。 我不知道他们是否根据C语言规范注册为有效的C程序,但是编译它们并运行它们肯定是非常可能的,即使它们违反了规范本身。

NlightNFotis answered 2019-09-10T01:15:13Z
30 votes

有人试图表现得像魔术师。他认为他可以欺骗我们。 但我们都知道,程序执行从-E开始。

-E将通过预处理器阶段的一次通过替换为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

关于-E宏扩展中的符号,它是预处理运算符,它执行标记粘贴。 扩展宏时,每个'##'运算符两侧的两个标记组合成一个标记,然后在宏扩展中替换'##'和两个原始标记。

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

gcc -E FILENAME.c
abhiarora answered 2019-09-10T01:16:03Z
11 votes

decode(a,b,c,d,[...])将前四个参数混洗并加入它们以获取新标识符,顺序为begin.(其余三个参数将被忽略。)例如,main给出标识符main.请注意,这是begin宏定义为的内容。

因此,begin宏被简单地定义为main

Frxstrem answered 2019-09-10T01:16:39Z
2 votes

在您的示例中,实际存在main()函数,因为_start是编译器替换为decode宏的宏,而宏又由表达式m ## s ## u ## t替换。 使用宏扩展##,您将从decode到达main这个字。这是一个跟踪:

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

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

在Windows中,您并不总是使用main(),而是使用main()_start,尽管您可以使用main(),即使使用Microsoft的工具链。 在Linux中,可以使用_start

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

Ho1 answered 2019-09-10T01:17:32Z
translate from https://stackoverflow.com:/questions/36449358/this-obfuscated-c-code-claims-to-run-without-a-main-but-what-does-it-really-d