【问题标题】:Compound literal lifetime and if blocks复合文字生命周期和 if 块
【发布时间】:2016-01-19 15:27:09
【问题描述】:

这是一个理论问题,我知道如何明确地做到这一点,但我很好奇并深入研究了标准,我需要第二双标准律师的眼睛。

让我们从两个结构体和一个初始化函数开始:

struct foo {
    int a;
};
struct bar {
    struct foo *f;
};
struct bar *
init_bar(struct foo *f)
{
    struct bar *b = malloc(sizeof *b);
    if (!b)
        return NULL;
    b->f = f;
    return b;
}

我们现在有一个不检查返回值的马虎程序员:

void
x(void)
{
    struct bar *b;

    b = init_bar(&((struct foo){ .a = 42 }));
    b->f->a++;
    free(b);
}

从我对标准的阅读来看,除了可能取消引用 NULL 指针之外,这里没有任何问题。通过struct bar 中的指针修改struct foo 应该是合法的,因为发送到init_bar 的复合文字的生命周期是包含它的块,即整个函数x

但是现在我们有了一个更细心的程序员:

void
y(void)
{
    struct bar *b;

    if ((b = init_bar(&((struct foo){ .a = 42 }))) == NULL)
        err(1, "couldn't allocate b");
    b->f->a++;
    free(b);
}

代码做同样的事情,对吧?所以它也应该工作。但更仔细地阅读 C11 标准让我相信这会导致未定义的行为。 (强调我的引号)

6.5.2.5 复合字面量

5 复合文字的值是由 初始化列表。如果复合文字出现在函数体之外,则该对象 具有静态存储持续时间;否则,它具有与关联的自动存储持续时间 封闭块

6.8.4 选择语句

3 选择语句是一个块,其范围是其范围的严格子集 封闭块。每个关联的子语句也是一个块,其范围是严格的 选择语句范围的子集。

我读对了吗? if 是一个块这一事实是否意味着复合文字的生命周期只是 if 语句?

(如果有人想知道这个人为的例子是从哪里来的,在实际代码中 init_bar 实际上是 pthread_create 并且线程在函数返回之前加入,但我不想通过涉及线程来混淆水域)。

【问题讨论】:

标签: c


【解决方案1】:

您引用的标准的第二部分(6.8.4 选择语句) 说明了这一点。在代码中:

{//scope 1

    if( ... )//scope 2
    {

    }//end scope 2

}//end scope 1

范围 2 完全在范围 1 内。请注意,在这种情况下,选择语句是整个 if 语句,而不仅仅是括号:

if( ... ){ ... }

该语句中定义的任何内容都在范围 2 中。因此,如第三个示例所示,在范围 2 中声明的复合文字的生命周期在结束 if 括号 (end scope 2),因此如果函数返回非 NULL(如果 err() 没有终止程序,则返回 NULL),该示例将导致未定义的行为。

(请注意,我在 if 语句中使用了括号,尽管第三个示例没有使用它们。示例的那部分等效于这个(6.8.2 复合语句):

if ((b = init_bar(&((struct foo){ .a = 42 }))) == NULL)
{
    err(1, "couldn't allocate b");
}

【讨论】:

  • 是的,我想就是这么简单。不过,这相当令人惊讶。一个我以前从未想过的小问题。并不是说它经常发生,但我一直想用这种方式打电话给pthread_create 几次。
  • 所以这是另一种情况,代码必须使用 &&?: 之类的运算符来执行“通常”应该使用 if 完成但不能 [例如将上述修复为(b = init_bar(&((struct foo) { .a = 42 }))) && err(1, "couldn't allocate b");]?或者不要为 C11 设计错误的复合字面量而烦恼?
  • @supercat 这可以通过 if 语句很好地完成,并且责备复合文字正在将责备从程序员身上移开。在我看来,if 语句中的调用函数是代码异味。正确的版本是struct bar* b = b = init_bar( &(struct foo){ .a = 42 } ); if( b ){...
  • @2501:我使用了那个特定示例,因为这就是上面的答案所使用的,但更相关的用途是代码需要使用由指针标识的对象(如果非空)的情况,或者如果它为空,则创建一个默认对象。如果使用复合文字的代码需要在外部范围内说struct foo DefaultObject;,然后说if (!p) { DefaultObject = (struct foo) { .a = computedValue }; p = &DefaultObject;},这与if (!p) { DefaultObject.a = computedValue; p = &DefaultObject;}相比并没有真正提供太多好处,并且在...
  • ...默认对象中的所有内容都是常量,这不如让DefaultObject 是静态的。我可以看到有一种语法的用处,它可以保证这样一个对象的生命周期将持续到执行离开包含语法的函数或复合文字在相同的语法上下文中重新执行,以先发生者为准,我可以看到还具有允许对象的生命周期类似于函数返回的结构的语法的有用性。然而,仅扩展到封闭块会使事情变得脆弱。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-03
相关资源
最近更新 更多