C++ 为什么这个将main定义为函数指针的程序会失败?

C++ 为什么这个将main定义为函数指针的程序会失败?,c++,main,C++,Main,以下程序在g++中编译得非常完美,没有错误或警告(即使使用-Wall),但会立即崩溃 #include <cstdio> int stuff(void) { puts("hello there."); return 0; } int (*main)(void) = stuff; #包括 int-stuff(void) { 放置(“你好。”); 返回0; } int(*main)(void)=填充物; 这是一个(明显误导的)试图在不显式声明main函数的情况下

以下程序在g++中编译得非常完美,没有错误或警告(即使使用
-Wall
),但会立即崩溃

#include <cstdio>

int stuff(void)
{
    puts("hello there.");
    return 0;
}


int (*main)(void) = stuff;
#包括
int-stuff(void)
{
放置(“你好。”);
返回0;
}
int(*main)(void)=填充物;

这是一个(明显误导的)试图在不显式声明main函数的情况下运行C++程序的尝试。我的目的是让程序通过绑定到符号

main
来执行
stuff
。我很惊讶这个程序被编译了,但是为什么编译后它会失败呢?我已经看过生成的程序集,但我知道的还不够,根本无法理解它

我完全知道有很多关于如何定义/使用
main
,但我不清楚我的程序是如何破坏它们的。我没有在我的程序中重载main或调用它。。。那么这样定义
main
到底违反了什么规则呢


注意:这不是我在实际代码中试图做的事情。这实际上是一次尝试的开始。

main
之前运行的代码中,有如下内容:

extern "C" int main(int argc, char **argv);
代码的问题在于,如果您有一个名为
main
的函数指针,它与函数不同(与Haskell相反,在Haskell中,函数和函数指针几乎可以互换——至少我对Haskell的了解为0.1%)

而编译器将乐于接受:

int (*func)()  = ...;

int x = func();
作为对函数指针
func
的有效调用。但是,当编译器生成调用
func
的代码时,它实际上是以不同的方式执行的[尽管标准没有说明如何执行,而且它在不同的处理器体系结构上有所不同,但实际上它在指针变量中加载值,然后调用此内容]

如果您有:

int func() { ... }

int x = func();
func
的调用只引用
func
本身的地址,并调用该地址

因此,假设您的代码确实编译,则
main
之前的启动代码将调用变量
main
的地址,而不是间接读取
main
中的值,然后调用该地址。在现代系统中,这将导致segfault,因为
main
存在于不可执行的数据段中,但在较旧的操作系统中,它很可能会由于
main
不包含真实代码而崩溃(但在这种情况下,它可能会在倒下之前执行一些指令——在遥远的过去,我不小心跑了各种各样的“垃圾”,很难发现原因……)

但是由于
main
是一个“特殊”函数,编译器也可能会说“不,你不能这样做”

许多年前,它曾起到过这样的作用:

char main[] = { 0xXX, 0xYY, 0xZZ ... }; 
但同样,这在现代操作系统中不起作用,因为
main
最终位于数据部分,在该部分不可执行

编辑:在实际测试发布的代码之后,至少在我的64位Linux上,代码实际上是编译的,但是当它试图执行main时,不出所料地崩溃了

在GDB中运行会产生以下结果:

Program received signal SIGSEGV, Segmentation fault.
0x0000000000600950 in main ()
(gdb) bt
#0  0x0000000000600950 in main ()
(gdb) disass
Dump of assembler code for function main:
=> 0x0000000000600950 <+0>: and    %al,0x40(%rip)        # 0x600996
   0x0000000000600956 <+6>: add    %al,(%rax)
End of assembler dump.
(gdb) disass stuff
Dump of assembler code for function stuff():
   0x0000000000400520 <+0>: push   %rbp
   0x0000000000400521 <+1>: mov    %rsp,%rbp
   0x0000000000400524 <+4>: sub    $0x10,%rsp
   0x0000000000400528 <+8>: lea    0x400648,%rdi
   0x0000000000400530 <+16>:    callq  0x400410 <puts@plt>
   0x0000000000400535 <+21>:    mov    $0x0,%ecx
   0x000000000040053a <+26>:    mov    %eax,-0x4(%rbp)
   0x000000000040053d <+29>:    mov    %ecx,%eax
   0x000000000040053f <+31>:    add    $0x10,%rsp
   0x0000000000400543 <+35>:    pop    %rbp
   0x0000000000400544 <+36>:    retq   
End of assembler dump.
(gdb) x main
0x400520 <stuff()>: 0xe5894855
(gdb) p main
$1 = (int (*)(void)) 0x400520 <stuff()>
(gdb) 
此时,打印
main
将为我们提供一个指向0x600950的函数指针,该指针是名为
main
的变量(与上面的内容相同)

(gdb)p main
$1=(整数(*)(整数,字符**,字符**))0x600950

注意,这是一个不同的变量<代码>主< /代码>,而不是一个被称为<代码>主< /代码>的源代码。

这里没有什么特别的关于它的主体()。如果你为任何函数这样做,同样会发生。请考虑这个例子:

file1.cpp:

#include <cstdio>

void stuff(void)
{
     puts("hello there.");
}

void (*func)(void) = stuff;
这也将编译,然后是segfault。它对函数func基本上做了相同的事情,但由于编码是显式的,所以现在看起来更明显是错误的。main()是一个普通的C类型函数,没有名称损坏,只是作为名称出现在符号表中。如果将其设置为函数以外的其他类型,则在执行指针时会出现SEGFULT


我想有趣的是,编译器将允许您定义一个名为main的符号,当它已经用不同的类型隐式声明时。

从我可能被误导的角度来看,我认为这在技术上是可以的,只要有一个真正的
main
,并且在技术上不应该链接,但这只是一个调用of
main()
,这就满足了。重新阅读main上的限制。“一个程序应该包含一个名为main的全局函数…”在你的代码中,main不是一个函数。@MooingDuck对我来说太多了Haskell。我从来没有想到
function!=指向函数的指针
。是否可以查看代码的启动部分?这样我们就可以确定它
调用
jmp
main
而不是加载存储的地址在
main
的位置,跳转到那个地址。@WiSaGaN:我肯定我能到达那个位置,但因为它实际上不是可执行文件本身的一部分,而是
glibc
代码,而对它的反汇编是几兆字节,而且不是完全微不足道的,我不确定仅仅剪切一条指令会有多大帮助很明显,IP寄存器指向的是
main
的地址,该地址不包含指令,这确实意味着代码“不理解间接寻址”.现在回想起来,你提到Haskell是正确的。撇开PGC的挑战不谈,我已经学习Haskell几个月了,我非常喜欢“函数和其他任何值一样都是一等公民”思维方式。我从来没有想到在C/C++中,
function!=指向函数的指针。非常感谢您的深入回答。实际上,
main
有一些特别之处<
(gdb) p main
$1 = (int (*)(int, char **, char **)) 0x600950 <main>
#include <cstdio>

void stuff(void)
{
     puts("hello there.");
}

void (*func)(void) = stuff;
extern "C" {void func(void);}

int main(int argc, char**argv)
{
    func();
}