当 4 产生分段错误时,为什么 1、2 和 3 起作用?(见下文。)
char c[10];
char* d;
1.
scanf("%s", &c);
printf("%s\n", &c);
2.
scanf("%s", c);
printf("%s\n", c);
3.
scanf("%s", &d);
printf("%s\n", &d);
4.
scanf("%s", d);
printf("%s\n", d);
当 4 产生分段错误时,为什么 1、2 和 3 起作用?(见下文。)
char c[10];
char* d;
1.
scanf("%s", &c);
printf("%s\n", &c);
2.
scanf("%s", c);
printf("%s\n", c);
3.
scanf("%s", &d);
printf("%s\n", &d);
4.
scanf("%s", d);
printf("%s\n", d);
重复问题中的代码:
char c[10];
char* d;
1.
scanf("%s", &c);
printf("%s\n", &c);
这可能会按预期工作,但实际上行为是未定义的。
scanf
使用"%s"
格式需要类型的参数char*
。 &c
是 type char (*)[10]
,即它是一个指向char[10]
数组的指针。它指向内存中与 的第 0 个元素的地址相同的位置c
,但它的类型不同。同样的事情发生在printf
:"%s"
格式告诉它期待一个char*
参数,但你传递给它一个char(*)[10]
参数。
由于scanf
是一个可变参数函数,因此除了格式字符串之外,不需要对参数进行类型检查。编译器将(可能)愉快地将char (*)[10]
值传递给scanf
,假设它可以处理它。在所有指针具有相同大小、表示和参数传递机制的实现上,它可能可以。但是,例如,用于奇异架构的 C 编译器可以轻松地使char*
指针大于指向更大类型的指针。想象一个 CPU,其本机地址指向一个 64 位字;一个char*
指针可能由一个字指针加上一个字节偏移量组成。
2.
scanf("%s", c);
printf("%s\n", c);
这个更好。 c
是一个数组,但在这种情况下,数组表达式“衰减”为指向数组第一个元素的指针——这正是格式所要求scanf
的。"%s"
同样的事情发生在传递c
给printf
. (但仍然存在一些问题;我将在其他示例之后解决这个问题。
3.
scanf("%s", &d);
printf("%s\n", &d);
因为d
is 是单个char*
参数,&d
是 type char**
,并且再次,您传递了错误类型的参数。如果所有指针具有相同的表示(和相同的参数传递机制),并且输入scanf
足够短,这可能会发生“工作”。它将char*
对象视为char
. 如果char*
是 4 个字节,并且输入字符串的长度不超过 3 个字符,这可能会起作用——就好像您使用了 achar[4]
并正确编写了调用一样。但这非常将字符串直接存储到指针对象中的做法很糟糕,并且存在写入对象末尾的巨大风险,结果不可预测。(这些不可预知的结果包括写入没有用于其他任何事情的内存,这可能看起来有效;这就是未定义行为的本质。)
(C 标准允许将任何对象视为字符数组,但在这种情况下,这是一个非常糟糕的主意。)
4.
scanf("%s", d);
printf("%s\n", d);
这里的类型都是正确的,但除非你已经初始化d
指向一个足够大的数组char
,否则它可能会失败(或者更糟糕的是,看起来“正常”工作,这意味着你有一个微妙的错误会可能稍后会出现)。
现在我们来谈谈我上面提到的其他问题。
例如4,我提到d
需要指向一个“足够大”的数组。“足够大”有多大?没有答案。 scanf("%s", ...)
读取以空格分隔的字符序列,其长度没有上限。例如,如果我运行您的程序并按住该x
键,我可以提供比您提供的任何缓冲区更长的输入字符串,但会产生不可预测的结果(再次出现未定义的行为)。
该scanf
函数的"%s"
格式无法安全使用(除非您的程序在可以控制标准输入流上显示的内容的环境中运行)。
读取文本输入的一种好方法是使用fgets
一次读取一行,然后使用其他函数来分析结果。 fgets
要求您指定输入的最大长度;如果实际输入超过限制,它会被截断并留给以后的调用读取。它不像 那样方便scanf
,但可以安全地完成。(并且永远不要使用该gets
功能;例如scanf("%s", ...)
,它不能安全地使用。)
推荐阅读:
comp.lang.c FAQ的第 6 节很好地解释了 C 数组和指针,以及它们如何相关(和不相关)。第 12 节讨论 C 标准 I/O。
(对不起,这个答案太长了;我没有时间把它缩短。)
在案例 3 和 4 中,您的行为未定义。
d
未初始化。3 有效(在许多平台上,如果打开它们会发出警告;从技术上讲,这是未定义的行为),因为您正在滥用指针(将&d
类型 的(char **)
,视为(char *)
并将字符存储在用于指针的内存中) . 4 死,因为未初始化的指针指向一个随机地址。
这里的重要问题是是否有存储结果的空间。
scanf("%s", &c);
printf("%s\n", &c);
有存储吗?是的,您使用的地址是数组第一个元素的地址。该数组存在,因此您可以将结果放在那里。
scanf("%s", c);
printf("%s\n", c);
有存储吗?是的。像这样使用,数组折叠成一个指针,它的传递与上面相同。
scanf("%s", &d);
printf("%s\n", &d);
有存储吗?是的。它不是适当的类型, ( char **
, 应该是char *
),但它应该与将 char 转换为指针类型并将其存储在声明为指针的变量中没有任何不同。(其他答案说这是未定义的行为。我不认为是,将 achar
或任何其他整数类型转换为 achar *
或其他指针类型是明确定义的,如果不明智的话;告诉我标准在哪里说这是未定义的。 )
scanf("%s", d);
printf("%s\n", d);
有存储吗?不是你分配的。从技术上讲,可能发生的任何事情都d
指向内存中不会出现段错误的位置。即使是这样,这也不是你的记忆,你可能会覆盖一些重要的东西,或者它可能会意外改变。你还没有告诉d
在哪里找到有效的内存指向,所以你在玩指针俄罗斯轮盘赌。