有没有办法在编译时计算整数类型的宽度?

Posted

技术标签:

【中文标题】有没有办法在编译时计算整数类型的宽度?【英文标题】:Is there any way to compute the width of an integer type at compile-time? 【发布时间】:2011-04-26 19:33:13 【问题描述】:

char/bytes 为单位的整数类型(或任何类型)的大小很容易计算为sizeof(type)。一个常见的习惯用法是乘以 CHAR_BIT 以找到该类型占用的位数,但在使用填充位的实现中,这将不等于值位中的 width。更糟糕的是,代码如下:

x>>CHAR_BIT*sizeof(type)-1

如果CHAR_BIT*sizeof(type) 大于type 的实际宽度,则实际上可能具有未定义的行为。

为简单起见,我们假设我们的类型是无符号的。那么type的宽度就是ceil(log2((type)-1)。有没有办法将此值计算为常量表达式?

【问题讨论】:

是否有有填充位的实现? @sbi:如果任何类型都允许填充位,那么实际上是否存在这样的实现并不重要。如果你想写portableconforming代码,那你别无选择。当然,您可以为自己重新定义“便携”为“到任何没有填充位的系统”。只需将其记录好,以便在它突然不再起作用时记住它。 @sbi:每个 C99 实现都有一个带有填充位的类型,即_Bool。它的最大值是1,它的大小也至少是1。所以它有CHAR_BIT - 1 填充位。 @sbi:整数类型的宽度是标准中定义明确的术语。它与它的大小(即它的存储要求)有明显的区别。 如果您查看 my question that you commented on 2010 年 12 月 29 日的编辑,我发现一个类似函数的宏在 comp.lang.c 中搜索,它应该为宽度创建一个编译时常量(定义作为值位和符号位(如果适用)。如前所述,您必须知道类型的 MAX 才能使用宏。我希望这是有帮助的。我也回复了你在a different question I had about padding bits 上的评论。我想知道您是否不同意我的想法,如果可以,您可以在那里回复。谢谢 【参考方案1】:

有一个类似函数的宏可以确定整数类型的值位,但前提是您已经知道该类型的最大值。您是否会获得编译时常量取决于您的编译器,但我猜在大多数情况下答案是肯定的。

感谢 Hallvard B. Furuseth 在回复 question on comp.lang.c 时发布的类似 IMAX_BITS() 函数的宏

/* Number of bits in inttype_MAX, or in any (1<<b)-1 where 0 <= b < 3E+10 */
#define IMAX_BITS(m) ((m) /((m)%0x3fffffffL+1) /0x3fffffffL %0x3fffffffL *30 \
                  + (m)%0x3fffffffL /((m)%31+1)/31%31*5 + 4-12/((m)%31+3))

IMAX_BITS(INT_MAX) 计算 int 中的位数,而 IMAX_BITS((unsigned_type)-1) 计算 unsigned_type 中的位数。无论如何,直到有人实现 4 GB 整数:-)

感谢 Eric Sosman 这个 [替代版本](http://groups.google.com/group/comp.lang.c/msg/e998153ef07ff04b?dmode=source) 应该可以使用 less超过 2040 位: **(编辑 2011 年 1 月 3 日晚上 11:30 EST:原来这个版本也是由 Hallvard B. Furuseth 编写的)**
/* Number of bits in inttype_MAX, or in any (1<<k)-1 where 0 <= k < 2040 */
#define IMAX_BITS(m) ((m)/((m)%255+1) / 255%255*8 + 7-86/((m)%255+12))
**请记住,尽管无符号整数类型的宽度等于值位数,但有符号整数类型的宽度要大一(第 6.2.6.2/6 节)。** 这在我的文章中特别重要对您的问题的原始评论我错误地指出 IMAX_BITS() 宏在实际计算值位数时计算宽度。对于那个很抱歉!

因此,例如IMAX_BITS(INT64_MAX) 将创建一个 63 的编译时常量。但是,在此示例中,我们正在处理有符号类型,因此如果您想要实际宽度,则必须添加 1 来说明符号位一个 int64_t,当然是 64。

在单独的 comp.lang.c 讨论中,名为 blargg 的用户详细介绍了宏的工作原理:Re: using pre-processor to count bits in integer types...

请注意,宏仅适用于 2^n-1 个值(即二进制中的所有 1),正如任何 MAX 值所预期的那样。另请注意,虽然很容易获得无符号整数类型 (IMAX_BITS((unsigned type)-1)) 的最大值的编译时常量,但在撰写本文时,我不知道有任何方法可以为有符号整数类型做同样的事情不调用实现定义行为的整数类型。如果我发现我会在这里回答我自己的相关 SO 问题:C question: off_t (and other signed integer types) minimum and maximum values - Stack Overflow

【讨论】:

对于有符号类型,实现定义行为相关的方法是什么?我认为这取决于将无符号值转换为有符号值,但很高兴看到你得到了什么以及它依赖于什么假设。 顺便说一句,这个宏的结果是C语言定义为常量表达式,只要参数m是常量表达式。 @R resigned types:我想出的方法是,如果我们从有符号整数类型中的值 1 开始并不断乘以 2 直到值变为负,我们可以确定值位的数量。实现定义的是该值是否实际上会变为负数。自从我问了我的问题后,我搜索并发现了一些有趣的宏,它们使用按位运算顺序检查有符号类型中的位,但这不是可移植的(反正我自己的例子不是这样)。我将在另一台计算机上搜索我的历史记录以获取一两个链接。 你好。我在上面之前的评论中说错了,因为显然整数溢出行为是未定义的,而不是实现定义的。尽我所能,这意味着在整数溢出的情况下,实现不必记录其行为(甚至根本没有任何行为)。对于我找不到的其他宏,我已经丢弃了它们,因为最容易描述为不合规的行为,或者在技术上根据 §6.5.7/4,5,按位移位运算符已定义、未定义或实现定义的行为取决于签名类型的值。 奇怪的是,for (int i = 0; m &gt;&gt; i; i++) IMAX_BITS 生成的优化程序集与 gcc7 相同。【参考方案2】:

一般的观察是,如果您在计算中依赖数据类型的宽度,则应使用&lt;stdint.h&gt; 中定义的显式宽度数据类型,例如uint32_t.

试图计算标准类型中的字节数是在问你的“可移植”代码在发生溢出时会做什么。

【讨论】:

我不同意。例如,在实现位数组时要做的一件非常合理的事情是假设unsignedsize_t 是一个有效的大小,然后您想知道每个“字”的位数" 商店。 另外,正如有人指出的那样,uint32_t 的等级可能低于int,这使其成为自动提升为 signed 类型的候选者,导致所有各种破坏,包括未定义的行为。 (它甚至可能比 int 更窄……)为了安全起见,我目前正在从所有 &lt;stdint.h&gt; 类型(,uintmax_t 除外)中更改我的所有代码远离【参考方案3】:

你可以在运行时用一个简单的循环来计算它,定义明确并且没有 UB 的危险:

unsigned int u;
int c;

for (c=0, u=1; u; c++, u<<=1);

total_bits   = CHAR_BIT * sizeof(unsigned int);
value_bits   = c;
padding_bits = total_bits - value_bits;

最简单的方法是检查您的单元测试(您拥有它们,对吗?) value_bits 与您当前的 INT_WIDTH 定义相同。

如果您真的需要在编译时计算它,我会使用给定的 #if-#elif 级联之一,测试 UINT_MAX 或您的目标系统。

你需要它做什么?也许是 YAGNI?

【讨论】:

我想到的示例用法是实现一个位数组,该数组使用一种预期高效的类型,例如unsigned intsize_t【参考方案4】:

第一种方法,如果您知道自己的标准类型(因此您的类型不是 typedef),请使用 UINT_MAX 宏并检查可能的大小。

如果你没有这个,对于无符号类型,这在概念上相对容易。对于您最喜欢的类型T,只需执行(T)-1 并执行一个怪物测试宏,使用?: 检查所有可能的值。由于这些只是编译时常量表达式,因此任何体面的编译器都会对其进行优化,只留下您感兴趣的值。

由于类型转换,这在 #if 等中不起作用,但这不能通过简单的方式避免。

对于有符号类型,这更复杂。对于至少与int 一样宽的类型,您可以希望做一个技巧来提升到相应的无符号类型并获得该类型的宽度。但是要知道您的签名类型是否只有一个值少一点,不,我不认为有一个通用表达式可以知道这一点。

编辑: 只是为了说明这一点,我摘录了一些你的内容 可以使这种方法(对于无符号类型)也不会生成 P99 中的大表达式我有类似的东西

#ifndef P99_HIGH2
# if P99_UINTMAX_WIDTH == 64
#  define P99_HIGH2(X)                                         \
((((X) & P00_B0) ? P00_S0 : 0u)                              \
 | (((X) & P00_B1) ? P00_S1 : 0u)                            \
 | (((X) & P00_B2) ? P00_S2 : 0u)                            \
 | (((X) & P00_B3) ? P00_S3 : 0u)                            \
 | (((X) & P00_B4) ? P00_S4 : 0u)                            \
 | (((X) & P00_B5) ? P00_S5 : 0u))
# endif
#endif
#ifndef P99_HIGH2
# if P99_UINTMAX_WIDTH <= 128
#  define P99_HIGH2(X)                                         \
((((X) & P00_B0) ? P00_S0 : 0u)                              \
 | (((X) & P00_B1) ? P00_S1 : 0u)                            \
 | (((X) & P00_B2) ? P00_S2 : 0u)                            \
 | (((X) & P00_B3) ? P00_S3 : 0u)                            \
 | (((X) & P00_B4) ? P00_S4 : 0u)                            \
 | (((X) & P00_B5) ? P00_S5 : 0u)                            \
 | (((X) & P00_B6) ? P00_S6 : 0u))
# endif
#endif

其中魔术常量是用#if 序列定义的 开始。重要的是不要暴露太大的常数 对于无法处理它们的编译器。

/* The preprocessor always computes with the precision of uintmax_t */
/* so for the preprocessor this is equivalent to UINTMAX_MAX       */
#define P00_UNSIGNED_MAX ~0u

#define P00_S0 0x01
#define P00_S1 0x02
#define P00_S2 0x04
#define P00_S3 0x08
#define P00_S4 0x10
#define P00_S5 0x20
#define P00_S6 0x40

/* This has to be such ugly #if/#else to ensure that the            */
/* preprocessor never sees a constant that is too large.            */
#ifndef P99_UINTMAX_MAX
# if P00_UNSIGNED_MAX == 0xFFFFFFFFFFFFFFFF
#  define P99_UINTMAX_WIDTH 64
#  define P99_UINTMAX_MAX 0xFFFFFFFFFFFFFFFFU
#  define P00_B0 0xAAAAAAAAAAAAAAAAU
#  define P00_B1 0xCCCCCCCCCCCCCCCCU
#  define P00_B2 0xF0F0F0F0F0F0F0F0U
#  define P00_B3 0xFF00FF00FF00FF00U
#  define P00_B4 0xFFFF0000FFFF0000U
#  define P00_B5 0xFFFFFFFF00000000U
#  define P00_B6 0x0U
# endif /* P00_UNSIGNED_MAX */
#endif /* P99_UINTMAX_MAX */
#ifndef P99_UINTMAX_MAX
# if P00_UNSIGNED_MAX == 0x1FFFFFFFFFFFFFFFF
#  define P99_UINTMAX_WIDTH 65
#  define P99_UINTMAX_MAX 0x1FFFFFFFFFFFFFFFFU
#  define P00_B0 0xAAAAAAAAAAAAAAAAU
#  define P00_B1 0xCCCCCCCCCCCCCCCCU
#  define P00_B2 0xF0F0F0F0F0F0F0F0U
#  define P00_B3 0xFF00FF00FF00FF00U
#  define P00_B4 0xFFFF0000FFFF0000U
#  define P00_B5 0xFFFFFFFF00000000U
#  define P00_B6 0x10000000000000000U
# endif /* P00_UNSIGNED_MAX */
#endif /* P99_UINTMAX_MAX */
.
.
.

【讨论】:

据我所知,有符号类型总是比相应的无符号类型少一个值。这是表示与有符号类型的正值兼容的结果,以及仅允许二进制补码、一个补码和符号/大小作为有符号表示的限制。 @R.,在我知道你的所有实际情况下,你都是对的,但如果你只考虑那个条件(正符号 -> 无符号),那么有符号类型完全有可能更少值位。 C99 标准明确允许这样做,它只要求有符号类型的值位不比无符号类型多。所以我的猜测是,委员会中至少有一个实施者否决了一个更清洁的解决方案,这反过来暗示着这样一个奇怪的事情存在于某个地方。 是的,重要的部分是“没有更多的价值位”。有符号类型甚至被允许具有与无符号类型相同数量的值位,加上符号位。所以无符号可以有 32 位和有符号 33 位。 @Secure:是的,我想这就是我想说的。 (不过,有符号和无符号的 31 位值比 32 位更有可能;-) 我试图说明的一点是,如果不检查 MAX 宏,您将无法知道有符号整数类型的最大值。没有这样的表达式可以保证在所有情况下都有效。【参考方案5】:

&lt;limits.h&gt; 中的宏与特定整数宽度的已知最大值进行比较:

#include <limits.h>

#if UINT_MAX == 0xFFFF
#define INT_WIDTH 16
#elif UINT_MAX == 0xFFFFFF
#define INT_WIDTH 24
#elif ...
#else
#error "unsupported integer width"
#endif

【讨论】:

【参考方案6】:

通常,int 的大小对于给定的编译器/平台是已知的。如果您有识别编译器/平台的宏,那么您可以使用它们来有条件地定义INT_WIDTH

您可以查看&lt;sys/types.h&gt; 及其依赖项作为示例。

【讨论】:

【参考方案7】:

是的,因为出于所有实际目的,可能的宽度数量是有限的:

#if ~0 == 0xFFFF
# define INT_WIDTH 16
#elif ~0 == 0xFFFFFFFF
# define INT_WIDTH 32
#else
# define INT_WIDTH 64
#endif

【讨论】:

你保证预处理器使用的整数类型和编译代码的整数类型是一样的吗?我不知道标准是不是这样。 @Paul:好点子。我的直觉是除非您开始使用标志来切换 int 宽度,否则您应该是安全的(例如“当 16 位为默认值时使用 32 位整数”)。 @Paul:参见 C99 6.10.1 §4:“出于此令牌转换和评估的目的,所有有符号整数类型和所有无符号整数类型的行为就好像它们分别具有相同的表示形式, 类型 intmax_tuintmax_t 定义在标题 &lt;stdint.h&gt;";改用来自&lt;limits.h&gt;_MAX @Aaron,由于 Christoph 引用的原因,您的预处理器方法只能用于确定 uintmax_t 的宽度,无论如何它的宽度至少为 64,因此您的方法不会不幸的是,t领先。在任何情况下,您都不能将其用于有符号类型,因为您无法知道有符号和无符号类型之间的关系。 @Aaron,对于您的回复@Secure:如果您认为您始终知道您的代码将在哪个平台上执行,那您很好。如果您想编写最终可能会在嵌入式设备上使用的可移植代码,您最好不要下注。

以上是关于有没有办法在编译时计算整数类型的宽度?的主要内容,如果未能解决你的问题,请参考以下文章

C++模板元编程深度解析:探索编译时计算的神奇之旅

文字的算术运算是在编译时还是运行时计算的?

在编译时计算一组常量表达式的最大值

由于编译时计算的值,如何避免“无法访问的语句”?

sizeof和strlen的区别

关于strlen和sizeof的使用