如何在不使用return的情况下从函数获取变量的地址(指针)

时间:2019-07-01 14:20:05

标签: c++ c function pointers

对于以下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;
}

4 个答案:

答案 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 看到看起来像有效的数据,当您从子例程立即返回将指针返回到局部变量时,该数据肯定会被覆盖,很可能在不久的将来,随着程序执行继续调用子例程。