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

How to get the address (pointer) of a variable from a function without using return

本文关键字:获取 函数 变量 地址 指针 情况下 返回      更新时间:2023-10-16

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

  • 由于某些原因,我无法在foo()中使用return
  • 来自main()功能,我不知道a的数据类型
void foo(void *ptr){
    int a = 12345;
    ptr = &a;
    printf("ptr in abc: %dn",ptr);
}
int main() {
    void *ptr;
    foo(ptr);
    printf("ptr in main: %dn",ptr);
    //printf("a in main: %dn",*ptr);       //print the value of a (ie. 12345)

    return 0;
}

如何在不使用返回的情况下从函数中获取[任何]

在不返回的情况下从函数到外部的一种方法是使用间接。将指针传递给某些对象作为参数,并通过函数内的指针间接设置指向对象的值。

从main((函数中,我不知道

的数据类型

您可以使用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
}

最重要的是:局部对象a不再存在foo返回之后,因此指针是悬挂的,并且对它没有太多有用。因此,这是一个毫无意义的练习。

您的函数foo有2个主要问题。

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

另一个导致不确定行为的问题是您的变量a量不足。如果要在范围用完之后访问它,则必须在堆上分配(例如,使用新的(。

由于某些原因,我无法在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: %dn", x);
}

为了了解为什么您不应该尝试将指针返回到本地变量,您需要可视化首先分配本地变量的方式。

局部变量分配在堆栈中。该堆栈是一个保留的内存区域,其主要目的是"面包屑"的内存地址,一旦CPU完成执行子例程,CPU就应该跳跃。

在输入子例程之前(通常是通过x86体系结构中的CALL机器语言指令(,CPU将在呼叫之后立即将指令的地址推在堆栈上。

ret_address_N
. . . . . . .
ret_address_3
ret_address_2
ret_address_1

当子例程结束时,RET URN指令使CPU从堆栈中弹出了最新地址,并通过跳到它来重定向执行,从而有效地恢复了启动呼叫的子例程或函数的执行。

>

此堆栈布置非常强大,因为它允许您嵌套大量独立的子例程呼叫(允许构建通用,可重复使用的库(,它还允许递归呼叫,函数可以自身呼叫(直接,直接,,或间接地通过嵌套子例程(。

此外,只要在从子例程中返回之前恢复了堆栈状态,> >> > >(有特殊的CPU指令(>,否则,当RET指令弹出预期的返回地址时,它将获取垃圾,并且会尝试将执行力跳动,很可能会崩溃。(顺便说一句,这也是通过使用有效地址覆盖堆栈的恶意软件利用数量,并在执行RET指令时强迫CPU跳到恶意代码(

例如,可以使用此堆栈功能来存储在子例程内修改的CPU寄存器的原始状态 - 允许代码在子例程退出之前还原其值与执行子例程呼叫之前的状态相同。

像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指令可以在堆栈的顶部找到正确的内存地址。

现在,您可以理解为什么您的不能不应将指针返回到本地变量。通过这样做,您将一个地址返回到堆栈中存储的值。您可以放弃指针,可能会查看看起来像有效的数据,因为您立即从子例程中返回,将指针返回到本地变量,但是此数据肯定会被覆盖,可能在不久的将来,随着程序的执行继续调用子例程。