如何确定变量的大小限制?
How to determine size limits of variable?
我正在努力确定每个变量的边缘大小。看不懂下面的问题
例如,要获取 char 的最大值,我使用:~ 0 >> 1
哪个应该像这样工作:
- transfer 0 to binary: 0000 0000 (我假设char存储在8
位)
- 否定它:1111 1111(现在我超出了 char max 大小)
- 向右移动一位:0111 1111(我得到127似乎是正确的)
现在我想用 printf
函数显示这个结果。
为什么我必须像这样使用 cast:
printf("%d\n", (unsigned char)(~0) >> 1)
?
我只是不明白。当我超出字符范围时,我认为它与第 2 点有关,但我不确定。
如果你能给我更复杂的解释来解决这个问题,我将不胜感激。
请不要使用这些技巧。它们可能在普通机器上工作,但它们可能不可移植且难以理解。相反,使用头文件 limits.h
中的符号常量,其中包含每个基本类型的大小限制。例如,CHAR_MAX
是 char
的上限,CHAR_MIN
是下限。 stddef.h
和 stdint.h
中声明的数字类型的更多限制可以在 stdint.h
.
中找到
现在回答您的问题:默认情况下,对 int
类型的值进行算术运算,除非您使涉及的操作数具有不同的类型。发生这种情况的原因有很多,例如涉及的变量之一具有不同的类型,或者您使用了不同类型的迭代(例如 1.0
或 1L
或 1U
)。更重要的是,算术表达式的类型由内向外提升。因此,在声明中
char c = 1 + 2 + 3;
表达式 1 + 2 + 3
被评估为类型 int
并且仅在分配之前立即转换为 char
。更重要的是,在C语言中,不能对小于int
的类型进行算术运算。例如,在表达式 c + 1
中,其中 c
是 char
类型,编译器插入一个 隐式转换 从 char
到 int
加一到 c
之前。因此,像
这样的语句
c = c + 1;
在 C:
中实际上是这样的
c = (char)((int)c + 1);
因此,~0 >> 1
实际上在通常的 32 位体系结构上计算为 0xffffffff
(-1),因为类型 int
通常具有 32 位并且有符号类型的右移通常会移位符号位,因此最高有效位变为 1。转换为 unsigned char
导致 截断 ,结果为 0xff
(255)。除了第一个 printf
之外的所有参数都是 可变参数列表 的一部分,这有点复杂但基本上意味着所有类型 更小 比 int
转换为 int
,float
转换为 double
,所有其他类型保持不变。
现在,我们怎样才能做到这一点?在一台有二进制补码且没有填充位的普通机器上,可以使用像这样的表达式来计算最大和最小的 char
,假设 sizeof (char) < sizeof (int)
:
(1 << CHAR_BIT - 1) - 1; /* largest char */
-(1 << CHAR_BIT - 1); /* smallest char */
对于其他类型,这会稍微困难一些,因为我们需要避免溢出。这是一个适用于普通机器上所有带符号整数类型的表达式,其中 type
是您想要限制的类型:
(type)(((uintmax_t)1 << sizeof (type) * CHAR_BIT - 1) - 1) /* largest */
(type)-((uintmax_t)1 << sizeof (type) * CHAR_BIT - 1) /* smallest */
对于无符号类型 type
,您可以使用它来获得最大值:
~(type)0
请注意,所有这些技巧都不应出现在可移植代码中。
这只适用于无符号整数。对于有符号整数,右移负数和按位反转的行为是实现定义的。它不仅取决于负值的表示,还取决于编译器用来执行右移的 CPU 指令(例如,某些 CPU 没有算术(右)移位。
因此,除非您对您的实施做出额外的限制,否则无法确定有符号整数的限制。这意味着没有完全可移植的方式(对于有符号整数)。
请注意,char
是有符号的还是无符号的也是实现定义的,并且 (unsigned char)(~0) >> 1
受 整数提升 的约束,因此它不会产生字符结果,而是 int
。 (这使得格式说明符正确 - 尽管可能是无意的)。
使用 limits.h
获取实现的整数限制的宏。此文件必须由任何符合标准的 C 编译器提供。
您的行为的确切效果与您假设的不同。
0
不是 0000 0000
。 0
的类型为 int
,这意味着它很可能是 0000 0000 0000 0000 0000 0000 0000 0000
,具体取决于您的平台上有多少位 int
。 (我假设是 32 位 int
。)
现在,~0
应该是 1111 1111 1111 1111 1111 1111 1111 1111
,它的类型仍然是 int
,并且是一个负值。
向右移动时,结果由实现定义。在 C 中右移负符号整数值并不能保证您将在符号位中获得 0
。恰恰相反,大多数平台在右移时实际上会复制符号位。这意味着 ~0 >> 1
仍然会给你 1111 1111 1111 1111 1111 1111 1111 1111
.
请注意,即使您在右移负值时将 0
移入符号位的平台上执行此操作,您仍将获得 0111 1111 1111 1111 1111 1111 1111 1111
,这是在一般情况下不是您尝试获得的 char
的最大值。
如果你想确保右移操作从左边移入 0
位,你必须 1) 移动一个 unsigned 位-pattern 或 2) 移位 有符号但为正的 位模式。对于负位模式,您可能会 运行 进入符号扩展行为,这意味着对于负值 1
位将从左侧移入而不是 0
位。
由于 C 语言没有可以在 [unsigned/signed] char
类型的域中工作的移位(操作数在移位之前被提升为 int
),您可以做的是确保您正在移动一个正的 int
值,并确保您的初始位掩码中包含正确数量的 1
。这正是您使用 (unsigned char) ~0
作为初始掩码所实现的。 (unsigned char) ~0
将作为类型 int
等于 0000 0000 0000 0000 0000 0000 1111 1111
的值参与移位(假设 8 位 char
)。转变后你将获得0000 0000 0000 0000 0000 0000 0111 1111
,这正是你想要获得的
我正在努力确定每个变量的边缘大小。看不懂下面的问题
例如,要获取 char 的最大值,我使用:~ 0 >> 1
哪个应该像这样工作:
- transfer 0 to binary: 0000 0000 (我假设char存储在8 位)
- 否定它:1111 1111(现在我超出了 char max 大小)
- 向右移动一位:0111 1111(我得到127似乎是正确的)
现在我想用 printf
函数显示这个结果。
为什么我必须像这样使用 cast:
printf("%d\n", (unsigned char)(~0) >> 1)
?
我只是不明白。当我超出字符范围时,我认为它与第 2 点有关,但我不确定。
如果你能给我更复杂的解释来解决这个问题,我将不胜感激。
请不要使用这些技巧。它们可能在普通机器上工作,但它们可能不可移植且难以理解。相反,使用头文件 limits.h
中的符号常量,其中包含每个基本类型的大小限制。例如,CHAR_MAX
是 char
的上限,CHAR_MIN
是下限。 stddef.h
和 stdint.h
中声明的数字类型的更多限制可以在 stdint.h
.
现在回答您的问题:默认情况下,对 int
类型的值进行算术运算,除非您使涉及的操作数具有不同的类型。发生这种情况的原因有很多,例如涉及的变量之一具有不同的类型,或者您使用了不同类型的迭代(例如 1.0
或 1L
或 1U
)。更重要的是,算术表达式的类型由内向外提升。因此,在声明中
char c = 1 + 2 + 3;
表达式 1 + 2 + 3
被评估为类型 int
并且仅在分配之前立即转换为 char
。更重要的是,在C语言中,不能对小于int
的类型进行算术运算。例如,在表达式 c + 1
中,其中 c
是 char
类型,编译器插入一个 隐式转换 从 char
到 int
加一到 c
之前。因此,像
c = c + 1;
在 C:
中实际上是这样的c = (char)((int)c + 1);
因此,~0 >> 1
实际上在通常的 32 位体系结构上计算为 0xffffffff
(-1),因为类型 int
通常具有 32 位并且有符号类型的右移通常会移位符号位,因此最高有效位变为 1。转换为 unsigned char
导致 截断 ,结果为 0xff
(255)。除了第一个 printf
之外的所有参数都是 可变参数列表 的一部分,这有点复杂但基本上意味着所有类型 更小 比 int
转换为 int
,float
转换为 double
,所有其他类型保持不变。
现在,我们怎样才能做到这一点?在一台有二进制补码且没有填充位的普通机器上,可以使用像这样的表达式来计算最大和最小的 char
,假设 sizeof (char) < sizeof (int)
:
(1 << CHAR_BIT - 1) - 1; /* largest char */
-(1 << CHAR_BIT - 1); /* smallest char */
对于其他类型,这会稍微困难一些,因为我们需要避免溢出。这是一个适用于普通机器上所有带符号整数类型的表达式,其中 type
是您想要限制的类型:
(type)(((uintmax_t)1 << sizeof (type) * CHAR_BIT - 1) - 1) /* largest */
(type)-((uintmax_t)1 << sizeof (type) * CHAR_BIT - 1) /* smallest */
对于无符号类型 type
,您可以使用它来获得最大值:
~(type)0
请注意,所有这些技巧都不应出现在可移植代码中。
这只适用于无符号整数。对于有符号整数,右移负数和按位反转的行为是实现定义的。它不仅取决于负值的表示,还取决于编译器用来执行右移的 CPU 指令(例如,某些 CPU 没有算术(右)移位。
因此,除非您对您的实施做出额外的限制,否则无法确定有符号整数的限制。这意味着没有完全可移植的方式(对于有符号整数)。
请注意,char
是有符号的还是无符号的也是实现定义的,并且 (unsigned char)(~0) >> 1
受 整数提升 的约束,因此它不会产生字符结果,而是 int
。 (这使得格式说明符正确 - 尽管可能是无意的)。
使用 limits.h
获取实现的整数限制的宏。此文件必须由任何符合标准的 C 编译器提供。
您的行为的确切效果与您假设的不同。
0
不是 0000 0000
。 0
的类型为 int
,这意味着它很可能是 0000 0000 0000 0000 0000 0000 0000 0000
,具体取决于您的平台上有多少位 int
。 (我假设是 32 位 int
。)
现在,~0
应该是 1111 1111 1111 1111 1111 1111 1111 1111
,它的类型仍然是 int
,并且是一个负值。
向右移动时,结果由实现定义。在 C 中右移负符号整数值并不能保证您将在符号位中获得 0
。恰恰相反,大多数平台在右移时实际上会复制符号位。这意味着 ~0 >> 1
仍然会给你 1111 1111 1111 1111 1111 1111 1111 1111
.
请注意,即使您在右移负值时将 0
移入符号位的平台上执行此操作,您仍将获得 0111 1111 1111 1111 1111 1111 1111 1111
,这是在一般情况下不是您尝试获得的 char
的最大值。
如果你想确保右移操作从左边移入 0
位,你必须 1) 移动一个 unsigned 位-pattern 或 2) 移位 有符号但为正的 位模式。对于负位模式,您可能会 运行 进入符号扩展行为,这意味着对于负值 1
位将从左侧移入而不是 0
位。
由于 C 语言没有可以在 [unsigned/signed] char
类型的域中工作的移位(操作数在移位之前被提升为 int
),您可以做的是确保您正在移动一个正的 int
值,并确保您的初始位掩码中包含正确数量的 1
。这正是您使用 (unsigned char) ~0
作为初始掩码所实现的。 (unsigned char) ~0
将作为类型 int
等于 0000 0000 0000 0000 0000 0000 1111 1111
的值参与移位(假设 8 位 char
)。转变后你将获得0000 0000 0000 0000 0000 0000 0111 1111
,这正是你想要获得的