为什么 C 编译器在为有符号类型分配过高的整数值时不发出警告?
Why do C compilers not warn when assigning integer value too high for signed type?
(假设是 64 位机器)
例如
int n = 0xFFFFFFFF; //max 32bit unsigned number
printf("%u\n", n);
一个正则有符号整数(32位)可以存储的最大正数是0x7FFFFFFF
。
在上面的示例中,我将最大 unsigned 整数值分配给一个常规有符号整数,我没有收到来自 GCC 的警告或错误,并且打印了结果没有问题(-Wall -Wextra
)。
将 U
或 L
附加到十六进制常量不会改变任何内容。
这是为什么?
0xFFFFFFFF
,在 unsigned
的最大值为 232-1 的平台上,将具有 unsigned
类型到标准的“6.4.4.1 整数常量”。
然后我们进行转换:
6.3.1.3 Signed and unsigned integers
1 When a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new type, it is unchanged.
2 Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.60)
3 Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.
因此,结果是实现定义的或引发实现定义的信号。
现在,您使用 %u
格式打印 int
,这完全不匹配。虽然严格来说这是 UB,但假设您有 2s 补码并且原始赋值使用了环绕,您可能会得到原始常量。
C 标准未指定行为,但要求实现指定它。 GCC always uses 2's complement representation and converts via truncation,因此 int32_t i = 0xFFFFFFFF;
将导致 i
在使用 GCC 编译时被设置为 -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
已签名的实现。
假设 int
和 unsigned int
是 32 位,这在您可能使用的大多数平台(32 位和 64 位系统)上都是这种情况。那么常量 0xFFFFFFFF
的类型是 unsigned int
,值为 4294967295.
这个:
int n = 0xFFFFFFFF;
将该值从 unsigned int
隐式转换为 int
。转换的结果是实现定义的;没有未定义的行为。 (原则上,它也可能导致引发实现定义的信号,但我知道没有实现会那样做)。
存储在 n
中的值很可能是 -1
。
printf("%u\n", n);
这里你使用了一个%u
格式说明符,它需要一个unsigned int
类型的参数,但是你给它传递了一个int
类型的参数。该标准表示相应的有符号和无符号类型的值可以作为函数参数互换,但仅适用于两种类型范围内的值,这里不是这种情况。
此调用不执行从 int
到 unsigned int
的转换。相反,int
值被传递给 printf
, 假定 它收到的值是 unsigned int
类型。行为未定义。 (再次提醒,这是一个合理的警告。)
最可能的结果是 -1
的 int
值(假设 2 的补码)与 0xFFFFFFFF
具有相同的表示形式,将被视为0xFFFFFFFF
的 unsigned int
值,以十进制打印为 4294967295
.
您可以使用 -Wconversion
或 -Wsign-conversion
选项在 int n = 0xFFFFFFFF;
上获得警告。 -Wextra
或 -Wall
中不包含这些选项。 (你必须问 gcc 维护者为什么。)
我不知道会在 printf
调用时引起警告的选项。
(当然,解决方法是将 n
定义为 unsigned int
,这样一切都正确且一致。)
(假设是 64 位机器)
例如
int n = 0xFFFFFFFF; //max 32bit unsigned number
printf("%u\n", n);
一个正则有符号整数(32位)可以存储的最大正数是0x7FFFFFFF
。
在上面的示例中,我将最大 unsigned 整数值分配给一个常规有符号整数,我没有收到来自 GCC 的警告或错误,并且打印了结果没有问题(-Wall -Wextra
)。
将 U
或 L
附加到十六进制常量不会改变任何内容。
这是为什么?
0xFFFFFFFF
,在 unsigned
的最大值为 232-1 的平台上,将具有 unsigned
类型到标准的“6.4.4.1 整数常量”。
然后我们进行转换:
6.3.1.3 Signed and unsigned integers
1 When a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new type, it is unchanged.
2 Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.60)
3 Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.
因此,结果是实现定义的或引发实现定义的信号。
现在,您使用 %u
格式打印 int
,这完全不匹配。虽然严格来说这是 UB,但假设您有 2s 补码并且原始赋值使用了环绕,您可能会得到原始常量。
C 标准未指定行为,但要求实现指定它。 GCC always uses 2's complement representation and converts via truncation,因此 int32_t i = 0xFFFFFFFF;
将导致 i
在使用 GCC 编译时被设置为 -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
已签名的实现。
假设 int
和 unsigned int
是 32 位,这在您可能使用的大多数平台(32 位和 64 位系统)上都是这种情况。那么常量 0xFFFFFFFF
的类型是 unsigned int
,值为 4294967295.
这个:
int n = 0xFFFFFFFF;
将该值从 unsigned int
隐式转换为 int
。转换的结果是实现定义的;没有未定义的行为。 (原则上,它也可能导致引发实现定义的信号,但我知道没有实现会那样做)。
存储在 n
中的值很可能是 -1
。
printf("%u\n", n);
这里你使用了一个%u
格式说明符,它需要一个unsigned int
类型的参数,但是你给它传递了一个int
类型的参数。该标准表示相应的有符号和无符号类型的值可以作为函数参数互换,但仅适用于两种类型范围内的值,这里不是这种情况。
此调用不执行从 int
到 unsigned int
的转换。相反,int
值被传递给 printf
, 假定 它收到的值是 unsigned int
类型。行为未定义。 (再次提醒,这是一个合理的警告。)
最可能的结果是 -1
的 int
值(假设 2 的补码)与 0xFFFFFFFF
具有相同的表示形式,将被视为0xFFFFFFFF
的 unsigned int
值,以十进制打印为 4294967295
.
您可以使用 -Wconversion
或 -Wsign-conversion
选项在 int n = 0xFFFFFFFF;
上获得警告。 -Wextra
或 -Wall
中不包含这些选项。 (你必须问 gcc 维护者为什么。)
我不知道会在 printf
调用时引起警告的选项。
(当然,解决方法是将 n
定义为 unsigned int
,这样一切都正确且一致。)