【问题标题】:Is scanf("%d%d", &x, &x) well defined?scanf("%d%d", &x, &x) 定义明确吗?
【发布时间】:2016-03-01 00:11:47
【问题描述】:

下面的代码定义好了吗?

#include <stdio.h>

int ScanFirstOrSecond(const char *s, int *dest) {
    return sscanf(s, "%d%d", dest, dest);
}

int main(void) {
    int x = 4;
    ScanFirstOrSecond("5", &x);
    printf("%d\n", x);  // prints 5

    // Here is the tricky bit
    ScanFirstOrSecond("6 7", &x);
    printf("%d\n", x);  // prints 7
    return 0;
}

换句话说,... 参数对它们有隐含的restrict 吗?

我发现的最适用的 C 规范是

fscanf 函数依次执行该格式的每个指令。 ... C11dr §7.21.6.2 4

【问题讨论】:

  • 与限制无关。它的定义很好。您将两次写入同一地址的值,以便获得最新的值。
  • 几个月前,我在处理scanf 的 UB 检测实现时遇到了同样的问题。
  • @Pascal Cuoq 如果是骗子,请告知。我确实找了 15 分钟,但如果是的话也不会感到惊讶。
  • @nneonneo 你确定你了解restrict 的工作原理吗?在 C11 6.7.3.1:4 中查找“and X is also modified”。
  • 考虑使用scanf 的等效情况。如果你用newline 分隔这两个条目,你会期望它们被颠倒吗?

标签: c language-lawyer restrict


【解决方案1】:

简短的回答是:是的,它已定义

scanf 将尝试将来自stdin 的字节序列转换为以10 为基数的整数,并带有可选的初始空格和可选的符号。如果成功,号码将被存储到x。然后scanf 将再次执行这些步骤。返回值可以是EOF012,对于后两者,最后转换的数字将被存储到x中。

长答案有点微妙:

似乎 C 标准确实指定了值按格式字符串的顺序存储。引用 C11 标准:

7.21.6.2 fscanf 函数

...

4 fscanf 函数依次执行格式的每个指令。当所有指令都已执行,或者如果指令失败(如下详述),函数返回。

...

7 作为转换规范的指令定义了一组匹配的输入序列,如下面的每个说明符所述。转换规范按以下步骤执行:

...

10 除了% 说明符的情况外,输入项(或者,在%n 指令的情况下,输入字符的计数)被转换为适合转换说明符的类型。如果输入项不是匹配序列,则指令执行失败:此条件为匹配失败。除非 * 指示分配抑制,否则转换结果将放置在尚未收到转换结果的格式参数后面的第一个参数所指向的对象中。

...

16 如果在第一次转换(如果有)完成之前发生输入错误,fscanf 函数将返回宏 EOF 的值。否则,该函数返回分配的输入项的数量,如果发生早期匹配失败,该数量可能少于提供的数量,甚至为零。

本规范的其他任何地方都没有提到任何对输出对象的访问。

然而,标准的措辞似乎表明,如果 2 个指针指向同一个对象,则行为可能会出乎意料:转换的结果将放在第一个参数所指向的对象中,格式如下尚未收到转换结果的参数。 这句话有点模棱两可:尚未收到转换结果的指的是什么?对象还是论点?对象接收转换结果,而不是指针参数。在您扭曲的示例中,对象 x 已经收到转换结果,因此它不应该收到另一个结果...但是正如 supercat 所指出的那样,这种解释具有明显的限制性,因为它意味着所有转换后的值都存储到第一个目标对象。

所以它看起来是完全指定和定义明确的,但规范的措辞可以完善以消除潜在的歧义。

【讨论】:

  • 该措辞是指在迭代指令时如何迭代参数。
  • 你的答案的关键要素:依次执行格式的每个指令转换的结果放在第一个参数指向的对象中。 ..。连同@Seb 注释,它是参数顺序(而不是值),指出这是确定结果定义良好所需的规范。
  • 在语法上,短语“......格式参数之后的第一个参数尚未收到转换结果”表明,为了识别“第一个”参数,参数被视为接收结果。否则,所有结果都将存储到格式说明符后面的第一个参数中,因为 no 参数将永远接收结果。如果标准说“第一个参数......其目标对象没有收到结果”,那将是另一回事。
  • @supercat:好点。我更新了答案以限制这种挑剔的解释的价值。
【解决方案2】:

scanf() 系列函数严格依次执行您在格式字符串中留下的指示。所以第一个值会被读入,然后第二个值会覆盖第一个值。这里没有 UB。

【讨论】:

    【解决方案3】:

    是的,定义明确。这意味着“将第一个令牌读入 *dest,然后将第二个令牌再次读入 *dest”。这很奇怪但合法。 是的,因为 sscanf() 以严格的顺序执行格式字符串中的指令。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-09-28
      • 2021-05-18
      • 2014-05-30
      • 2017-06-25
      • 2022-11-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多