【问题标题】:How to determine size limits of variable?如何确定变量的大小限制?
【发布时间】:2015-07-18 17:13:43
【问题描述】:

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

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

  1. 将 0 转为二进制:0000 0000(我假设 char 存储在 8 位)
  2. 否定它:1111 1111(现在我的字符大小已经用完了)
  3. 向右移动一位:0111 1111(我得到 127,这似乎是正确的)

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

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

如果您能对这个问题提供更复杂的解释,我将不胜感激。

【问题讨论】:

  • 数据类型的最小值和最大值在<limits.h>中定义。
  • Er... 当表达式 ~ 0 >> 1 不包含对类型 char 的单个引用时,您究竟希望如何使用 ~ 0 >> 1 来获得 char 的最大值? ??为什么~ 0 >> 1 应该给你char 的最大值,而不是shortlong 的最大值?
  • @AnT:这没什么区别,因为表达式将始终以int 执行(除非任何操作数具有更高的等级)。
  • @Olaf:这不是我的意思。差异或没有差异,表达式不以任何方式绑定到所需的目标类型。 OP 应该很明显(不管语言的任何其他规则),表达式不能神奇地“知道”OP 所考虑的目标类型。它不能产生char 的最大值,除非它以某种方式明确或隐含地提及char

标签: c variables limits


【解决方案1】:

请不要使用这些技巧。它们可能在普通机器上工作,但它们可能不便携且难以理解。相反,请使用头文件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 中,其中c 的类型为char,编译器会在c 加一之前插入从charint隐式转换。因此,像

这样的语句
c = c + 1;

实际上在 C 中的行为是这样的:

c = (char)((int)c + 1);

因此,~0 >> 1 在通常的 32 位架构上实际上计算为 0xffffffff (-1),因为类型 int 通常具有 32 位,而有符号类型的右移通常会移动符号位,因此最高有效位变为一个。转换为unsigned char 导致截断,结果为0xff (255)。除了printf 的第一个参数之外的所有参数都是变量参数列表 的一部分,这有点复杂,但基本上意味着所有类型小于int 都转换为@ 987654352@、float 转换为double,其他类型保持不变。

现在,我们如何才能做到这一点?在具有二进制补码且没有填充位的普通机器上,假设sizeof (char) < sizeof (int),可以使用这样的表达式来计算最大和最小char

(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

请注意,所有这些技巧不应出现在可移植代码中。

【讨论】:

  • 在我的机器上,intmax_tsize_tstdint.h 中声明。
  • @cad 类型stdint.h中声明,但大小限制limits.h中。
  • @cad: size_t 实际上应该在stddef.h 中定义。
  • 我知道这一点,但这是一种理解变量大小的练习。
  • 只是补充一点,如果启用了推荐的警告(这里:-Wconversion 用于 gcc),编译器应该抱怨在将整数表达式分配给(很可能)较小的类型(如 @987654370)时可能出现截断@ 没有显式转换。
【解决方案2】:

您的行为的确切效果与您的假设不同。

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 &gt;&gt; 1 仍然会给你1111 1111 1111 1111 1111 1111 1111 1111

请注意,即使您在将负值右移时将0 移入符号位的平台上执行此操作,您仍将获得0111 1111 1111 1111 1111 1111 1111 1111,这通常不是最大值char 你试图获得。

如果您想确保右移操作从左侧移入 0 位,您必须 1) 移动 unsigned 位模式或 2) 移动有符号,但为正位模式。对于负位模式,您可能会遇到符号扩展行为,这意味着对于负值,1 位将从左侧移入,而不是 0 位。

由于 C 语言没有可在 [unsigned/signed] char 类型的域中工作的移位(操作数在移位之前无论如何都会提升为 int),您可以做的是确保您正在移位一个正数 @ 987654342@ 值并确保您的初始位掩码中包含正确数量的1s。这正是您使用(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,这正是您想要获得的。

【讨论】:

    【解决方案3】:

    这只适用于无符号整数。对于有符号整数,负数右移和按位反转的行为是实现定义。它不仅取决于负值的表示,还取决于编译器用于执行右移的 CPU 指令(例如,某些 CPU 没有算术(右)移位。

    因此,除非您为您的实现设置额外的约束,否则无法确定有符号整数的限制。这意味着没有完全可移植的方式(对于有符号整数)。

    请注意,char 是有符号还是无符号也是实现定义的,并且 (unsigned char)(~0) &gt;&gt; 1整数提升的约束,因此它不会产生字符结果,而是产生 int。 (这使得格式说明符正确 - 尽管可能是无意的)。

    使用limits.h 获取实现的整数限制的宏。该文件必须由任何符合标准的 C 编译器提供。

    【讨论】:

      猜你喜欢
      • 2019-12-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-28
      • 1970-01-01
      • 2018-12-01
      • 2012-06-17
      • 2019-03-22
      相关资源
      最近更新 更多