【问题标题】:Can unverified scanf call cause an undefined behavior?未经验证的 scanf 调用会导致未定义的行为吗?
【发布时间】:2014-11-24 12:29:20
【问题描述】:

如果出现错误,下面的 sn-p 是否会调用未定义的行为?

#include <stdio.h>

int main() {
    int i;                      /* Indeterminate */
    if (scanf("%d", &i) == 1)   /* Initialize */
        printf("%d\n", i);      /* Success! Print read value */
    else
        printf("%d\n", i);      /* Input failed! Is printing `i` UB or not? */
    return 0;
}

如果scanf失败了,是不是访问了一个未初始化的变量?

编辑
此外,如果我将scanf("%d", &amp;i) 替换为my_initializer(&amp;i) 会怎样:

int my_initializer(int *pi)
{
  double room_temp_degc = get_room_temp_in_degc();
  if(room_temp_degc < 12.0) {
    // Cool
    *pi = 42;
    return 1;
  } else {
    return 0;
  }
}

【问题讨论】:

    标签: c undefined-behavior c11


    【解决方案1】:

    在 C90 中,这是 UB。

    对于 C99 和 C11,从技术上讲,它不是,但输出是不确定的。甚至有可能,紧随其后的另一个 printf 将打印不同的值;未初始化的变量可能会在没有程序的明确操作的情况下发生变化。但是请注意,未初始化的变量只有在其地址被占用时才能被读取*)(这是在scanf 调用中完成的)。从 n1570 6.3.2.1 p2:

    如果左值指定了一个可以使用register 存储类声明的具有自动存储持续时间的对象(从未使用过它的地址),并且该对象未初始化(未使用初始化程序声明并且没有分配给它)已在使用前执行),则行为未定义。

    理论上,这将允许类似

    int n;
    &n;
    printf("%d\n", n);
    

    但编译器仍可能基于第一次读取不会发生在第一次写入之前的假设重新排序语句或分配寄存器,并忽略无副作用的&amp;n; 语句。

    出于任何实际目的,切勿读取未初始化的值。首先,您没有理由想要这样做;其次,即使是未指定的值也可以进行惊人的优化:有些人认为“垃圾”值可用于收集随机数的一些熵,这会导致加密软件中出现非常糟糕的错误,例如Xi Wang's blog entry。对于一个更奇怪的示例,其中未初始化的值在乘以 2 后为奇数,请参见例如this blog(是的,不确定的乘以 2 只是不确定的,不是偶数,只有其他不确定)。

    另见DR 260

    *) C99 中缺少引用的段落,但这应该被视为标准中的缺陷,而不是 C11 中的更改。 C99 使其在技术上定义(对于没有陷阱表示的机器)来读取任何未初始化的变量(尽管它们的值仍然不确定并且可能仍然看起来随机变化,它只是不是 UB)。

    对于DR 338,这已得到纠正,但在 C11 之前没有。添加它是为了在 Titanium 平台上允许 NaT(它存在于寄存器,但不存在于内存中的值),即使对于没有陷阱表示的整数类型也是如此。我不知道,如果上面代码中的&amp;n 对这样的平台有任何影响(通过严格阅读 C11,它应该,但我不会依赖它)。

    【讨论】:

    • 技术上绝对正确的答案,但我应该指出,gcc -std=c99 至少在某些版本中进行了优化,就好像阅读不确定的内容是 UB:blog.frama-c.com/index.php?post/2013/03/13/…
    • 不,根据 DR 338 的时间线,C11 中存在的“可能已使用 register 存储类声明”的修订定义从未出现在 C99 的任何修订中。DR 338 是2007-03-09提交,在TC3定稿之前,但组委会没有及时就TC3达成协议,变化出现在C11。除非您可以访问官方 TC3 并且您坚持它包含该句子(我有 N1256 草案,但我在其中的任何地方都找不到)。
    • 嗯.. 所以理想情况下,C90 应该编写极其防御性的代码。
    • @MohitJain,我不确定。有没有使用未初始化变量的实际用例?还是您在谈论意外未初始化的变量?一些编译器可以警告它,例如-Wuninitialized 使用 GCC(您可能需要使用优化来启用必要的流分析)。然而,这并不可靠(并且提出了一个问题,至少对于使用此 UB 进行优化的情况而言,它是否可靠)。
    • @mafso:主要用例,主要发生在结构中,但也可能发生在被复制到结构中的离散对象中,当有一个对象(始终初始化)时,它指示是否各种其他对象具有有意义的值。能够盲目地复制“哪些对象包含有意义的值”标志以及所有可能包含或不包含有意义值的对象,可能比尝试避免复制不包含有意义值的对象更快。
    猜你喜欢
    • 2013-10-02
    • 2014-08-11
    • 2016-09-04
    • 1970-01-01
    • 2013-04-24
    • 1970-01-01
    • 1970-01-01
    • 2016-02-17
    • 2022-11-16
    相关资源
    最近更新 更多