通过堆栈传递数据

Passing Data through the Stack

我想看看你是否可以通过堆栈传递结构,我设法从另一个 void 函数中的 void 函数获取局部变量。

你们认为这有什么用吗?您是否有可能在两次函数调用之间获得损坏的数据?

这是 C 中的代码(我知道它很脏)

#include <stdio.h>

typedef struct pouet
{
    int a,b,c;
    char d;
    char * e;
}Pouet;

void test1()
{
    Pouet p1;
    p1.a = 1;
    p1.b = 2;
    p1.c = 3;
    p1.d = 'a';
    p1.e = "1234567890";
    printf("Declared struct              : %d %d %d %c \'%s\'\n", p1.a, p1.b, p1.c, p1.d, p1.e);
}

void test2()
{
    Pouet p2;
    printf("Element of struct undeclared : %d %d %d %c \'%s\'\n", p2.a, p2.b, p2.c, p2.d, p2.e);
    p2.a++;
}

int main()
{
    test1();
    test2();
    test2();
    return 0;
}

输出为:

Declared struct : 1 2 3 a '1234567890'

Element of struct undeclared : 1 2 3 a '1234567890'

Element of struct undeclared : 2 2 3 a '1234567890'

当然,您可能会得到损坏的数据;您正在使用 未定义的行为

你的行为是未定义的。

printf("Element of struct undeclared : %d %d %d %c \'%s\'\n", p2.a, p2.b, p2.c, p2.d, p2.e);

变量 p2 的作用域是函数 test2() 的局部范围,一旦退出函数,变量就不再有效。

您正在访问未初始化的变量,这将导致未定义的行为。

不保证在任何时候和所有平台上都能看到您所看到的输出。所以你需要摆脱代码中的未定义行为。

数据可能会或可能不会出现在 test2 中。这完全取决于程序是如何编译的。它在像您这样的玩具示例中比在真实程序中更有可能工作,如果您关闭编译器优化,它更有可能工作。

语言定义说局部变量在函数结束时不复存在。尝试读取您认为存储它的地址可能会或可能会产生结果;它甚至可能使程序崩溃,或使其执行一些完全出乎意料的代码。这是 undefined behavior.

例如,编译器可能决定将一个变量放在一个函数的寄存器中,而不是另一个函数中,从而破坏变量在堆栈上的对齐方式。它甚至可以用一个大结构来做到这一点,将它分成几个寄存器和一些堆栈——只要你不获取结构的地址,它就不需要作为可寻址的内存块存在。编译器可能会在其中一个变量之上编写堆栈金丝雀。这些只是我脑海中的可能性。

C让你看到很多幕后花絮。您在幕后看到的很多内容都可能完全不同于一次 生产 编译或 运行 下一次编译。

了解这里发生的事情作为一种调试技巧很有用,可以了解您在调试器中看到的值可能来自何处。作为一种编程技术,这是无用的,因为您没有让计算机完成任何特定的结果。

仅仅因为这适用于一个编译器并不意味着它适用于所有编译器。如何处理未初始化的变量是未定义的,一台计算机可以在不违反任何规则的情况下很好地将指针初始化为 null 等。 所以不要这样做或依赖它。我实际上已经看到依赖于 mysql 中的功能的代码是一个错误。在更高版本中修复该问题后,该程序停止工作。关于该系统设计者的想法,我将保留给自己。

简而言之,永远不要依赖未定义的功能。如果您有意将它用于特定功能并且您准备好对编译器等进行更新可能会破坏它并且您始终注意这一点它可能是您可以解释和接受的东西。但大多数时候这远不是一个好主意。

与大多数人的意见相反,我认为它在大多数情况下都能奏效(不过你不应该依赖它)。

我们来看看吧。首先调用 test1,它会得到一个新的 堆栈帧 堆栈指针 表示堆栈顶部上升。在该堆栈帧上,除其他外,还保留了结构的内存(正好是 sizeof(struct pouet) 的大小),然后进行了初始化。当 test1 returns 时会发生什么?它的栈帧和你的内存是否被破坏了?

恰恰相反。它留在堆栈上。然而,堆栈指针下降到它下面,回到调用函数中。你看,这是一个非常简单的操作,只是改变堆栈指针的值而已。我怀疑是否有任何技术可以在处置时清除堆栈框架。这样做成本太高了!

然后会发生什么?嗯,你叫test2。它存储在堆栈上的只是 struct pouet 的另一个实例,这意味着它的堆栈帧 很可能 test1 的大小完全相同。这也意味着 test2 将为它自己的变量 Pouet p2 保留先前包含您初始化的 struct pouet 的内存,因为这两个变量应该 最有可能 相对于堆栈帧开头的相同位置。这反过来意味着它将被初始化为相同的值。

但是,这种设置并不可靠。即使抛开对非标准化行为的担忧,它也必然会被一些简单的事情破坏,比如在 test1test2test1 和 [ 之间调用不同的函数=13=] 具有不同大小的栈帧。

此外,您应该考虑编译器优化,这也可能会破坏某些东西。但是,您的功能越相似,它们获得不同优化处理的机会就越小。