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'。
答案 0 :(得分:6)
由于赋值运算符的左侧和右侧的求值之间没有序列点,因此未指定是先评估*ptr
还是func(&ptr)
。因此,不能保证允许评估*ptr
,并且程序具有未定义的行为。
答案 1 :(得分:5)
我相信这是不确定的行为。该标准没有规定何时评估指配的LHS与RHS相比。如果在调用函数后计算*ptr
,则将取消引用空指针;如果在调用函数之前对其进行求值,那么你就会得到理智的行为。
代码完全没有声誉。不要在实际代码中尝试使用它或任何类似的东西。
请注意,在评估其参数之后,在调用函数之前会有一个序列点;在函数返回之前还有一个序列点。因此,有一些与函数参数及其返回值的评估相关的序列点,但是......这在这个上下文中是至关重要的...它仍然没有告诉你在{之前或之后是否评估*ptr
该函数被调用。要么是可能的;两者都是正确的;代码取决于发生的情况,这使得它依赖于未定义的行为。
答案 2 :(得分:5)
该语言不保证您在
中使用右侧子表达式func(&ptr)
*ptr = func(&ptr);
首先评估,然后评估左侧子表达式*ptr
(这显然是您预期发生的事情)。在调用func
之前,可以先合法地评估左侧,。这正是您的情况中发生的事情:*ptr
在通话之前得到了评估,当ptr
仍然指向x
时。之后,分配目的地最终确定(即,已知代码将分配给x
)。一旦发生,更改ptr
不再更改分配目的地。
因此,由于未指定的评估顺序,您的代码的直接行为是未指定。但是,一个可能的评估计划通过导致空指针取消引用而导致 undefined 行为。这意味着在一般情况下,行为是 undefined 。
如果我不得不用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开始。
答案 3 :(得分:4)
赋值运算符为not a sequence point。因此,无保证,首先评估哪一方。所以,这是未指明的行为。
在其中一种情况下(取消引用NULLPTR),它可能会出现未定义的行为。
在连续的“序列点”之间,对象的值可以是 仅通过表达式修改一次。
您可以在C here中看到已定义序列点的列表。
答案 4 :(得分:1)
虽然函数的调用是一个序列点,但这与参数的评估(在调用之前)有关,而不是函数的副作用(调用本身)。