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

Posted

技术标签:

【中文标题】如何在不使用return的情况下从函数中获取变量的地址(指针)【英文标题】:How to get the address (pointer) of a variable from a function without using return 【发布时间】:2019-07-01 14:20:05 【问题描述】:

对于下面的 C 代码,我如何从 foo() 函数中获取 a 的地址(指针)到 main() 函数?

由于某些原因,我无法在foo() 中使用return 来自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;

【问题讨论】:

你不应该因为a是局部变量,一旦控制退出foo函数它就会消失。 您可以将指针作为输入参数,指向像 int** 这样的指针。但更重要的是,你想在注释掉的 printf 中做的事情是不可能的,因为一旦函数返回,“a”就会死去 除此之外,您不能从void foo 返回值,因为它是void,这意味着它不会返回任何值。如果不清楚诸如声明函数之类的基本语言特性,那么我建议在good book 的帮助下学习该语言。 “由于某些原因,我不能在 foo() 中使用 return” 如果你想学习,应该分享这些原因。我猜想其中一个原因是该函数返回void。另一个原因可能是编译器告诉您return &a; 可能是一个错误。 【参考方案1】:

如何在不使用 return 的情况下从函数中获取 [anything]

一种从函数内部获取东西而不返回的方法是使用间接。将指向某个对象的指针作为参数传递,并通过函数内部的指针间接设置指向对象的值。

从main()函数中,我不知道a的数据类型

您可以使用 void 指针指向任何对象,而无需知道对象的类型。

把这些东西放在一起:

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

然而最重要的是:在foo 返回后,本地对象a 不再存在,因此指针悬空,并没有太多用处。因此,这是一个相当毫无意义的练习。

【讨论】:

这可以用作在堆中分配内存并将地址返回给它分配的内存的函数。不过不太好。【参考方案2】:

你的函数foo有两个主要问题。

第一个,也就是程序无法编译的原因,是foo的返回类型。因为它是void,所以不能从中返回任何值。

另一个会导致未定义行为的问题是您的变量a 超出范围。如果你想在它超出范围后访问它,它必须在堆上分配(例如用 new )。

【讨论】:

访问超出范围的变量不太可能导致崩溃。更有可能的是,看到的值会随着其他函数的调用而改变,并将空间重新用于它们自己的变量。尝试它仍然是 UB(未定义行为),即使不太可能发生崩溃也是可能的。 变量可以声明为static,函数返回静态变量的引用或指针。【参考方案3】:

由于某些原因,我不能在foo() 中使用return

因为您将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);

【讨论】:

【参考方案4】:

为了理解为什么不应该尝试返回指向局部变量的指针,首先需要可视化局部变量是如何分配的。

局部变量在堆栈中分配。堆栈是一个保留的内存区域,主要用途是留下内存地址的“面包屑”轨迹,一旦 CPU 完成执行子程序,就应该在其中跳转。

在进入子程序之前(通常通过 x86 架构中的 CALL 机器语言指令),CPU 会将紧跟在 CALL 之后的指令地址压入堆栈。

ret_address_N
. . . . . . .
ret_address_3
ret_address_2
ret_address_1

当子例程结束时,RETurn 指令使 CPU 从堆栈中弹出最近的地址并通过跳转到该地址重定向执行,从而有效地在发起调用的子例程或函数上恢复执行。

这种堆栈安排非常强大,因为它允许您嵌套大量独立的子例程调用(允许构建通用的、可重用的库),它还允许递归调用,其中函数可以调用自身(直接,或间接地,通过嵌套的子例程)。

此外,没有什么可以阻止您将自定义数据推送到堆栈上(对此有特殊的 CPU 指令)只要堆栈状态在从子程序返回之前恢复 >,否则当 RET 指令弹出预期的返回地址时,它将获取垃圾并尝试跳转执行,很可能会崩溃。 (顺便说一句,这也是有多少恶意软件利用有效地址覆盖堆栈,并在执行 RET 指令时强制 CPU 跳转到恶意代码)

此堆栈功能可用于,例如,存储在子例程中修改的 CPU 寄存器的原始状态 - 允许代码在子例程退出之前恢复它们的值,以便调用者子例程可以看到寄存器与执行子程序 CALL 之前的状态相同。

像 C 这样的语言也使用此功能通过设置堆栈框架来分配局部变量。编译器基本上将计算某个子例程中的每个局部变量所需的字节数相加,并在调用子例程时发出 CPU 指令,这些指令将按计算出的字节数替换堆栈顶部。现在每个局部变量都可以作为当前堆栈状态的相对偏移量来访问。

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

【讨论】:

以上是关于如何在不使用return的情况下从函数中获取变量的地址(指针)的主要内容,如果未能解决你的问题,请参考以下文章

如何在不使用 CURDATE、MONTH、YEAR 等 DATE 函数的情况下从 RDBMS 获取当前月份记录

如何在不复制的情况下从 N 维容器中获取可迭代范围?

如何在不返回 Promise 的情况下从对象的`get()` 获取异步数据

为啥 Python 3.8.0 允许在不使用“非本地”变量的情况下从封闭函数范围更改可变类型?

如何在不使用自动模型表单的情况下从 Django 的文本框中获取数据?

如何在不使用 regexp_like 的情况下从列中获取整数