对于以下C代码,如何从a
函数到foo()
函数获取main()
的地址(指针)?
return
中使用foo()
main()
函数,我不知道a
的数据类型void foo(void *ptr){
int a = 12345;
ptr = &a;
printf("ptr in abc: %d\n",ptr);
}
int main() {
void *ptr;
foo(ptr);
printf("ptr in main: %d\n",ptr);
//printf("a in main: %d\n",*ptr); //print the value of a (ie. 12345)
return 0;
}
答案 0 :(得分:4)
如何在不使用return的情况下从函数中获取[任何内容]
一种将内容从函数内部传递到外部而不返回的方法是使用间接寻址。将指针传递给某个对象作为参数,然后间接通过函数内部的指针来设置指向对象的值。
从main()函数中,我不知道a的数据类型
您可以使用空指针指向任何对象,而不必知道对象的类型。
将这些东西放在一起:
int main(void) {
void* ptr; // a variable to store the address
foo(&ptr); // pass pointer to the variable
// ptr now points to where a used to be
}
void foo(void** ptr){
int a = 12345;
*ptr = &a; // set the pointed variable
}
最重要的:在a
返回之后,本地对象foo
不再存在,因此指针悬空了,没有太多有用的东西了。完成它。因此,这是一个相当无意义的练习。
答案 1 :(得分:1)
函数foo
有两个主要问题。
第一个,即程序不编译的原因,是返回类型foo
。因为它是void
,所以您不能从中返回任何值。
另一个导致未定义行为的问题是变量a
超出范围。如果要在超出范围后访问它,则必须在堆上分配它(例如,使用new)。
答案 2 :(得分:0)
由于某些原因,我无法在
return
中使用foo()
因为您将foo
声明为具有返回类型void
。如果有机会,可以使用它:
int* foo() {
int a = 42;
return &a;
}
但是,调用代码不能使用返回值,因为它指向不再有效的内存(在过去的函数调用中为局部变量)。无论调用代码如何如何获取指针,都是如此:无论是通过返回指针,还是将其传递给out参数。您完全不必这样做。
通过
main()
函数,我不知道a
的数据类型
对,因为您将指针显式声明为void*
,因此删除了数据类型。声明正确的数据类型以避免这种情况。
长话短说,这里没有理由使用void*
参数代替返回值int
:
int foo() {
int a = 42;
return a;
}
int main(void) {
int a = foo();
printf("a in main: %d\n", x);
}
答案 3 :(得分:0)
为了理解为什么,您不应该尝试返回指向局部变量的指针,您需要首先观察一下局部变量的分配方式。
局部变量在堆栈中分配。堆栈是保留存储器区域,其主要目的是在其完成子程序执行后,在CPU应该跳转到的存储器地址中留下“面包屑”痕迹。
在进入子程序之前(通常通过x86架构中的CALL
机器语言指令),CPU将在CALL之后立即将指令的地址压入堆栈。
ret_address_N
. . . . . . .
ret_address_3
ret_address_2
ret_address_1
子例程结束时,RET
urn指令使CPU从堆栈中弹出最近的地址,并通过跳转到该地址来重定向执行,从而有效地恢复了发起调用的子例程或函数的执行。 >
这种堆栈安排非常强大,因为它允许您嵌套大量独立的子例程调用(允许构建通用的可重用库),还允许递归调用,其中函数可以自行调用(直接,或通过嵌套子例程间接访问。)
此外,没有什么可以阻止您将自定义数据压入堆栈(对此有特殊的CPU指令)只要从子程序返回之前就恢复了堆栈状态, >,否则当RET指令弹出预期的返回地址时,它将获取垃圾并尝试将执行跳转到该地址,很可能导致崩溃。 (顺便说一下,通过使用有效地址覆盖堆栈,并在执行RET指令时强制CPU跳转到恶意代码,这也是恶意软件利用的数量)
例如,可以使用此堆栈功能来存储在子例程中修改的CPU寄存器的原始状态-允许代码在子例程退出之前恢复其值,以便调用者子例程可以在其中查看寄存器。与执行子程序CALL之前的状态相同。
类似于C的语言也使用此功能通过设置堆栈框架来分配局部变量。编译器基本上累加了某个子例程中每个局部变量所需的字节数,并会发出CPU指令,当调用子例程时,CPU指令会将堆栈的顶部移位此计算出的字节数。现在,每个局部变量都可以相对于当前堆栈状态的 relative 偏移量进行访问。
-------------
------------- local variables for subroutine N
-------------
ret_address_N
------------- local variables for subroutine 3
ret_address_3
------------- local variables for subroutine 2
-------------
ret_address_2
-------------
------------- local variables for subroutine 1
-------------
-------------
ret_address_1
除了发出指令以设置堆栈帧(有效地在堆栈上分配局部变量并保留当前寄存器值)外,C编译器还将发出指令以将堆栈状态恢复到函数调用之前的原始状态,因此当RET指令弹出应该跳转到的值时,它可以在堆栈的顶部找到正确的内存地址。
现在您可以理解为什么不能不应返回指向局部变量的指针。这样,您将返回一个地址,该地址为临时存储在堆栈中的值。您可以取消引用指针,然后 MIGHT 看到看起来像有效的数据,当您从子例程立即返回将指针返回到局部变量时,该数据肯定会被覆盖,很可能在不久的将来,随着程序执行继续调用子例程。