被引用的复合数组字面量的生命周期

5

我最近才了解到在C语言中,我可以使用复合字面数组的引用,发现这很有用,但我不太明白它是如何工作的。

例如,假设我使用该功能来避免为某个套接字接口函数调用声明变量,而我并不关心返回名称的长度,就像这样:

int sockfamily(int fd)
{
    struct sockaddr_storage ss;

    getpeername(fd, (struct sockaddr *)&ss, (socklen_t [1]){sizeof(ss)});
    return(ss.ss_family);
} 

显然,为了将指向它的指针传递给getpeernamesizeof(ss)实际上需要存储在堆栈中,并且必须为此目的分配和保留堆栈上的空间,但是这个分配的生命周期是多久?我能信任它保持分配的时间有多长?
查看GCC的汇编输出,如果我将对getpeername的调用放在循环中,分配不会在循环的多次迭代中幸存,但是还有什么其他条件可能导致它停止存在呢?

关于结构体:https://dev59.com/8mEh5IYBdhLWcg3w6HAG#31178926 - Ciro Santilli OurBigBook.com
1个回答

12

在函数内定义的复合字面值与包含它的块具有自动生存期(即与在同一级别声明的变量具有相同的生存期)。这在标准中指定,第6.5.2.5段第5句。

int f() {
    for (int i = 0; i < 10; ++i) {
        int *j = (int []){i};  // storage duration of loop body
    }
} 
这实际上意味着复合字面量等同于在同一作用域中声明和初始化的变量:
int f() {
    for (int i = 0; i < 10; ++i) {
        int __unnamed[] = {i};
        int *j = __unnamed;
    }
} 

如果将复合文本字面值传递到其指针可能会超过其生命周期的任何地方,请小心处理:

int f() {
    int *p;
    if (1) {
        p = (int []){0, 1, 2};
        assert(p[0] == 0);
    }
    // *p is undefined
}

谢谢您提供标准的参考!这非常有帮助。 - Dolda2000
如果有类似于void blah(void) { struct foo *f; ... foo = somefunc(); if (foo==NULL) f=(struct foo*){data}; ...}这样的代码,是否有一种简洁的方式来让if控制多个语句? - supercat
如果我理解正确的话,您可以尝试类似于struct foo *fallback[] = {data}; if (f==NULL) f=fallback;这样的东西-但是那么您就不需要一个复合字面量了。 - ecatmur
@ecatmur:这就是我的观点。我认为如果没有引入语义上的困难,就无法使复合文字作为lvalues行为,除非在它们可以(并且是)const static的情况下。 我想知道,如果标准被更改,使复合文字成为“const static” lvalue,如果所有成员都是常量,则会破坏多少现实世界的代码,并且否则可以视为rvalue [编译器被允许具有后者产生临时lvalues的情况,但此类使用将被弃用]? - supercat
对于类型不包含VLA但值是非常量的情况,我可以看到将复合字面量作为lvalue并将其(在生命周期方面)提升到函数范围的某些有用性,但我认为让它们成为更短寿命的lvalue似乎是最糟糕的解决方案。能够在宏中定义const static项(除字符串外)将代表不可获得的语义,并且对于编译器来说不应该很难处理(处理基本上相当于字符串文字)。 - supercat
@ecatmur:我怀疑gcc的语句内表达式扩展没有被采纳为标准的原因是,一些编译器可能会在表达式上下文中创建自动持续时间的lvalue时遇到问题;定义一种标准方法,让能够在表达式中间创建自动lvalue的编译器允许程序员在那里定义命名变量似乎更有用,而不是让复合字面量行为像匿名本地变量。 - supercat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接