如何确定变量的大小限制?

How to determine size limits of variable?

我正在努力确定每个变量的边缘大小。看不懂下面的问题

例如,要获取 char 的最大值,我使用:~ 0 >> 1 哪个应该像这样工作:

  1. transfer 0 to binary: 0000 0000 (我假设char存储在8 位)
  2. 否定它:1111 1111(现在我超出了 char max 大小)
  3. 向右移动一位:0111 1111(我得到127似乎是正确的)

现在我想用 printf 函数显示这个结果。 为什么我必须像这样使用 cast: printf("%d\n", (unsigned char)(~0) >> 1)?

我只是不明白。当我超出字符范围时,我认为它与第 2 点有关,但我不确定。

如果你能给我更复杂的解释来解决这个问题,我将不胜感激。

请不要使用这些技巧。它们可能在普通机器上工作,但它们可能不可移植且难以理解。相反,使用头文件 limits.h 中的符号常量,其中包含每个基本类型的大小限制。例如,CHAR_MAXchar 的上限,CHAR_MIN 是下限。 stddef.hstdint.h 中声明的数字类型的更多限制可以在 stdint.h.

中找到

现在回答您的问题:默认情况下,对 int 类型的值进行算术运算,除非您使涉及的操作数具有不同的类型。发生这种情况的原因有很多,例如涉及的变量之一具有不同的类型,或者您使用了不同类型的迭代(例如 1.01L1U)。更重要的是,算术表达式的类型由内向外提升。因此,在声明中

char c = 1 + 2 + 3;

表达式 1 + 2 + 3 被评估为类型 int 并且仅在分配之前立即转换为 char 。更重要的是,在C语言中,不能对小于int的类型进行算术运算。例如,在表达式 c + 1 中,其中 cchar 类型,编译器插入一个 隐式转换 charint 加一到 c 之前。因此,像

这样的语句
c = c + 1;

在 C:

中实际上是这样的
c = (char)((int)c + 1);

因此,~0 >> 1 实际上在通常的 32 位体系结构上计算为 0xffffffff (-1),因为类型 int 通常具有 32 位并且有符号类型的右移通常会移位符号位,因此最高有效位变为 1。转换为 unsigned char 导致 截断 ,结果为 0xff (255)。除了第一个 printf 之外的所有参数都是 可变参数列表 的一部分,这有点复杂但基本上意味着所有类型 更小 int 转换为 intfloat 转换为 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 00000 的类型为 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,这正是你想要获得的