通过堆栈传递数据

时间:2015-02-11 09:36:57

标签: stack c

我想看看你是否可以通过堆栈传递struct,我设法从另一个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;
}

输出是:

  

声明结构:1 2 3 a&#39; 1234567890&#39;

     

未声明的结构元素:1 2 3 a&#39; 1234567890&#39;

     

未声明的结构元素:2 2 3 a&#39; 1234567890&#39;

5 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:1)

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

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

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

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

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

答案 2 :(得分:1)

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

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

恰恰相反。它停留在堆栈上。但是,堆栈指针在它之下,返回到调用函数。你看,这是一个非常简单的操作,它只是改变堆栈指针的值。我怀疑是否有任何技术可以在处理时清除堆叠框架。要做的事情实在太费钱了!

那会怎么样?好吧,你打电话给test2。存储在堆栈中的所有内容只是struct pouet的另一个实例,这意味着它的堆栈框架最可能test1的大小完全相同。这也意味着test2将保留以前包含已初始化struct pouet的内存以用于其自己的变量Pouet p2,因为这两个变量很可能具有相同的位置相对于堆栈帧的开头。这反过来意味着它将被初始化为相同的值。

但是,这种设置不是必须依赖的。即使担心不支持非标准化行为,也必然会被调用test1test2test1和{test2之间调用不同函数这样简单的事情打破。 {1}}具有不同大小的堆栈帧。

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

答案 3 :(得分:0)

数据可能会显示在test2中,也可能不会显示在{{1}}中。这取决于程序的编译方式。它更像是在你的玩具示例中工作,而不是在真实程序中工作,如果你关闭编译器优化,它更有可能工作。

语言定义表示局部变量在函数末尾不再存在。试图读取您认为存储的地址可能会或可能产生结果;它甚至可以使程序崩溃,或者让它执行一些完全意外的代码。这是undefined behavior

例如,编译器可能决定将变量放在一个函数中的寄存器中,而不是放在另一个函数中,从而打破了堆栈上变量的对齐。它甚至可以用一个大结构,将它分成几个寄存器和一些堆栈 - 只要你不采用结构的地址就不需要存在作为可寻址的内存块。编译器可能会在其中一个变量之上编写堆栈canary。这些只是我头脑中的可能性。

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

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

答案 4 :(得分:0)

仅仅因为这适用于一个编译器并不意味着它适用于所有人。如何处理未初始化的变量是未定义的,并且一台计算机可以在不破坏任何规则的情况下初始化指向null等的指针。 所以不要这样做或依赖它。我实际上已经看到了依赖于mysql中的功能的代码,这是一个bug。当在以后的版本中修复时,程序停止工作。我对那个系统的设计师的看法我会留给自己。

简而言之,永远不要依赖未定义的功能。如果您故意将它用于特定功能,并且您已准备好对编译器等进行更新可能会破坏它并且您始终密切关注它,这可能是您可以解释和使用的内容。但大部分时间这都不是一个好主意。