在米切尔的书(概念编程语言)第6.2.1章中,它提到:
类型转换。类型转换允许将一种类型的值用作另一种类型。特别是在C中,可以将整数转换为函数,允许跳转到不包含正确形式的指令的位置为C函数。
所以我开始使用这种非安全性并做一些与众不同的事情 我试过这样的事情(伪代码):
int x = 0;
print "loop";
x();
创建无限循环。我试过改变和测试,但我无法应对。 我怎么能做这些东西或其他所有东西呢?
提前致谢
答案 0 :(得分:6)
这不是它的工作方式。
在C中,可以将整数值强制转换为函数指针值,然后调用这样的函数指针
void (*ptr)() = (void (*)())42;
ptr();
但这可能只会导致崩溃,除非您确切知道自己在做什么,即您已经以某种方式知道在地址42处启动具有该签名的函数;在这个例子中有一个固定的地址可能会在系统编程中发生,而在应用程序编程中则非常罕见。
实际发生的次数比想要的更频繁(特别是在Windows编程中)是传递给某些整数的函数指针(LPARAM
/ WPARAM
中的回调任何人?),然后将它们转换回函数指针以实际使用它们。
此外,执行此类强制转换/调用时会发生什么情况超出了C标准的范围,这使得实现可以自由地在这方面做他们想要的事情。
答案 1 :(得分:3)
@Soroush,这是一个可以帮助您更好地了解幕后情况的例子:
#include <stdio.h>
int main(void)
{
printf("begin\n");
printf("loop\n");
// declare a function pointer
int (*loopPtr)();
// set the function pointer to the current function
loopPtr = main;
// skip over the first printf();
loopPtr += 22;
// call the new location
loopPtr();
}
对我来说,当用clang -O0
编译时,它适用于x86_64(好吧,它一直有效,直到堆栈耗尽,因为这是无限递归,每个函数调用都会通过堆栈空间进行咀嚼)。
我通过编译,然后从第二个main()
的地址中反汇编并从printf()
开始减去地址来确定偏移量。
首先,我编译了它:
clang -O0 test.c
然后拆开它:
otool -tv a.out
...产生了这个输出:
[...]
_main:
0000000100000ee0 pushq %rbp
0000000100000ee1 movq %rsp,%rbp
0000000100000ee4 subq $0x20,%rsp
0000000100000ee8 leaq 0x00000073(%rip),%rdi
0000000100000eef movb $0x00,%al
0000000100000ef1 callq 0x100000f40
0000000100000ef6 leaq 0x0000006c(%rip),%rdi
0000000100000efd movl %eax,0xf4(%rbp)
0000000100000f00 movb $0x00,%al
0000000100000f02 callq 0x100000f40
[...]
_main:
表示main()
函数的入口点,其第一个地址为0x100000ee0。第一个callq
指令对应第一个printf()
调用,我想跳过它,所以我选择了之后的地址:0x100000ef6。 0x100000ef6减去0x100000ee0是小数点后22位。
答案 2 :(得分:2)
好吧,x()
并没有神奇地调用该地址的函数。我认为他的意思是:
typedef void (*functionPtr)();
int x;
//...
functionPtr foo = (functionPtr)x;
//or
functionPtr goo = (functionPtr)&x;
foo();
答案 3 :(得分:1)
你的书有误导性。标准允许的唯一事情是强制转换操作,只有当值适合时才这样做。在大多数情况下,执行这样的函数指针是未定义的行为。您必须非常了解您的系统才能实现这一目标。所以引用段落的第二部分是:
特别是在C中,可以将整数转换为函数,允许a 跳转到不包含正确形式的位置 指令是C函数。
在这种形式中是不正确的。特别是所有体面的现代系统都不允许您执行数据,您必须为页面设置特殊标志,以便可以认为它包含可执行代码。