hel*_*ell 8 c pointers variable-assignment undefined-behavior
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;
}
Run Code Online (Sandbox Code Playgroud)
这是一个未定义行为的例子还是与序列点有关?为什么行:
*ptr = func(&ptr);
Run Code Online (Sandbox Code Playgroud)
表现不像:
*NULL = 1234;
Run Code Online (Sandbox Code Playgroud)
编辑:我忘了提到我用gcc 4.7获得输出'1234'和'nil'.
我相信这是不明确的行为.该标准没有规定何时评估指配的LHS与RHS相比.如果*ptr在调用函数后进行求值,则将取消引用空指针; 如果在调用函数之前对其进行求值,那么你就会得到理智的行为.
该代码完全没有声誉.不要在实际代码中尝试使用它或任何类似的东西.
请注意,在评估其参数之后,在调用函数之前有一个序列点; 在函数返回之前还有一个序列点.因此,存在与函数参数及其返回值的评估相关的序列点,但是......这在此上下文中是至关重要的......它仍然不会告诉您*ptr在调用函数之前或之后是否进行了求值.要么是可能的; 两者都是正确的; 代码取决于发生的情况,这使得它依赖于未定义的行为.
该语言不保证您在右侧子表达式func(&ptr)中
*ptr = func(&ptr);
Run Code Online (Sandbox Code Playgroud)
首先评估,然后评估左侧子表达式*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
Run Code Online (Sandbox Code Playgroud)
(尽管C语言没有引用,但在内部它使用相同的"运行时绑定左值"概念来存储赋值左侧的评估结果.)语言规范允许足够的自由来制作步骤1a和1b以任何顺序发生.你期望1b首先发生,而你的编译器决定从1a开始.