如何将字符串输入到字符指针数组?

时间:2019-07-13 04:05:18

标签: c

我的问题是如何将字符串输入字符指针数组?是在这里动态分配内存吗?实际存储在数组'name'中的内容是什么?

char *name[20];
printf("Enter a string:");
scanf("%s",name);
printf("%s",name);

此代码可以正常工作。它打印我输入的字符串。怎么可能像char *name[20]一样对待char name[20]

3 个答案:

答案 0 :(得分:2)

  

是在这里动态分配内存吗?

否。

  

此代码可以正常工作。它打印我输入的字符串。像char name [20]一样如何对待char * name [20]?

程序的行为是不确定的。

它是未定义的,因为它违反了scanfprintf函数的要求。

C标准提到%sscanf的{​​{1}}说明符(引用标准草案N1570):

  

相应的参数应为指向字符数组初始元素的指针...

printf不是指向字符数组初始元素的指针。它是(实际上是一个数组,但是)衰减后的指针,它是指向字符的指针数组中指向字符的初始指针。这样就违反了要求,并且程序的行为未定义。

  

“行为未定义”是什么意思?

这意味着不能保证程序的行为。就语言而言,该程序可能会:

  • 产生您期望的输出。
  • 产生您意想不到的输出。
  • 生产要生成的输出。
  • 产生一些您不想要的输出。
  • 完全不产生输出。
  • 崩溃
  • 不崩溃
  • 在另一个系统上的行为不同。
  • 在同一系统上的行为不同。
  • 调试时的行为有所不同。
  • 只有在度假时,行为方式才不同。
  • 出于任何可能的原因有所不同。
  • 似乎毫无理由地表现不同。
  • 始终表现相同
  • 即使没有使用,其行为也与使用name时的行为完全相同。
  • 不像那样。
  • 有任何行为。

要避免未定义的行为。

答案 1 :(得分:2)

scanfprintf期望使用字符数组,但是您给了它一个指针数组。但是,它们无法识别出差异,因为它们是可变的。这会导致不确定的行为,因此一切皆有可能。

这里可能发生的情况是,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个字符。

您以&name[0]或简单地以name的形式到达此块的开头。后者仅是因为静态数组变量(在您的情况下为20个字符的整个块)是隐式地可转换(不等同于)指向该存储块开头的指针,即{{ 1}}。 ((如果您觉得自己不明白最后一条语句,可以咨询一下short article或Google,例如 char数组与c中的char指针的区别。< / p>

以下内容说明了正确的代码会发生什么,您的代码会发生什么,为什么它是未定义的行为(UB),以及为什么它在您的情况下很不幸地起作用。

使用正确的声明char *,您会得到

char [20] memory block uninitialized

char name[20]表示尚未指定缓冲区每个单元中的内容,但编译器将其视为字符。

当您将其传递到?character且用户输入“ HelloKitty”时,此答案将填充到编译器保留的scanf内存块中。

char [20] memory block after passing it to scanf

这就是你想要的。

现在,这是您使用char [20]声明所得到的。一开始情况就是这样

char * [20] memory block uninitialized

char * name[20]表示尚未指定缓冲区每个单元中的内容,但是编译器将其视为字符的指针

当您将其传递到?pointer且用户输入其“ HelloKitty”时,该答案将被填充到编译器保留的scanf内存块中,如前所述。

现在需要更多关注。仅由于char * [20]不是类型安全的事实,才有可能这样做。它会根据地址scanf的指定,获取所获得的任何地址(在您的情况下为name[0]的地址),然后漫不经心地填写。因此,它很乐意将字符数组填充到应该包含Poiners数组的内存块中。现在,每个字符都确实具有二进制表示形式。因此,实际上,format specifiers内存块将用代表字符串“ HelloKitty”(直到该字符串的长度)的“零和一”填充。

char * [20] memory block after passing it to scanf

char * [20]意味着编译器仍将内容视为指向字符的指针,但是该指针的值(技术上指向的地址)已通过调用进行了重写?!pointer。 (请注意:即使“ HelloKitty”包含11个字符(包括终止符nul),由于scanf每个像元等于指针的sizeof,也不会重写11个像元。通常,指针的sizeof为4倍于sizeof在32位架构上的存储空间,因此将仅重写2个单元和第三个单元的一部分)

现在为什么一切对您都很好,只是因为char和它的姊妹printf一样对它的实际类型一无所知。因此,scanfprintf的地址,并将其“零和一”(返回)解释为“ HelloKitty”字符串。

实际上,只要您仅在name[0]name(或类似函数)之间传递printf变量,您的程序就不可能调用UB(见下文)。为scanf保留的存储块的(二进制)内容在某种程度上(请参见下文)保证与“ HelloKitty”字符串的(二进制表示)等效。只要char * name[20]读取的内容不超过为其提供的存储块,该程序就不可能(以下列方式)损坏(请参见下文)。您也可以编写scanf,只要用户输入的字符数少于3个,该程序就可以正常运行。 (假设int i; scanf("%s",&i); printf("%s",&i); sizeof是4 int

从技术上讲,您的代码属于UB。这就是标准所说的。该标准无法解决“程序员编写怪异代码时代码才能真正起作用”的所有情况。是的,在chars中将char * [20]视为char [20]是“奇怪的事情”。同时,通过指定UB,该标准为编译器实现者提供了一定的自由度。如果您要编写编译器,则可以决定在scanf编译时解析格式,并生成一组特殊的指令,该指令依赖是为{提供了正确的类型{1}}(我几乎无法想象这样的情况-至少对于scanf切换器-不会改变任何事实)。所以程序是UB。期。 :)

ps。>考虑在代码中使用name。这样可以防止%s读取超出存储块长度的字符,这将导致另一个UB。