2015-02-11 89 views
0

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

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

这里的(我知道它的脏)

#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; 
} 

输出是代码在C:

申报的结构:1 2 3 A '1234567890'

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

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

+0

我忘了说:因为在C语言中,当你在堆栈中声明一个新变量时,它不会将值初始化为0或NULL,所以取值就是堆栈中的值:在这个情况下p1的值。 – 2015-02-11 09:53:54

+1

有人会称这是一个可怕的想法。它过于依赖于C标准中没有出现的实现细节。 – 2015-02-11 09:58:34

+0

尝试添加类似'char dummy [16];'Pouet p2;'上面的内容'并查看它是否仍按预期工作。 – 2015-02-11 10:30:39

回答

1

与大多数人的意见相反,我认为它可以在大多数情况下工作(不是你应该依靠它)。

让我们来看看。首先你打电话test1,它得到一个新的堆栈帧堆栈指针这表示堆栈的顶部上升。在那个栈框架上,除了其他的东西外,你的结构体的内存(正是sizeof(struct pouet)的大小)被保留并初始化。当test1返回时会发生什么?它的堆栈框架和你的记忆是否会被破坏?

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

然后会发生什么?那么,你叫test2。所有它在堆栈上存储的只是struct pouet的另一个实例,这意味着它的堆栈帧将为,最可能的是test1的尺寸完全相同。这也意味着test2将保留之前包含您初始化的struct pouet的内存中的变量Pouet p2,因为两个变量都应该是最可能是相对于堆栈帧的开始位置具有相同的位置。这又意味着它将被初始化为相同的值。

但是,这种设置不是可以依赖的。即使担心非标准化行为,它也必然会被打破,如调用test1test2test1test2的不同大小的堆栈帧之间的不同功能。

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

+1

当你解释到底发生了什么,我验证你的答案 – 2015-02-11 11:29:03

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(),一旦退出该函数的变量不再有效。

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

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

+0

我想知道这个未定义的行为是否可以使用,我不会在任何项目中使用它,这太危险了。 – 2015-02-11 10:54:30

+1

@Jiloko当行为未定义时,如果你问'我试图知道这个未定义的行为是否可以使用?',我会说不会有任何事情发生。'我说不,不。NO。请不要依赖这个 – Gopi 2015-02-11 10:57:15

+0

@Jiloko:那里就C标准而言,严肃与“不太严肃”的UB没有区别。您的程序可能会崩溃或提供不可预知的输出。从理论上讲,它甚至可能会擦掉硬盘,尽管它不太可能发生。有关更多详细信息,请参见[未定义的行为](http://en.wikipedia.org/wiki/Undefined_behavior)。 – 2015-02-11 11:13:49

0

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

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

例如,编译器可能会决定将一个变量放在寄存器中的一个函数中,而不是另一个函数中,从而打破堆栈上变量的对齐。它甚至可以用一个大的结构来实现,将它分成几个寄存器和一些堆栈 - 只要你不把结构的地址作为一个可寻址的内存块存在。编译器可能会在其中一个变量的顶部写入一个堆栈金丝雀。这些只是我头顶的可能性。

C让你看到很多幕后。很多你在幕后看到的内容可以完全从 制作 编译或运行到下一个。

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

0

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

总之,永远不要依赖没有定义的功能。如果您故意将其用于特定功能,并且您已准备好对编译器等进行更新可能会破坏它,并且始终注意这一点,则可能是您可以解释和解决的问题。但大多数情况下,这远非一个好主意。