【问题标题】:sscanf handles maximal unsigned integer value differently than assignment doessscanf 处理最大无符号整数值的方式与赋值不同
【发布时间】:2018-08-22 17:29:13
【问题描述】:

考虑以下代码:

main()
{
  int assigned = 4294967295;     // Max unsigned integer value on 32-bits arch

  char input[] = "4294967295";
  int sscanned;


  unsigned int result = sscanf(input, "%d", &sscanned);
  printf ("scanned %u elements : %d\n
          "Assigned j = %d\n", 
          result, sscanned, assigned);

  return 0;
}

当为 32 位 arch 编译时(使用编译命令:gcc -Wall -Wextra -std=c11 -pedantic -m32 test_sscanf.c -o test_sscanf32),它会发出一个关于“从 'long long int' 转换为 'int' 的值从 '4294967295' 的转换中溢出的预期警告到'-1' [-Woverflow]”。

现在看到结果了:

> ./test_sscanf32 
scanned 1 elements : 2147483647
Assigned j = -1

虽然assigned 值已通过二进制补码表示(-1 = -2^31 + 2^30 + ... + 2^ 0),另一方面,scanned 值显然已取消其 MSB,导致其缩小到值 2147483647 = 2^31 - 1

所以我的问题是:在 n 位机器上 上处理 最大 n 位整数值 的这种差异有什么理由(知道在64 位拱门,发生相同的行为)?
在给定的架构上,程序员是否无权期望sscanf 会像赋值一样对待值?

【问题讨论】:

  • 使用%u 读取无符号整数。
  • @wildplasser 我知道这一点。但这不是我的问题!我的问题与为什么 sscanf 处理 有符号的最大整数值 是通过丢弃它的 MSB 来处理的,而赋值只是用二进制补码转换它?
  • 赋值由编译器完成;扫描由库例程完成。他们可能使用不同的算法从 ascii 字符中获取值。正如 wildplasser 所说,使用 %u 读取 unsigned;否则结果未定义。
  • ...例如,编译器本身是 64 位并截断值,但运行时库例程是 32 位并溢出。
  • "...为什么 sscanf 通过丢弃它的 MSB 来处理有符号的最大整数值"。这不是发生的事情。试试char input[] = "4294967290";(最后一位是 0 而不是 5)。我怀疑结果仍然是2147483647。查看strtol() 以获得洞察力。

标签: c scanf integer-overflow twos-complement


【解决方案1】:

通过强制转换或赋值将整数值转换为int,当该值不能由int 表示但可以由具有更大范围的某些受支持类型表示时,会在int ( C11 §6.3.1.3)。现在几乎所有的实现都定义了这种转换,使得int x = UINT_MAX; 将 x 设置为 -1。我知道的唯一例外是 Unisys(née Burroughs)大型机,它仍然使用补码表示负数。

相比之下,所有scanf 函数在读取超出将要写入的变量类型的可表示范围的数字时都有未定义 行为(C11 §7.21.6.2p10 )。这意味着,您不仅可以指望它做与整数转换相同的事情,而且您根本不能指望它做任何建设性的事情,而且编译器实际上有权生成机器代码,让恶魔飞出你的鼻子。

我认为 7.21.6.2p10 是标准中的一个缺陷,但因为我认为 scanf 家庭不适合目的无论如何(这只是他们的许多问题之一),我懒得提交 DR。请改用strto* 函数。它们具有明确定义和记录的溢出行为。

【讨论】:

  • 这太棒了。我没想到一个答案中收集了如此多的相关信息!
  • @chux 谢谢,我总是忘记十进制文字不遵循与十六进制文字相同的类型确定规则。请看看您对修改后的文本有何看法。
  • 好 - 紫外线。奇怪的是,这个答案讨论了分配的 ID 行为(可能包括“发出信号”)作为一个大问题打折,并且预期为 -1 但scanf() 的 UB 在某些代码简单地执行时被广泛地描绘得很糟糕就像strtol() 会分配INT_MAX。 IMO,IDB 与 UB 只是稍微好一点,您使用 strto*() 的建议是最好的方法。
  • @chux 这是一个公平的观点。我不赞成 scanf 比 i-d 整数转换溢出行为更多的原因是,我对实际实现中 i-d 整数转换溢出的预期比实际实现中 scanf 的预期要多得多。是的,OP 的实现似乎调用了strtol,但我个人不知道可以依赖跨平台。现在我也对编译器从 UB 绘制优化推断感到非常紧张,甚至在库函数内部也是如此。
  • 真正的“现在对编译器从 UB 绘制优化推断感到紧张”是一个真正的问题。使用 IDB,至少编译器制造商需要说明行为,这为他们创造了历史,使未来的变化不那么反复无常。
【解决方案2】:

scanf 使用错误的格式说明符会调用undefined behavior,因此结果不一定有意义。

例如,在我的机器上运行相同的代码会得到以下结果:

scanned 1 elements : -1
Assigned j = -1

所以使用%u 而不是%d

【讨论】:

  • 在 OP 的示例代码中,格式说明符确实与正在写入的变量的类型相匹配;它是超出范围的值(仍然是 UB,但在不同的规则下)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-11-02
  • 1970-01-01
  • 1970-01-01
  • 2020-09-11
  • 2020-08-10
  • 1970-01-01
相关资源
最近更新 更多