为啥最小的 int -2147483648 的类型为“long”? [复制]

Posted

技术标签:

【中文标题】为啥最小的 int -2147483648 的类型为“long”? [复制]【英文标题】:Why does the smallest int, −2147483648, have type 'long'? [duplicate]为什么最小的 int -2147483648 的类型为“long”? [复制] 【发布时间】:2016-04-15 22:14:00 【问题描述】:

对于一个学校项目,我必须编写 C 函数 printf。事情进展得很顺利,但有一个问题我找不到很好的答案,所以我来了。

printf("PRINTF(d) \t: %d\n", -2147483648);

告诉我 (gcc -Werror -Wextra -Wall):

   error: format specifies type 'int' but the argument has type 'long'
      [-Werror,-Wformat]
        printf("PRINTF(d) \t: %d\n", -2147483648);
                              ~~     ^~~~~~~~~~~
                              %ld

但如果我使用 int 变量,一切都会顺利:

int i;

i = -2147483648;
printf("%d", i);

为什么?

编辑:

我明白了很多点,而且很有趣。无论如何,我猜printf 正在使用<stdarg.h> 库,所以va_arg(va_list ap, type) 也应该返回正确的类型。对于%d%i,显然返回的类型是int。有什么改变吗?

【问题讨论】:

对于我已经回答的其他问题,但随后我的评论已被删除:va_arg() 不知道您尝试获取的参数的类型。您需要知道这一点,如果您尝试获取与作为参数传递的类型不同的类型,那是未定义的行为。如果您执行printf("%d\n", -2147483648),这也适用,因为参数的类型为long,但printf 尝试获取int 不是重复的。如果您阅读了另一个问题的已接受答案,那是由于特定于问题上下文的未定义行为。这个问题与未定义的行为无关。这个问题是关于 C 的,另一个是关于 C++ 的。两种语言都有相似的推广规则,但可能存在细微差别。这个问题将帮助更多的未来访问者,如果更高的投票数有任何迹象,也许已经有了。 也不是第二个问题的重复。关键事实是他们以十六进制指定文字,这意味着它是无符号整数而不是有符号长。 @AdrianMcCarthy 他们以十六进制指定文字,这意味着它是无符号整数而不是有符号长整数。 这可以理解为“十六进制常量始终是无符号的”。旧的预标准 C 编译器通常使十六进制常量无符号,因此可能会造成混淆。每6.4.4.1 Integer constants, paragraph 5:“整数常量的类型是可以表示其值的相应列表的第一个。”在这种情况下,0x80000000 在这里是 unsigned int,因为它适合 unsigned int,但对于 [signed] int 来说太大了。 @AdrianMcCarthy 我重写了我的评论以表明我只是想澄清一下,并删除了我之前的评论 【参考方案1】:

在 C 中,-2147483648 不是整数常量。 2147483648 是一个整数常量,- 只是一个应用于它的一元运算符,产生一个常量表达式。 2147483648 的值不适合 int(它太大了,2147483647 通常是最大的整数),因此整数常量的类型为 long,这会导致您观察到的问题。如果您想提及int 的下限,请使用<limits.h> 中的宏INT_MIN(可移植方法)或小心避免提及2147483648

printf("PRINTF(d) \t: %d\n", -1 - 2147483647);

【讨论】:

【参考方案2】:

问题在于-2147483648 不是整数文字。这是一个由一元否定运算符- 和整数2147483648 组成的表达式,如果ints 是32 位,则它太大而不能成为int。由于编译器会在应用否定运算符之前选择一个适当大小的有符号整数来表示2147483648,因此结果的类型将大于int

如果您知道您的 ints 是 32 位,并且希望在不破坏可读性的情况下避免警告,请使用显式强制转换:

printf("PRINTF(d) \t: %d\n", (int)(-2147483648));

这是在具有 32 位 ints 的 2 的补码机器上定义的行为。

为了提高理论上的可移植性,请使用 INT_MIN 代替数字,并告诉我们您在哪里找到了一台非 2 的补码机器来测试它。


需要明确的是,最后一段部分是一个笑话。如果您的意思是“最小的int”,INT_MIN 绝对是要走的路,因为int 的大小各不相同。例如,仍然有很多 16 位实现。写出 -231 仅在您绝对始终准确地表示该值时才有用,在这种情况下,您可能会使用固定大小的类型,例如 int32_t 而不是 int

您可能需要一些替代方法来写出十进制数字,以便那些可能没有注意到21474836482174483648 之间区别的人更清楚,但您需要小心。

如上所述,在 32 位 2 的补码机器上,(int)(-2147483648) 不会溢出,因此是明确定义的,因为 -2147483648 将被视为更广泛的有符号类型。但是,(int)(-0x80000000) 并非如此。 0x80000000 将被视为unsigned int(因为它适合无符号表示); -0x80000000 是明确定义的(但如果 int 是 32 位,则 - 无效),结果 unsigned int 0x80000000int 的转换涉及溢出。为避免溢出,您需要将十六进制常量转换为有符号类型:(int)(-(long long)(0x80000000))

同样,如果你想使用左移运算符,你也需要小心。 1<<31 是具有 32 位(或更小)ints 的 32 位机器上的未定义行为;如果int 至少为 33 位,它只会计算为 231,因为仅当 k 严格小于非左侧参数的整数类型的符号位。

1LL<<31是安全的,因为long long int需要能够表示263-1,所以它的位大小必须大于32。所以形式

(int)(-(1LL<<31))

可能是最易读的。 YMMV。


对于任何通过的学究,这个问题被标记为 C,并且最新的 C 草案 (n1570.pdf) 说,关于 E1 &lt;&lt; E2,其中 E1 有一个带符号的类型,该值仅在 @ 987654362@ 是非负数,E1 × 2<sup>E2</sup>“可在结果类型中表示”。 (§6.5.7 第 4 段)。

这与 C++ 不同,在 C++ 中,如果 E1 是非负数且 E1 × 2<sup>E2</sup> "是可表示的,则定义左移运算符的应用 在结果类型的相应无符号类型中”(第 5.8 节第 2 段,强调添加)。

在 C++ 中,根据最新的草案标准,如果值不能在目标类型中表示(第 4.7 段),则将整数值转换为有符号整数类型是实现定义的 3). C 标准的相应段落——第 6.3.1.3 段。 3 -- 表示“结果要么是实现定义的,要么是实现定义的信号被引发”。)

【讨论】:

定义INT_MIN 的常规方法是#define INT_MIN (-(INT_MAX)-1),(这避免了您描述的尝试取负数的问题。(reference) @abelenky:我知道INT_MIN 的传统定义是在2s 补码机器上。否则,我认为将其定义为(-INT_MAX) 是有意义的,因为只键入一次数字 2147483647 是有意义的。但有人(合理地)建议用-2147483647-1 调用 printf 有点奇怪。当然,既然我们已经进行了讨论,我们知道您为什么必须这样做。我只建议使用显式转换让您以通常由既不是编译器也不是语言律师的人编写的形式编写文字数字:)

以上是关于为啥最小的 int -2147483648 的类型为“long”? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

C语言int 取最小值为啥是-32768而不能是-32769

为啥C语言中bool型变量占用一个字节

为啥函数 `memchr()` 使用 `int` 作为 `char` 类型的参数?

如图,c++里为啥template还能这样用啊?为啥template尖括号里有变量?(template<int x,int y>)

为啥不能比较两个 int 类型变量的值? [复制]

Java中short类型想加为啥会成为int型