【问题标题】:What is the proper way to store narrower data types into a wider data type in the C language?在 C 语言中将较窄的数据类型存储到较宽的数据类型中的正确方法是什么?
【发布时间】:2014-09-21 08:24:08
【问题描述】:

我目前正在修复 C 代码中的遗留错误。在修复此错误的过程中,我将unsigned int 存储到unsigned long long 中。但令我惊讶的是,当我在 GCC 的 64 位版本上编译此代码时,数学停止工作。我发现问题在于,当我为 long long 分配一个 int 值时,我得到了一个看起来像 0x0000000012345678 的数字,但在 64 位机器上,这个数字变成了 0xFFFFFFFF12345678

有人可以向我解释或指出某种规范或文档,说明在将较小的数据类型存储在较大的数据类型中时应该发生什么,以及在 C 中执行此操作的适当模式是什么?

更新 - 代码示例

这就是我正在做的事情:

// Results in 0xFFFFFFFFC0000000 in 64 bit gcc 4.1.2
// Results in 0x00000000C0000000 in 32 bit gcc 3.4.6
u_long foo = 3 * 1024 * 1024 * 1024;

【问题讨论】:

  • 你如何存储值?如果你做了类似int64_t my64bitInt = (int64_t)some32bitInt 的事情,那么这不应该发生。您是在使用memcpy 还是一些基于块的复制机制,还是像我上面的第一个示例那样进行直接分配?有一些方法可以通过使用基于块的复制而不是直接分配来实现这一点。在 C 中,向上转换是隐式的,并且保证无害,除非您在定点值和浮点值之间进行转换。
  • Unsigned long long 可以表示 unsigned int 的所有值,因此您描述的内容是不可能的。所以问题的关键部分是如何你做“分配”操作:)
  • 添加了代码示例。该示例使用 int 常量,但如果 int 是变量,也会发生这种情况。
  • u_long foo = 3UL * 1024UL * 1024UL * 1024UL;
  • 为了便携,最好使用unsigned long long而不是unsigned longlong 在某些 64 位机器上只有 32 位长。该标准保证long long 至少为 64 位。

标签: c types


【解决方案1】:

我认为你必须告诉编译器右边的数字是无符号的。否则它认为它是一个普通的有符号整数,并且由于设置了符号位,它认为它是负数,然后将它符号扩展到接收器中。

所以在右边做一些无符号转换。

【讨论】:

  • 就是这样。 3 * 1024 * 1024 * 1024 溢出了有符号整数的大小。感谢您的帮助。
  • 签名不是唯一的问题。
  • @ShafikYaghmour:在这种特殊情况下,3 * 1024 * 1024 * 1024 的数学结果恰好适合 unsigned int,但不适合 int(假设 32 位 int)。更一般地说,如果表达式溢出,使操作数无符号并不总是有帮助(行为可以很好地定义,但它仍然可以环绕)。考虑unsigned long long foo = 1024U * 1024U * 1024U * 1024U;,数学结果是2**40,它适合unsigned long long,但不适合unsigned long
  • @Keith Thompson 我不认为 product 适合 intunsigned 会导致问题。每个数字3102410241024 都适合int,因此结果基于int * int * int * int,即一个int,然后分配给一个unsigned long long
  • @chux:我不确定你的意思。所有操作数都是int类型,所以所有三个乘法都在int类型中完成;如果INT_MAX < 2147483648,它将在转换为unsigned long long之前溢出。
【解决方案2】:

表达式通常是独立评估的;它们的结果不受它们出现的上下文的影响。

1024 这样的整数常量是intlong intlong long int 中最小的一个,它的值适合;在 1024 的特定情况下,始终是 int

我在这里假设u_longunsigned long 的typedef(尽管您在问题中也提到了long long)。

所以给定:

unsigned long foo = 3 * 1024 * 1024 * 1024;

初始化表达式中的4个常量都是int类型,三个乘法都是int-by-int。结果恰好比 231 大(1.5 倍),这意味着它不适合 intint 为 32 位的系统上。 int 结果,不管它是什么,都会隐式转换为目标类型unsigned long,但到那时已经太晚了;溢出已经发生了。

溢出意味着你的代码有未定义的行为(因为这可以在编译时确定,我希望你的编译器会警告它)。在实践中,有符号溢出通常会回绕,因此上述通常会将foo 设置为-1073741824。你不能指望这一点(而且这也不是你想要的)。

理想的解决方案是通过首先确保所有内容都是目标类型来避免隐式转换:

unsigned long foo = 3UL * 1024UL * 1024UL * 1024UL;

(严格来说只有第一个操作数需要是unsigned long类型,但保持一致更简单。)

让我们看看更一般的情况:

int a, b, c, d; /* assume these are initialized */
unsigned long foo = a * b * c * d;

您不能将UL 后缀添加到变量中。如果可能,您应该更改abcd 的声明,使它们的类型为unsigned long long,但也许还有其他原因,它们需要为int 类型.您可以添加强制类型转换显式将每个类型转换为正确的类型。通过使用强制转换,您可以准确控制何时执行转换:

unsigned long foo = (unsigned long)a *
                    (unsigned long)b *
                    (unsigned long)d *
                    (unsigned long)d;

这有点冗长;您可能会考虑仅将强制转换应用于最左边的操作数(确保您了解表达式的解析方式之后)。

注意:这不会起作用:

unsigned long foo = (unsigned long)(a * b * c * d);

强制转换将int 结果转换为unsigned long,但仅在溢出已经发生之后。它只是明确指定了隐式执行的强制转换。

【讨论】:

    【解决方案3】:

    带有后缀的整体文字是 int 如果它们适合,在您的情况下 31024 绝对适合。这在草案 C99 标准部分 6.4.4.1 整数常量 中有介绍,这部分的引用可以在我对 Are C macros implicitly cast? 的回答中找到。

    接下来是乘法,它对操作数执行通常的算术转换,但由于它们都是int,其结果太大而无法放入有符号的 int 会导致溢出。根据 5 部分,这是未定义的行为,其中说:

    如果在计算表达式期间出现异常情况(即,如果 结果未在数学上定义或不在其可表示值的范围内 type),行为未定义。

    我们可以使用 clang 和 -fsanitize=undefined 标志 (see it live) 凭经验发现这种未定义的行为:

    运行时错误:有符号整数溢出:3145728 * 1024 不能用“int”类型表示

    虽然在两个补码中,这最终只会是一个负数。解决此问题的一种方法是使用 ul 后缀:

    3ul * 1024ul * 1024ul * 1024ul 
    

    那么为什么一个负数转换为一个无符号值会给出一个非常大的无符号值呢?这在6.3.1.3 有符号和无符号整数 部分中有介绍,其中说:

    否则,如果新类型是无符号的,则通过重复添加或转换值 比新类型可以表示的最大值减一 直到值在新类型的范围内。49)

    这基本上意味着将unsigned long max + 1 添加到负数中,这会导致非常大的无符号值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-01-25
      • 1970-01-01
      • 1970-01-01
      • 2013-06-01
      • 2011-08-09
      • 2018-11-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多