我的问题是如何将字符串输入字符指针数组?是在这里动态分配内存吗?实际存储在数组'name
'中的内容是什么?
char *name[20];
printf("Enter a string:");
scanf("%s",name);
printf("%s",name);
此代码可以正常工作。它打印我输入的字符串。怎么可能像char *name[20]
一样对待char name[20]
?
答案 0 :(得分:2)
是在这里动态分配内存吗?
否。
此代码可以正常工作。它打印我输入的字符串。像char name [20]一样如何对待char * name [20]?
程序的行为是不确定的。
它是未定义的,因为它违反了scanf
和printf
函数的要求。
C标准提到%s
和scanf
的{{1}}说明符(引用标准草案N1570):
相应的参数应为指向字符数组初始元素的指针...
printf
是不是指向字符数组初始元素的指针。它是(实际上是一个数组,但是)衰减后的指针,它是指向字符的指针数组中指向字符的初始指针。这样就违反了要求,并且程序的行为未定义。
“行为未定义”是什么意思?
这意味着不能保证程序的行为。就语言而言,该程序可能会:
name
时的行为完全相同。要避免未定义的行为。
答案 1 :(得分:2)
scanf
和printf
期望使用字符数组,但是您给了它一个指针数组。但是,它们无法识别出差异,因为它们是可变的。这会导致不确定的行为,因此一切皆有可能。
这里可能发生的情况是,scanf
只是将字符写入指针数组的内存中,而printf
将数据视为字符,因为这两个函数都不知道您给它的内存块应该存储指针而不是字符。打印第二个元素可以使您获得第五个字符,因为您的系统有四个字节指针,因此第二个指针元素从第五个字节开始,因此从第五个字符开始。
同样,您的代码表现出未定义的行为,因此上一段仅是推测。标准并不能保证所有这些,您永远都不应依赖它。
答案 2 :(得分:0)
尽管eerorika’s answer是完全正确的,但从我的感觉来看,OP可能需要对这里发生的事情进行更详细的解释……
您用char * name[20]
声明的是否一个指向20个字符的数组。它 IS 由20个指向内存的指针组成的数组(每个指针都被视为指向一个字符或一个字符数组)。声明中的[20]
已经指定您需要20个元素,并且前面的内容(在您的情况下为char *
)指定了这些元素。因此,正确的声明(在您的上下文中)只是char name[20]
。
现在,编译器看到您需要一个20个字符的内存块,并为您保留了内存(在堆栈上,此处没有动态分配)。然后char name[20]
是一个连续的保留内存块,可以容纳20个字符。
您以 以下内容说明了正确的代码会发生什么,您的代码会发生什么,为什么它是未定义的行为(UB),以及为什么它在您的情况下很不幸地起作用。 使用正确的声明 char [20] memory block uninitialized 当您将其传递到 char [20] memory block after passing it to scanf 这就是你想要的。 现在,这是您使用 char * [20] memory block uninitialized 当您将其传递到 现在需要更多关注。仅由于 char * [20] memory block after passing it to scanf 现在为什么一切对您都很好,只是因为 实际上,只要您仅在 从技术上讲,您的代码属于UB。这就是标准所说的。该标准无法解决“程序员编写怪异代码时代码才能真正起作用”的所有情况。是的,在 ps。>考虑在代码中使用&name[0]
或简单地以name
的形式到达此块的开头。后者仅是因为静态数组变量(在您的情况下为20个字符的整个块)是隐式地可转换(不等同于)指向该存储块开头的指针,即{{ 1}}。 ((如果您觉得自己不明白最后一条语句,可以咨询一下short article或Google,例如 char数组与c中的char指针的区别。< / p>
char *
,您会得到char name[20]
表示尚未指定缓冲区每个单元中的内容,但编译器将其视为字符。 ?character
且用户输入“ HelloKitty”时,此答案将填充到编译器保留的scanf
内存块中。char [20]
声明所得到的。一开始情况就是这样char * name[20]
表示尚未指定缓冲区每个单元中的内容,但是编译器将其视为字符的指针。 ?pointer
且用户输入其“ HelloKitty”时,该答案将被填充到编译器保留的scanf
内存块中,如前所述。 char * [20]
不是类型安全的事实,才有可能这样做。它会根据地址scanf
的指定,获取所获得的任何地址(在您的情况下为name[0]
的地址),然后漫不经心地填写。因此,它很乐意将字符数组填充到应该包含Poiners数组的内存块中。现在,每个字符都确实具有二进制表示形式。因此,实际上,format specifiers
内存块将用代表字符串“ HelloKitty”(直到该字符串的长度)的“零和一”填充。char * [20]
意味着编译器仍将内容视为指向字符的指针,但是该指针的值(技术上指向的地址)已通过调用进行了重写?!pointer
。 (请注意:即使“ HelloKitty”包含11个字符(包括终止符nul),由于scanf
每个像元等于指针的sizeof
,也不会重写11个像元。通常,指针的sizeof
为4倍于sizeof
在32位架构上的存储空间,因此将仅重写2个单元和第三个单元的一部分)char
和它的姊妹printf
一样对它的实际类型一无所知。因此,scanf
取printf
的地址,并将其“零和一”(返回)解释为“ HelloKitty”字符串。name[0]
和name
(或类似函数)之间传递printf
变量,您的程序就不可能调用UB(见下文)。为scanf
保留的存储块的(二进制)内容在某种程度上(请参见下文)保证与“ HelloKitty”字符串的(二进制表示)等效。只要char * name[20]
读取的内容不超过为其提供的存储块,该程序就不可能(以下列方式)损坏(请参见下文)。您也可以编写scanf
,只要用户输入的字符数少于3个,该程序就可以正常运行。 (假设int i; scanf("%s",&i); printf("%s",&i);
sizeof
是4 int
)chars
中将char * [20]
视为char [20]
是“奇怪的事情”。同时,通过指定UB,该标准为编译器实现者提供了一定的自由度。如果您要编写编译器,则可以决定在scanf
编译时解析格式,并生成一组特殊的指令,该指令依赖是为{提供了正确的类型{1}}(我几乎无法想象这样的情况-至少对于scanf
切换器-不会改变任何事实)。所以程序是UB。期。 :)name
。这样可以防止%s
读取超出存储块长度的字符,这将导致另一个UB。