在C11中是否未定义修改函数调用的结果,或在下一个序列点之后访问它?

时间:2015-06-30 03:04:20

标签: c c99 c11

C99§6.5.2.2p5那里有一个小小的宝石,我为了强调这个问题而加粗:

  

如果表示被调用函数的表达式具有返回对象类型的函数的类型指针,则函数调用表达式与该对象类型具有相同的类型,并且具有在6.8.6.4中指定的值。否则,函数调用的类型为void。 如果尝试修改函数调用的结果或在下一个序列点之后访问它,则行为未定义。

这使我们可以返回structs,例如:

struct foo { int foo;
             char bar[2]; };

struct foo get_foo() {
    struct foo return_value = { .foo = 42,
                                .bar = "x" };
    return return_value;
}

...并在调用者中的其他位置指定返回值,例如:

int main(void) {
    struct foo bar = get_foo(); /* Well defined because the return value
                                 * is copied -before- the sequence point
                                 * that terminates its storage duration */
    printf("%s\n", bar.bar);
    printf("%d\n", get_foo().foo); /* Again, well defined because the access
                                    * occurs before the next sequence point
                                    * (the function call). */
}

...虽然呈现如下无效的示例:

int main(void) {
    printf("%s\n", get_foo().bar); /* UB because there's a sequence point
                                    * between the evaluation of the sub-
                                    * expression `get_foo().bar` and the
                                    * entrace to the function `printf` */
    get_foo().bar[0]++; /* UB because an attempt is made to modify the
                         * result of a function call */
}

-

然而,

C11§6.5.2.2p5基本上是相同的段落,但没有粗体文​​字。

  

如果表示被调用函数的表达式具有返回对象类型的函数的类型指针,则函数调用表达式与该对象类型具有相同的类型,并且具有在6.8.6.4中指定的值。否则,函数调用的类型为void。

上面那些C99中未定义行为的例子在C11中是否仍未定义?如果是这样,哪些段落使它们无效?如果没有,我收集必须延长自动值/对象返回的存储持续时间;标准的哪一部分规定了存储持续时间的延长?

1 个答案:

答案 0 :(得分:4)

  

上面那些C99中未定义行为的例子在C11中仍未定义吗?

上面提出的定义明确的例子仍然有很好的定义。

此对象的临时生命周期“在包含完整表达式或声明符的评估结束时结束”,因此此前未定义的示例现已明确定义:

printf("%s\n", get_foo().bar);

此示例仍然是未定义的行为,因为尝试修改具有临时生存期的对象:

get_foo().bar[0]++;
  

如果是这样,哪些段落使它们无效?如果没有,我收集必须延长自动值/对象返回的存储持续时间;标准的哪一部分规定了存储持续时间的延长?

正如Jens Gustedt在评论中所指出的那样,C11§6.2.4p8似乎传达了与C99§6.5.2.2p5所包含的句子略有不同的含义,其中C11§6.5.2.2p5被省略:

  

具有结构或联合类型的非左值表达式,其中结构或联合包含具有数组类型的成员(包括,递归地,所有包含的结构和联合的成员)是指具有自动存储持续时间和临时生存期的对象。 36)它的生命周期在评估表达式时开始,其初始值是表达式的值。当包含完整表达式或完整声明符的评估结束时,它的生命周期结束。任何使用临时生命周期修改对象的尝试都会导致未定义的行为。

     

36)访问数组成员时隐式采用此类对象的地址。

似乎进行了一点重组; C99中的“存储持续时间扩展”句子被更改并从“函数调用”部分移动到“存储持续时间”部分,它更适合。

唯一剩下的问题是函数调用的结果是否被视为左值。对于每个产生左值的运算符,似乎都明确提到运算符产生左值。例如,C11§6.5.3.2p6表示一元*运算符生成一个左值,为对象提供操作数点。

然而,函数调用运算符没有说明产生左值,因此我们必须假设它不会产生左值。如果这还不够好,那么请考虑C11§6.5.2.3p3和p7,它们会说:

  

后缀表达式后跟.运算符,标识符指定结构或联合对象的成员。该值是指定成员的值, 95),如果第一个表达式是左值,则为左值。

     

如果f是返回结构或联合的函数,并且x是该结构或联合的成员,则f().x是有效的后缀表达式,但不是左值。< / p>

我们还可以从这两段中推断出函数的结果不是左值,因此符合C11§6.2.4p8的标准(上面引用)。

脚注95很有趣,但与现有讨论相关:

  

95)如果用于读取union对象内容的成员与上次使用的成员不同   在对象中存储一个值,该值的对象表示的相应部分被重新解释   作为6.2.6中描述的新类型中的对象表示(有时称为''类型的过程)   双关语””)。这可能是陷阱表示。