main()
{
char buffer[6]="hello";
char *ptr3 = buffer +8;
char *str;
for(str=buffer;str <ptr3;str++)
printf("%d \n",str);
}
这里,ptr3
指向数组边界。但是,如果我运行这个程序,我将获得连续的内存位置(ex.1000 ..... 1007)。因此,根据C标准,指向多个数组边界的指针明显是未定义的行为。
我的问题是上面的代码是如何导致未定义的行为的?
答案 0 :(得分:1)
程序中出现多次未定义的行为。
对于初学者,您在没有必需printf
的情况下呼叫#include <stdio.h>
,而main()
应该是int main(void)
。这不是你要问的问题,但你应该解决它。
char buffer[6]="hello";
没关系。
char *ptr3 = buffer +8;
评估表达式buffer +8
具有未定义的行为。 N1570 6.5.6指定+
加法运算符的行为,第8段说明:
如果指针操作数和结果都指向了元素 相同的数组对象,或者超过数组对象的最后一个元素, 评估不得产生溢出;否则,行为 未定义。
计算指针值本身具有未定义的行为,即使您从未取消引用它或访问其值。
char *str;
for(str=buffer;str <ptr3;str++)
printf("%d \n",str);
您将char*
值传递给printf
,但%d
需要int
类型的参数。将错误类型的值传递给printf
也具有未定义的行为。
如果要打印指针值,则需要编写:
printf("%p\n", (void*)str);
可能会以十六进制打印指针值,具体取决于实现方式。 (我删除了不必要的尾随空格。)
当str
指向buffer[5]
时,str++
有效;它会导致str
指向buffer
的末尾。 (在此之后取消引用str
会有未定义的行为,但是你不这样做。)之后再次增加str
具有未定义的行为。比较str < ptr3
也有未定义的行为,因为ptr3
具有无效值 - 但您在初始化ptr3
时已经触发了未定义的行为。所以这只是锦上添花的结果。
请记住,“未定义的行为”意味着C标准没有定义行为。这并不意味着程序将崩溃或打印错误消息。实际上,未定义行为的最坏结果是代码似乎“有效”;这意味着你有一个错误,但诊断和修复它会很困难。
答案 1 :(得分:0)
您正在查看指针的地址。如果您想要该值,则需要在*
中使用取消引用(printf
)运算符。
另一方面,如果你想看到字符而不是ASCII码,你应该在%c
中使用printf
。
printf("%c\n",*str);
答案 2 :(得分:0)
在C中,您始终可以添加两个数字。您始终可以向指针添加整数,或减去两个指针。您将始终获得“答案”:编译器将生成代码并执行代码。这无法保证答案有效,有用,甚至定义。
C标准定义了语言。在语法允许的范围内,它定义了什么是有效的 - 什么是肯定的东西 - 什么不是。当您在这些行之外着色时,编译器可能会产生奇怪的代码或没有代码。在C中,编译器的工作不是预测每个奇怪的情况并得出合理的答案。编译器编写者假定程序员知道规则,并且不需要验证他是否遵循它们。
有许多有效语法的例子没有意义或未定义。在数学中,你不能记录负数,也不能除以零。除以零不会产生零或不为零;该操作是 undefined 。
在您的情况下,ptr3
有一个正确计算的值,比buffer
大8。这是一些指针算法的结果。到现在为止还挺好。
但仅仅因为你有一个指针,并不意味着它指向任何东西。 (void*) 0
明确保证不会指向任何内容。同样,您的ptr3
并未指出任何内容。它甚至不需要大于buffer
的值8。 C标准的6.5.6节定义了向指针添加整数的结果,并将其置于这样:
如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素,则评估不应产生溢出;否则,行为未定义。
当你说,我得到连续的记忆位置(对于ex.1000 ..... 1007),你所看到的就是行为。你必须看到一些行为。而且这种行为是不确定的。根据标准,您可以看到一些其他行为,例如回绕到1000或0。
编译器接受的内容和标准定义的内容是两个不同的东西。