8
int func(int **a)
{
    *a = NULL;
    return 1234;
}

int main()
{
    int x = 0, *ptr = &x;
    *ptr = func(&ptr);      // <-???
    printf("%d\n", x);      // print '1234'
    printf("%p\n", ptr);    // print 'nil'

    return 0;
}

这是未定义行为的示例还是与序列点有关?为什么行:

*ptr = func(&ptr);

不像:

*NULL = 1234;

编辑:我忘了提到我用 gcc 4.7 得到了输出 '1234' 和 'nil'。

4

5 回答 5

6

由于赋值运算符的左右两侧的求值之间没有序列点,所以没有指定是否先求值*ptrfunc(&ptr)先求值。因此,不能保证*ptr允许评估 ,并且程序具有未定义的行为。

于 2013-06-14T19:12:41.823 回答
5

我相信这是未定义的行为。该标准没有规定何时评估作业的 LHS 与 RHS 相比。如果*ptr在调用函数后进行评估,您将取消引用空指针;如果在调用函数之前对其进行评估,那么您将获得理智的行为。

该代码是完全声名狼藉的。不要尝试在实际代码中使用它或任何类似的东西。

请注意,在函数被调用之前,在其参数被评估之后,有一个序列点;在函数返回之前还有一个序列点。因此,存在与函数参数及其返回值的评估相关的序列点,但是......这在这种情况下至关重要......它仍然不能告诉您*ptr是在调用函数之前还是之后评估。两者皆有可能;两者都是正确的;代码取决于发生了什么,这使得它依赖于未定义的行为。

于 2013-06-14T19:13:40.307 回答
5

func(&ptr)该语言不保证您的右侧子表达式

*ptr = func(&ptr);

首先评估,然后评估左侧的子表达式*ptr(这显然是您期望发生的)。在调用之前,可以合法地首先评估左侧func。这正是您的案例中发生的情况:*ptr在通话之前进行了评估,当时ptr仍然指向x. 之后,分配目的地最终确定(即已知代码将分配给x)。一旦发生,更改ptr不再更改分配目标。

因此,由于未指定评估顺序,您的代码的即时行为未指定。但是,一种可能的评估计划通过导致空指针取消引用而导致未定义的行为。这意味着在一般情况下行为是未定义的。

如果我必须用 C++ 语言对这段代码的行为进行建模,我会说这种情况下的评估过程可以分为这些基本步骤

1a. int &lhs = *ptr;      // evaluate the left-hand side
1b. int rhs = func(&ptr); // evaluate the right-hand side
2.  lhs = rhs;            // perform the actual assignment

(尽管 C 语言没有引用,但在内部它使用“运行时绑定左值”的相同概念来存储赋值左侧的评估结果。)语言规范允许足够的自由来进行步骤 1a 和1b 以任何顺序发生。您希望首先出现 1b,而您的编译器决定从 1a 开始。

于 2013-06-14T19:15:50.593 回答
4

赋值运算符不是序列点。因此,不能保证先评估哪一方。因此,这是未指定的行为。

在其中一种情况下(取消引用 NULLPTR),它可能表现出未定义的行为。

在连续的“序列点”之间,一个对象的值只能被一个表达式修改一次。

您可以在此处查看C 中定义的序列点列表。

于 2013-06-14T19:14:27.857 回答
1

虽然函数的调用是一个序列点,但这与参数的评估有关(在调用之前),而不是函数的副作用(调用本身)。

于 2013-06-14T19:15:53.277 回答