假设我有一个声明并初始化两个局部变量的函数 - 默认情况下它具有存储持续时间auto
。然后,该函数调用第二个函数,它传递这两个局部变量的地址。第二个功能可以安全地使用这些指针吗?
一个简单的程序化示例,以补充该描述:
#include <stdio.h>
int adder(int *a, int *b)
{
return *a + *b;
}
int main()
{
auto int a = 5; // `auto' is redundant; included for clarity
auto int b = 3;
// adder() gets the addresses of two auto variables! is this an issue?
int result = adder(&a, &b);
printf("5 + 3 = %d\n", result);
return 0;
}
此程序按预期工作,打印5 + 3 = 8
。
通常,当我对C有疑问时,我会转向标准,这也不例外。具体来说,我检查了ISO/IEC 9899,§6.2.4。它在那里说:
4 声明标识符没有链接且没有链接的对象 存储类说明符
static
具有自动存储持续时间。5 对于没有可变长度数组类型的对象, 它的生命周期从进入到它所在的区块延伸 关联,直到该块的执行以任何方式结束。 (进入 封闭块或调用函数暂停,但不结束, 执行当前块。)如果以递归方式输入块, 每次都会创建一个新的对象实例。初始值 对象是不确定的。如果指定了初始化 对象,每次到达声明时执行 块的执行;否则,该值变得不确定 每次达到声明。
阅读本文,我提出以下几点:
变量a
和b
的存储时间为auto
,我已使用auto
关键字对其进行了明确说明。
在上面的部分引用中,调用adder()
函数对应于第5节中的括号。也就是说,输入adder()
函数“暂停但不结束”当前块的执行(main()
)。
由于main()
块未以任何方式“结束[编辑],”a
和b
的存储空间得到保证。因此,即使在&a
内,使用地址&b
和adder()
访问它们也应该是安全的。
那么,我的问题是:我在这方面是否正确?或者我只是“幸运”,并访问记忆位置,偶然,没有被覆盖?
P.S。我无法通过Google或SO的搜索找到这个问题的确切答案。如果可以,请将此标记为重复,我将删除它。
答案 0 :(得分:8)
是的,这是安全的,基本上你的假设是正确的。自动对象的生命周期来自声明它的块中的条目,直到块终止。
(C99,6.2.4p5)“对于这样的对象[...],它的生命周期从入口延伸到与之关联的块,直到该块的执行以任何方式结束。
答案 1 :(得分:7)
您的推理对于您的特定函数调用链是正确的,并且您已阅读并引用了标准的相关部分。这是对局部变量指针的完美有效使用。
如果函数将指针值存储在生命周期长于其自身调用的结构中,则必须要小心。考虑两个函数foo()
和bar()
:
int *g_ptr;
void bar (int *p) {
g_ptr = p;
}
void foo () {
int x = 10;
bar(&x);
}
int main () {
foo ();
/* ...do something with g_ptr? */
return 0;
}
在这种情况下,变量x
的生命周期以foo()
返回结束。但是,指向x
的指针已存储在g_ptr
bar()
中。在这种情况下,foo()
将指向其局部变量x
的指针传递给bar()
是错误的。
这意味着为了知道将指向局部变量的指针传递给函数是否有效,你必须知道该函数将对它做什么。
答案 2 :(得分:1)
这些变量在堆栈中分配。只要您不从声明它们的函数返回,它们仍然有效。
答案 3 :(得分:0)
由于我还没有被允许发表评论,我宁愿写另一个答案作为jxh's answer above的修正案:
有关类似问题,请参阅此处my elaborate answer。这包含一个真实世界的例子,即使它遵循所有的c语言规则,被调用函数中的别名会使你的代码中断。
即使它在C语言中是合法的,我认为在函数调用中传递指向自动变量的指针是有害的。你永远不会知道(并且通常你不想知道)被调用的函数对传递的值的作用。当被调用的函数建立别名时,你会遇到大麻烦。