1

当 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

4 回答 4

4

重复问题中的代码:

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"同样的事情发生在传递cprintf. (但仍然存在一些问题;我将在其他示例之后解决这个问题。

3.

scanf("%s", &d);
printf("%s\n", &d);

因为dis 是单个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。

(对不起,这个答案太长了;我没有时间把它缩短。)

于 2012-04-15T01:40:27.743 回答
3

在案例 3 和 4 中,您的行为未定义。

  • 情况一和二是相同的,因为都指向数组中的第一个元素。
  • 情况 3 未定义,因为您在期望指向 char 的指针时给出指向 char 的指针。
  • 情况 4 未定义,因为指针d未初始化。

于 2012-04-15T00:37:26.083 回答
2

3 有效(在许多平台上,如果打开它们会发出警告;从技术上讲,这是未定义的行为),因为您正在滥用指针(将&d类型 的(char **),视为(char *)并将字符存储在用于指针的内存中) . 4 死,因为未初始化的指针指向一个随机地址。

于 2012-04-15T00:39:30.640 回答
0

这里的重要问题是是否有存储结果的空间。

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在哪里找到有效的内存指向,所以你在玩指针俄罗斯轮盘赌。

于 2012-04-15T01:25:09.743 回答