C++ 为什么这个将main定义为函数指针的程序会失败?
以下程序在g++中编译得非常完美,没有错误或警告(即使使用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函数的情况下
-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
,并且在技术上不应该链接,但这只是一个调用ofmain()
,这就满足了。重新阅读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();
}