14

假设我有一个声明和初始化两个局部变量的函数——默认情况下它们具有 storage duration 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 对于这种不具有可变长度数组类型的对象,其生命周期从进入与其关联的块开始,直到该块的执行以任何方式结束。(进入封闭的块或调用函数会暂停,但不会结束当前块的执行。)如果递归地进入块,则每次都会创建对象的新实例。对象的初始值是不确定的。如果为对象指定了初始化,则在执行块时每次到达声明时都会执行该初始化;否则,每次达到声明时,该值变得不确定。

读到这里,我推理以下几点:

  1. 变量a并且b有 storage duration ,我已经使用关键字auto明确说明了这一点。auto

  2. 调用adder()函数对应于第 5 节中的括号,在上面的部分引用中。也就是说,输入adder()函数“暂停,但不结束”当前块(即main())的执行。

  3. 由于该main()块不是“以任何方式结束[ed]”,因此可以保证存储a和。b因此,使用地址访问它们&a&b甚至在内部adder(),应该是安全的。

那么,我的问题是:我在这方面是否正确?还是我只是变得“幸运”并访问了偶然未被覆盖的内存位置?


PS我无法通过谷歌或SO的搜索找到这个问题的确切答案。如果可以,请将其标记为重复,我将其删除。

4

4 回答 4

8

是的,它是安全的,基本上你的假设是正确的。自动对象的生命周期是从块中声明它的条目开始,直到块终止。

(C99, 6.2.4p5) “对于这样的对象 [...],它的生命周期从进入与其关联的块开始,直到该块的执行以任何方式结束。

于 2013-07-22T22:57:07.227 回答
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_ptrby中bar()。在这种情况下,将foo()指向其局部变量的指针传递xbar().

这意味着为了知道将指向局部变量的指针传递给函数是否有效,您必须知道该函数将如何处理它。

于 2013-07-22T23:01:23.843 回答
1

这些变量在堆栈中分配。只要您不从声明它们的函数返回,它们就保持有效。

于 2013-07-22T22:57:54.893 回答
0

由于我还不允许发表评论,我宁愿写另一个答案作为对上面 jxh答案的修正:

对于类似的问题,请在此处查看我的详细回答。这包含一个真实世界的示例,其中被调用函数中的别名会使您的代码中断,即使它遵循所有 c 语言规则。

尽管在 C 语言中是合法的,但我认为在函数调用中传递指向自动变量的指针是有害的。您永远不知道(通常您也不想知道)被调用的函数对传递的值究竟做了什么。当被调用的函数建立别名时,你会遇到大麻烦。

于 2016-02-10T08:04:18.817 回答