最近的一个问题引发了一场以数组和指针为中心的讨论。问题是参考scanf("%s", &name)
vs scanf("%s", name)
。
对于以下代码,Microsoft实际上在VS2010中解决了这个问题(也许是早期版本?),
#include <stdio.h>
int main()
{
char name[30];
printf("Scan \"name\" - ");
scanf("%s", name);
printf("Print \"&name\" - %s\n", &name);
printf("Print \"name\" - %s\n", name);
printf("Pointer to &name - %p\n", &name);
printf("Pointer to name - %p\n", name);
printf("\n\n");
printf("Scan \"&name\" - ");
scanf("%s", &name);
printf("Print \"&name\" - %s\n", &name);
printf("Print \"name\" - %s\n", name);
printf("Pointer to &name - %p\n", &name);
printf("Pointer to name - %p\n", name);
return 0;
}
这实际上是在ANSI C标准中定义的,还是允许编译器依赖?这是否有效,因为MS将所有内容都视为C ++?请暂时忽略缓冲区溢出问题。
答案 0 :(得分:7)
name
和&name
都应该给出相同的结果。严格来说,只有name
符合C语言标准且&name
会导致未定义的行为,因此您绝对应该使用name
,但实际上两者都有效。
name
是一个数组,因此当你将它用作函数的参数时(就像你将它传递给printf
时那样),它会“衰减”到指向其初始元素的指针(这里是char*
)。
&name
为您提供数组的地址;此地址与初始元素的地址相同(因为在数组的初始元素之前或数组中的元素之间不能有填充字节),因此&name
和name
具有相同的地址指针值。
但是,它们有不同的类型:&name
类型为char (*)[30]
(指向30 char
的数组的指针)而name
,当它衰减到指针时其初始元素的类型为char*
(指向char
的指针,在本例中为数组char
的初始name
元素)。
由于它们具有相同的值,并且由于printf
和scanf
函数无论如何都将参数重新解释为char*
,因此无论您是否通过name
或&name
。
答案 1 :(得分:2)
根据标准的未定义行为。
printf转换说明符"%p"
需要void*
:其他任何内容都会调用UB
printf转换说明符"%s"
期望char*
包含指向对象内某处的空字节:其他任何调用UB
scanf转换说明符"%s"
期望char*
具有足够的空间用于输入和额外的空终止字节:其他任何调用UB
如果任何实现定义了行为,那么可以在该实现中使用。
通常使用char*
打印char(*)[30]
或void*
而不是printf("%p")
会导致与预期行为无法区分的UB表现形式。
通常使用char(*)[30]
打印char*
而不是printf("%s")
会导致UB表现形式与预期行为无法区分。
根据您的说法,在这些情况下,UB的MS表现与预期相同。
但它仍然是未定义的行为。