【问题标题】:Why do C compilers not warn when assigning integer value too high for signed type?为什么 C 编译器在为有符号类型分配过高的整数值时不会发出警告?
【发布时间】:2018-07-03 17:43:25
【问题描述】:

(假设是64位机器)

例如

int n = 0xFFFFFFFF; //max 32bit unsigned number
printf("%u\n", n);

常规有符号整数(32 位)可以存储的最大正数是0x7FFFFFFF

在上面的示例中,我将最大 unsigned 整数值分配给常规有符号整数,我没有收到来自 GCC 的警告或错误,并且打印结果没有问题(使用 @ 987654325@)。

UL 附加到十六进制常量不会改变任何内容。

这是为什么呢?

【问题讨论】:

  • 0xFFFFFFFF 不是有符号或无符号值,而是位模式的十六进制表示,在其上下文中是有意义的。
  • @WeatherVane 0xFFFFFFFF 有一个类型...
  • 对于带符号的 32 位整数,值 0xFFFFFFFF 是有效的 -1(2 的补码)。即使超出范围,C 也不会进行任何范围检查(据我所知);值只是被截断。这使得编译的 C 代码速度更快。需要时编写正确的代码或范围检查的负担由开发人员承担。
  • 这里n的实际值是实现定义的。如果int 是32 位长,那么0xFFFFFFFF 的类型是unsigned int(请参阅here)。然后转换为int,结果为implementation defined。将int 打印为%x%u 是未定义的行为。
  • 根据 C 2011 (N1570) 6.4.4.1 第 5 段,不带后缀的十六进制常量的类型是以下可以表示其值的第一个:intunsigned int、@ 987654340@、unsigned long intlong long intunsigned long long int。因此,在int 有32 个值位的C 实现中,0xFFFFFFFFunsigned int。在int 有64 个值位的C 实现中,0xFFFFFFFFint

标签: c integer unsigned-integer


【解决方案1】:

0xFFFFFFFF,在unsigned 的最大值为 232-1 的平台上,根据标准的“6.4.4.1 整数常量”将具有 unsigned 类型.

然后我们进行转换:

6.3.1.3 有符号和无符号整数

1 当一个整数类型的值转换为_Bool以外的其他整数类型时,如果该值可以用新的类型表示,则保持不变。
2 否则,如果新类型是无符号的,则在新类型可以表示的最大值的基础上反复加减一,直到值在新类型的范围内。60)
3 否则,新类型是有符号的,值不能在其中表示;结果要么是实现定义的,要么是产生实现定义的信号。

因此,结果是实现定义的或引发实现定义的信号。

现在,您使用%u 格式打印您的int,这完全是不匹配的。虽然这是严格意义上的 UB,但假设您有 2s-complement 并且原始分配使用了环绕,您可能会得到原始常量。

【讨论】:

    【解决方案2】:

    C 标准没有指定行为,但要求实现指定它。 GCC always uses 2's complement representation and converts via truncation,因此在使用 GCC 编译时,int32_t i = 0xFFFFFFFF; 将导致 i 设置为 -1。在其他编译器 YMMV 上。


    要从 GCC 获得警告,您需要提供 -Wsign-conversion flag:

    % gcc 0xfffffff.c -c -Wsign-conversion                         
    0xfffffff.c:1:9: warning: conversion of unsigned constant value to negative integer
            [-Wsign-conversion]
     int i = 0xFFFFFFFF;
             ^~t ~~~~~~~~
    

    一般情况下,C 编译器默认只对非常明显的错误和违反约束产生警告。 -Wsign-conversion 会使许多编译变得非常嘈杂——即使是那些定义明确的编译,例如:

    unsigned char c = '\x80';
    

    产生

    unsignedchar.c:1:19: warning: negative integer implicitly converted to unsigned type
             [-Wsign-conversion]
     unsigned char c = '\x80';
                       ^~~~~~
    

    关于签名char 的实现。

    【讨论】:

    • unsigned char c = '\x80'; 也调用实现定义的行为。 128 超出了此系统上的执行字符集范围,即 -128 到 +127。 (C11 6.4.4.4/10)。因此,我认为该示例与原始示例属于同一类别: implementation-defined ,但始终按预期工作,适合“always”的定义。
    • @M.M 这里是凌晨 5 点,我猜我的意思是 int 的转换是明确定义的
    【解决方案3】:

    假设 intunsigned int 是 32 位,在您可能使用的大多数平台(32 位和 64 位系统)上都是这种情况。那么常量0xFFFFFFFF的类型为unsigned int,值为4294967295。

    这个:

    int n = 0xFFFFFFFF;
    

    将该值从unsigned int 隐式转换为int。转换的结果是实现定义的;没有未定义的行为。 (原则上,它也可能导致引发实现定义的信号,但我知道没有执行此操作的实现。

    n 中存储的值很可能是 -1

    printf("%u\n", n);
    

    这里使用了%u 格式说明符,它需要unsigned int 类型的参数,但你将int 类型的参数传递给它。标准规定,相应的有符号和无符号类型的值可以作为函数参数互换,但适用于两种类型范围内的值,此处并非如此。

    此调用不会执行从 intunsigned int 的转换。相反,int 值被传递给printf,它假定它收到的值是unsigned int 类型。行为未定义。 (同样,这将是一个合理的警告。)

    最可能的结果是-1int 值(假设为2 的补码)与0xFFFFFFFF 具有相同的表示,将被视为unsigned int 值@987654343 @,以十进制打印为4294967295

    您可以使用-Wconversion-Wsign-conversion 选项在int n = 0xFFFFFFFF; 上收到警告。这些选项不包含在-Wextra-Wall 中。 (你必须问 gcc 维护者为什么。)

    我不知道有哪个选项会在printf 调用中引起警告。

    (当然,解决方法是将n 定义为unsigned int,这样可以使一切正确且一致。)

    【讨论】:

      猜你喜欢
      • 2010-10-20
      • 1970-01-01
      • 2010-11-27
      • 1970-01-01
      • 1970-01-01
      • 2011-01-22
      • 1970-01-01
      • 1970-01-01
      • 2016-07-26
      相关资源
      最近更新 更多