#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
吗?怎么样?
Answers:
C语言将执行环境定义为两类:独立式和托管。在两种执行环境中,环境都会调用一个函数来启动程序。
在独立的环境中,程序的启动功能可以定义实现,而在宿主环境中应该是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
。
该程序确实是main()
由于宏扩展而调用的,但您的假设存在缺陷-根本不需要调用main()
!
严格来说,您可以拥有一个C程序,并且可以在没有main
符号的情况下对其进行编译。在完成自己的初始化之后,它main
是c library
期望加入的东西。通常,您会main
从libc符号跳入_start
。总是可能有一个非常有效的程序,该程序只执行汇编而没有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博客。
另一个有趣的问题是,您还可以拥有一个无需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程序,但是即使它们违反了规范本身,也很有可能编译并运行它们。
_start
定义标准的一部分的名称,还是仅特定于实现的名称?当然,您的“作为阵列的主体”是特定于体系结构的。同样重要的是,由于安全方面的限制,“作为阵列的主体”技巧在运行时失败不会是不合理的(尽管如果不使用const
限定符,这种可能性更大,而且仍然有很多系统允许这样做)。
_start
不在ELF标准,尽管AMD64 psABI包含参照_start
在3.4进程初始化。正式地,ELF只知道e_entry
ELF标头中的地址,_start
这只是实现选择的名称。
const
没关系-该二进制可执行文件中的符号名称为main
。不多不少。 const
是一个C构造,在执行时没有任何意义。
有人试图扮演魔术师。他认为他可以欺骗我们。但众所周知,c程序的执行始于main()
。
的int begin()
将被替换为decode(a,n,i,m,a,t,e)
由预处理器级的一个通。然后再次decode(a,n,i,m,a,t,e)
将其替换为m ## a ## i ## n。如通过宏调用的位置关联, s
将具有character的值a
。同样,u
将被替换为“ i”,t
将被替换为“ n”。而且,这m##s##u##t
将成为main
关于##
符号,在宏扩展中,它是预处理运算符,它执行令牌粘贴。扩展宏时,每个'##'运算符两侧的两个标记合并为一个标记,然后替换宏扩展中的'##'和两个原始标记。
如果您不相信我,可以使用-E
flag编译代码。预处理后它将停止编译过程,您可以看到令牌粘贴的结果。
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编程语言中,不必为程序的入口函数使用名称。它取决于您的操作系统和链接器作为其工具之一。
在Windows中,你并不总是使用main()
,但是相当WinMain
或者wWinMain
,虽然可以使用main()
,甚至与微软的工具链。在Linux中,可以使用_start
。
链接程序作为操作系统工具来设置入口点,而不是语言本身。您甚至可以设置我们自己的入口点,并且可以创建一个可执行文件的库!
main()
功能绑定到C编程语言的第一个答案,这是不正确的。