我正在尝试编写一个简单的程序来展示如何在堆栈上间接操作变量。在下面的代码中,一切都按计划工作:即使a的地址被传入,我也可以间接地改变c的值。但是,如果我删除最后一行代码(或最后三行代码),则不再适用。这些行是否以某种方式迫使编译器将我的3个变量按顺序放入堆栈中?我的期望是情况总是如此。
#include <iostream>
using namespace std;
void someFunction(int* intPtr)
{
// write some code to break main's critical output
int* cptr = intPtr - 2;
*cptr = 0;
}
int main()
{
int a = 1;
int b = 2;
int c = 3;
someFunction(&a);
cout << a << endl;
cout << b << endl;
cout << "Critical value is (must be 3): " << c << endl;
cout << &a << endl;
cout << &b << endl;
cout << &c << endl; //when commented out, critical value is 3
}
答案 0 :(得分:9)
您的代码会导致未定义的行为。您无法将指针传递给int
,然后只需从中减去任意数量,并指望它指向有意义的内容。编译器可以按照自己喜欢的顺序将a
,b
和c
放在任何地方。他们之间没有任何保证的关系,所以你不能假设someFunction
会做任何有意义的事情。
答案 1 :(得分:4)
编译器可以将它们放置在当前堆栈帧中的任何位置和任何顺序,如果不使用它甚至可以优化它们。只需让编译器按照您想要的方式执行操作,使用数组,其中指针算法是安全的:
int main()
{
int myVars[3] = {1,2,3};
//In C++, one could use immutable (const) references for convenience,
//which should be optimized/eliminated pretty well.
//But I would never ever use them for pointer arithmetic.
int& const a = myVars[0];
int& const b = myVars[1];
int& const c = myVars[2];
}
答案 2 :(得分:2)
你做的是未定义的行为,所以任何事情都可能发生。但是可能会发生的是,当你没有通过注释c
来获取cout << &c << endl;
的地址时,编译器可以优化avay变量c
。然后,它会将cout << c
替换为cout << 3
。
答案 3 :(得分:1)
许多人已经回答,自触发undefined behavior以来,您的代码错误,另请参阅this answer类似的问题。
在原始代码中,优化编译器可以将a
,b
和c
放在寄存器中,重叠其堆栈位置等。 ..
然而,有合理的理由想知道堆栈上局部变量的位置(精确的垃圾收集,内省和反射......)。
然后,正确的方法是将这些变量打包在struct
(或class
)中,并以某种方式访问该结构(例如,将它们链接到列表中等)
所以你的代码可以从
开始void fun (void)
{
struct {
int a;
int b;
int c;
} _frame;
#define a _frame.a
#define b _frame.b
#define c _frame.c
do_something_with(&_frame); // e.g. link it
您也可以使用数组成员(甚至可能使用flexible or zero-length数组进行内务处理例程)和#define a _frame.v[0]
等...
实际上,优秀的优化编译器可以优化原始代码。
可能_frame
的类型可能在fun
函数之外,并且您将为_frame
生成用于检查或垃圾收集的内务处理函数。功能
不要忘记在例程结束时取消链接框架。使用适当的构造函数和析构函数使框架成为对象肯定有帮助。构造函数将链接框架,析构函数将取消链接。
对于使用此类技术的两个示例(因为需要precise garbage collector),请参阅我的qish garbage collector和MELT的(生成的C ++)代码(特定于域的语言)延伸GCC)。另请参阅Chicken Scheme或Ocaml runtime conventions(及其<caml/memory.h>
标题的(生成的)C代码。
在实践中,这种方法对生成的 C或C ++代码更受欢迎(正因为您还将生成内务代码)。如果手动编写它们,请至少考虑花哨的宏(和模板)来帮助您。参见例如gcc/melt-runtime.h
我实际上认为这是C中的缺陷。应该有一些语言特性(和编译器实现)来内省堆栈并对其进行(可移植)回溯。