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

C++ 如何在不使用return的情况下从函数中获取变量的地址(指针),c++,c,function,pointers,C++,C,Function,Pointers,对于下面的C代码,我如何获得从foo函数到main函数的地址指针 由于某些原因,我不能在foo中使用return 从main函数中,我不知道 函数foo有两个主要问题 第一个是foo的返回类型,这也是程序不编译的原因。因为它是空的,所以不能从中返回任何值 导致未定义行为的另一个问题是变量a超出范围。如果您想在它超出范围后访问它,则必须在堆上分配它,例如使用new 如何在不使用return的情况下从函数中获取[任何内容] 从函数内部到函数外部而不返回的一种方法是使用间接寻址。将指向某个对象的指针作

对于下面的C代码,我如何获得从foo函数到main函数的地址指针

由于某些原因,我不能在foo中使用return 从main函数中,我不知道
函数foo有两个主要问题

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

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

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

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

从main函数中,我不知道

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

把这些东西放在一起:

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不再存在,因此指针悬空,并且没有多少有用的东西可以用它来完成。因此,这是一项毫无意义的工作

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

因为您声明foo具有返回类型void。如果您有机会,您可以使用它:

int* foo() {
    int a = 42;
    return &a;
}
但是,调用代码不能使用该返回值,因为它指向的内存在过去的函数调用中不再是有效的局部变量。无论调用代码如何获取指针:无论是通过返回指针,还是通过将指针传递给out参数,这都是正确的。你绝对不能这样做

从main函数中,我不知道

对,因为您显式地将指针声明为void*,从而删除了数据类型。声明正确的数据类型以避免这种情况

长话短说,这里没有理由使用void*参数而不是int返回值:

int foo() {
    int a = 42;
    return a;
}

int main(void) {
    int a = foo();
    printf("a in main: %d\n", x);
}

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

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

在x86体系结构中,通常通过调用机语言指令输入子例程之前,CPU将在调用后立即在堆栈上推送指令的地址

ret_address_N
. . . . . . .
ret_address_3
ret_address_2
ret_address_1
当子例程结束时,返回指令使CPU从堆栈中弹出最近的地址,并通过跳转到该地址重定向执行,从而有效地恢复启动调用的子例程或函数的执行

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

此外,没有什么可以阻止您将自定义数据推送到堆栈上。只要堆栈状态在从子例程返回之前恢复,就有专门的CPU指令,否则当RET指令弹出预期的返回地址时,它将获取垃圾并尝试将执行跳转到它,很可能是撞车。顺便说一句,这也是恶意软件攻击的数量,通过使用有效地址覆盖堆栈,并迫使CPU在执行RET指令时跳转到恶意代码

例如,此堆栈功能可用于存储在子例程内修改的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指令可以在 当它弹出应该跳转到的值时,堆栈顶部会显示正确的内存地址


现在您可以理解为什么不能返回指向局部变量的指针。通过这样做,您将向临时存储在堆栈中的值返回一个地址。您可以取消对指针的引用,当您从将指针返回到局部变量的子例程中立即返回时,可能会看到看起来像有效数据的内容,但这些数据肯定会被覆盖,可能在不久的将来,当程序执行继续调用子例程时。

您不应该这样做,因为它是局部变量,一旦控件退出foo函数,它就会消失。您可以将指向int**之类指针的指针作为输入参数。但更重要的是,你想在注释掉的printf中做什么是不可能的,因为一旦函数返回到前面所说的内容上,a就会消失,你不能从void foo返回一个值,因为它是void,这意味着它不会返回任何值。如果基本语言功能(如声明函数)不清楚,那么我建议您在a的帮助下学习该语言。由于某些原因,我无法在foo中使用return。如果您想学习,请分享这些原因。我猜其中一个原因是函数返回void。另一个原因可能是编译器告诉您返回&A;很可能是一个错误。访问范围外变量不太可能导致崩溃。更有可能的是,看到的值会随着其他函数的调用而改变,并将空间重新用于它们自己的变量。尝试此操作仍然是未定义的行为,即使不太可能,也可能发生崩溃。变量可以声明为静态变量,函数返回指向静态变量的引用或指针。此函数可以用作在堆中分配内存并将地址返回给其分配的内存的函数。不过不太好。
-------------
-------------   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