改变简单的c代码:
#include <stdio.h>
int main() {
int arr[] = {1, 2, 3, 4, 5};
int *ptr = arr;
printf("%d, %d\n", *ptr, *(++ptr));
return 0;
}
使用gcc 4.8.2编译,结果:
2, 2
用clang 3.4编译,结果:
1, 2
为什么会这样?
答案 0 :(得分:7)
调用函数时使用的逗号不带sequence point。
因此,此代码*ptr, *(++ptr)
调用未定义的行为,因为它尝试在序列点之间访问ptr两次,用于其他目的,而不是确定要分配给ptr的值。
这由C11 6.5 / 2定义,在下面的乱码文本中:
如果标量对象的副作用相对于其中任何一个都没有排序 对同一个标量对象或值有不同的副作用 使用相同标量对象的值进行计算,行为是 未定义。如果有多个允许的排序 表达式的子表达式,如果这样的话,行为是不确定的 任何顺序都会出现无序的副作用。
在我们的例子中,副作用(用*(++ptr)
中的++更改ptr)与同一对象(*ptr
)的值计算无关。
由于它是未定义的行为,任何事情都可能发生。由于您的程序执行“某些操作”,因此它的行为符合预期(或者更确切地说,“意外”)。
此外,未指定函数参数的评估顺序,因此您无法知道函数调用中的第一个参数或第二个参数是否先被评估。订单不仅可以在编译器之间有所不同,而且可以在同一程序中的源代码行之间有所不同。编译器可以按照自己喜欢的顺序对它们进行评估,而不需要记录如何。
答案 1 :(得分:1)
试试这个:
#include <stdio.h>
int main() {
int arr[] = {1, 2, 3, 4, 5};
int *ptr = arr;
printf("%d, %d\n", ptr[0], ptr[1]);
/* Or this
printf("%d, %d\n", ptr[1], ptr[0]);
whichever you meant
*/
return 0;
}
为函数参数的评估顺序定义了顺序,编译器可以选择他们喜欢的任何东西。我可以先*(++ptr)
,然后*ptr
,这会导致argmuents 2, 2
传递给printf,反之亦然,这会导致1, 2
传递给printf的。
答案 2 :(得分:1)
printf行中的评估顺序未指定,仅此一项就足以允许两种解释。
此外,两个指针访问之间没有序列点。这会导致未定义的行为(读取:您的程序可能会崩溃,按预期工作,或将您的硬盘内容上传到互联网,具体取决于当前的星期几,以及您正在使用的编译器。)
如果您使用警告进行编译,您会注意到以下内容:
mic@mic-nb $ gcc test.c -std=c11 -Wall -Wextra -pedantic
test.c: In function ‘main’:
test.c:6:29: warning: operation on ‘ptr’ may be undefined [-Wsequence-point]
printf("%d, %d\n", *ptr, *(++ptr));
^
mic@mic-nb $ clang test.c -std=c11 -Wall -Wextra -pedantic
test.c:6:29: warning: unsequenced modification and access to 'ptr' [-Wunsequenced]
printf("%d, %d\n", *ptr, *(++ptr));
~~~ ^
1 warning generated.
生活提示:始终使用-Wall -Wextra -pedantic
进行编译,始终修复所有警告,始终使用clang
和gcc
进行测试,您的错误将少得多。我甚至将-Werror
添加到我的发布构建配置中。