【问题标题】:Passing Data through the Stack通过堆栈传递数据
【发布时间】:2015-02-11 09:36:57
【问题描述】:

我想看看你是否可以通过堆栈传递结构,并且我设法从另一个 void 函数中的 void 函数获取本地 var。

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

这是 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;
}

输出是:

声明的结构:1 2 3 a '1234567890'

结构元素未声明:1 2 3 a '1234567890'

结构元素未声明:2 2 3 a '1234567890'

【问题讨论】:

  • 我忘了说:它之所以有效,是因为在 C 中,当您在堆栈中声明一个新变量时,它不会将值初始化为 0 或 NULL,因此取的值就是其中的值堆栈:在这种情况下是 p1 的值。
  • 有些人会认为这是一个糟糕的主意。它过于依赖未出现在 C 标准中的实现细节。
  • 尝试在Pouet p2; 上方添加类似char dummy[16]; 的内容,看看它是否仍能按预期工作。
  • 没有段错误,如果你在 test2() 的末尾和 Pouet p2 之间触摸堆栈;它不起作用。我真的不认为你可以在真正的程序中使用它,但它是一个很酷的 hack imo

标签: stack c


【解决方案1】:

与大多数人的意见相反,我认为它在大多数情况下都可以解决(但不是你应该依赖它)。

让我们检查一下。首先你调用test1,它会得到一个新的栈帧栈指针,它表示栈顶向上。在该堆栈帧上,除其他事项外,您的结构的内存(正好是sizeof(struct pouet) 的大小)被保留然后初始化。当test1 返回时会发生什么?它的堆栈帧和你的内存会被破坏吗?

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

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

但是,此设置不可依赖。即使不考虑非标准化行为,它也一定会被一些简单的事情打破,比如在调用 test1test2test1test2 之间调用不同的函数,堆栈帧为不同的尺寸。

此外,您应该考虑编译器优化,这也可能会破坏事情。但是,您的函数越相似,它们接受不同优化处理的可能性就越小。

【讨论】:

  • 我验证了您的回答,因为您准确地解释了正在发生的事情
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    你所拥有的是未定义的行为。

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

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

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

    您所看到的输出并不能保证始终在所有平台上。所以你需要摆脱代码中未定义的行为。

    【讨论】:

    • 我想知道是否可以使用这种未定义的行为,我不会在任何项目中使用它,这太危险了。
    • @Jiloko 当行为未定义时,任何事情都可能发生,所以如果你问I'm trying to know if this undefined behavior can be used? 我说不不不.. 请不要依赖这个
    • @Jiloko:就 C 标准而言,严重和“不太严重”的 UB 之间没有区别。您的程序可能会崩溃或提供不可预测的输出。从理论上讲,它甚至可能会擦除您的硬盘,尽管这不太可能发生。有关详细信息,请参阅Undefined behavior
    【解决方案4】:

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

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

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

    C 让您看到很多幕后的东西。你在幕后看到的很多东西都可以从一个生产编译或运行到下一个完全改变。

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

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-04-21
        • 1970-01-01
        • 2016-09-27
        • 2021-04-17
        相关资源
        最近更新 更多