C问题:off_t(和其他有符号整数类型)的最小值和最大值

我偶尔会遇到一个整数类型(例如POSIX有符号整数类型off_t ),其中有一个宏的最小值和最大值是有帮助的,但我不知道如何制作一个真正可移植的宏。

对于无符号整数类型,我一直认为这很简单。 0表示最小值, ~0表示最大值。 我已经阅读了几个不同的SO线程,建议使用-1而不是-1来实现可移植性。 这里有一个有争议的有趣线程:
c ++ – 使用-1将所有位设置为true是否安全? – 堆栈溢出

然而,即使在阅读了这个问题后,我仍然感到困惑。 另外,我正在寻找兼容C89和C99的东西,所以我不知道是否适用相同的方法。 说我有一种uint_whatever_t 。 难道我不能先转为0然后按位补码? 这样可以吗?

 #define UINT_WHATEVER_T_MAX ( ~ (uint_whatever_t) 0 ) 

有符号的整数类型看起来像是一个更难以破解的坚果。 我已经看到了几种不同的可能解决方案,但只有一种似乎是可移植的。 无论是那个还是不正确的。 我在谷歌搜索OFF_T_MAX和OFF_T_MIN时找到了它。 感谢Christian Biere:

 #define MAX_INT_VAL_STEP(t) \ ((t) 1 << (CHAR_BIT * sizeof(t) - 1 - ((t) -1 < 1))) #define MAX_INT_VAL(t) \ ((MAX_INT_VAL_STEP(t) - 1) + MAX_INT_VAL_STEP(t)) #define MIN_INT_VAL(t) \ ((t) -MAX_INT_VAL(t) - 1) [...] #define OFF_T_MAX MAX_INT_VAL(off_t) 

我找不到关于C89中不同允许类型的有符号整数表示的任何内容,但C99在§J.3.5中有关于整数可移植性问题的注释:

是否使用符号和幅度,二进制补码或一对补码表示有符号整数类型,以及非常值是陷阱表示还是普通值(6.2.6.2)。

这似乎意味着只能使用那三个列出的有符号数字表示。 暗示是否正确,并且上面的宏是否与所有三种表示兼容?


其他想法:
如果存在填充位,似乎函数式宏MAX_INT_VAL_STEP()将给出不正确的结果。 我想知道是否有任何办法解决这个问题。

通过阅读维基百科上的带符号数字表示 ,我发现对于所有三个有符号整数表示,任何有符号整数类型的MAX都将是:
符号位关闭,所有值位都打开(全部三个)
其MIN将是:
符号位,所有值位(符号和幅度)
符号位,所有值位关闭(一个/两个补码)

我想我可以通过这样做来测试符号和幅度:

 #define OFF_T_MIN ( ( ( (off_t)1 | ( ~ (off_t) -1 ) ) != (off_t)1 ) ? /* sign and magnitude minimum value here */ : /* ones and twos complement minimum value here */ ) 

然后,当符号和幅度为符号位,并且所有值位都不是off_t的最小值,在这种情况下是~ (off_t) 0 ? 对于一些/两个补码最小值,我需要一些方法来关闭所有的值位,但保持符号位。 不知道如何在不知道值位数的情况下执行此操作。 标志位是否保证始终比最重要的值位更重要?

谢谢,如果post太长,请告诉我


编辑12/29/2010美国东部时间下午5点
如下所述,通过ephemient得到无符号类型的最大值, (unsigned type)-1比〜0或甚~(unsigned type)0更正确。 从使用-1时我可以收集到的它与0-1相同,它总是导致无符号类型的最大值。

此外,因为可以确定无符号类型的最大值,所以可以确定无符号类型中有多少个值位。 感谢Hallvard B. Furuseth在回复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千兆字节的整数,无论​​如何:-)

然而,我的问题的核心仍然没有答案:如何通过宏确定签名类型的最小值和最大值。 我还在调查这个。 也许答案是没有答案。

如果您在大多数情况下没有在StackOverflow上查看此问题,则在接受之前您无法看到建议的答案。 建议在StackOverflow上查看此问题 。

令人惊讶的是,C在算术运算之前将类型提升为int ,结果至少为int大小。 (同样奇怪的是'a'字符文字,类型为int ,而不是char 。)

 int a = (uint8_t)1 + (uint8_t)-1; /* = (uint8_t)1 + (uint8_t)255 = (int)256 */ int b = (uint8_t)1 + ~(uint8_t)0; /* = (uint8_t)1 + (int)-1 = (int)0 */ 

所以#define UINT_WHATEVER_T_MAX ( ~ (uint_whatever_t) 0 )不一定好。

我相信我终于解决了这个问题,但解决方案只能在configure -time,而不是编译时或运行时使用,所以它仍然不明智。 这里是:

 HEADERS="#include " TYPE="off_t" i=8 while : ; do printf "%s\nstruct { %sx : %d; };\n" "$HEADERS" "$TYPE" $i > test.c $CC $CFLAGS -o /dev/null -c test.c || break i=$(($i+1)) done rm test.c echo $(($i-1)) 

这个想法来自6.7.2.1第3段:

指定位字段宽度的表达式应为整数常量表达式,其非负值不超过将指定的类型的对象的宽度,省略冒号和表达式。 如果该值为零,则声明不应具有声明者。

如果这导致在编译时解决问题的任何想法,我会很高兴。

对于符号量表示,它相当容易(对于类型至少与int一样宽,无论如何):

 #define SM_TYPE_MAX(type) (~(type)-1 + 1) #define SM_TYPE_MIN(type) (-TYPE_MAX(type)) 

不幸的是,地面上的符号幅度表示相当薄;)

您可能希望查看limits.h(在C99中添加)此标头提供应设置为与编译器范围匹配的宏。 (或者它随着编译器附带的标准库一起提供,或者第三方标准库替换负责使其正确)

仅限快速回答:

#define UINT_WHATEVER_T_MAX ( ~ (uint_whatever_t) 0 )看起来没关系,-1的优先级是uint_whatever_t = -1;uint_whatever_t = ~(uint_whatever_t)0;更简洁uint_whatever_t = ~(uint_whatever_t)0;

(CHAR_BIT * sizeof(t))看起来并不严格符合我的要求。 你对填充位是正确的,所以这个值可能远远超过类型的宽度,除非Posix另外说了off_t

相比之下,C99中的固定宽度整数类型不能有填充位,因此对于intN_t您可以使用大小来推断宽度。 他们也保证两个补充。

这似乎意味着只能使用那三个列出的有符号数字表示。 暗示是否正确

是。 6.2.6.2/2列出了符号位的三个允许含义,因此列出了三个允许的带符号数字表示。

符号位保证总是比最重要的值位更重要

间接要求比值位重要,事实上(再次为6.2.6.2/2)“作为值位的每个位应具有与相应无符号类型的对象表示中的相同位相同的值。 ”。 因此,值位必须是从最低位开始的连续范围。

但是,您无法仅设置符号位。 阅读6.2.6.2/3和/ 4,关于负零,并注意即使实现使用原则上具有它们的表示,它也不必支持它们,并且没有保证生成它的方法。 在符号+幅度实现上,您想要的是负零。

[编辑:哦,我误读了,你只需要在排除符号+幅度之后生成该值,所以你仍然可以。

老实说,如果Posix定义了一个整数类型而没有为它提供限制,那对我来说听起来有点麻烦。 嘘他们。 我可能会采用旧的“移植标头”方法,在那里你把可能无处不在的东西放在标题中,并记录某人应该在编译任何奇怪的实现代码之前检查它。 与通常要让任何人的代码一起工作所做的相比,他们很乐意接受这一点。

签名最多:

 #define GENERIC_S_MAX(stype) ((stype) ((1ULL << ((sizeof(stype) * 8) - 1)) - 1ULL)) 

假设您的系统使用两个补码,则签名min应为:

 #define GENERIC_S_MIN(stype) ((stype) -1 - GENERIC_S_MAX(stype)) 

这些应该是完全可移植的,除了long long在技术上是C89中的编译器扩展。 这也避免了有符号整数上/下溢的未定义行为。

它在技术上不是宏,但在实践中,无论符号表示如何,对于off_t或任何有符号类型,以下内容应始终折叠为常量最小值。 虽然我不确定什么不使用两个恭维,如果有的话。

POSIX需要off_t有符号整数类型,因此C99签名的精确宽度值应该足够了。 有些平台实际上定义了OFF_T_MIN (OSX),但遗憾的是POSIX并不需要它。

 #include  #include  #include  assert(sizeof(off_t) >= sizeof(int8_t) && sizeof(off_t) <= sizeof(intmax_t)); const off_t OFF_T_MIN = sizeof(off_t) == sizeof(int8_t) ? INT8_MIN : sizeof(off_t) == sizeof(int16_t) ? INT16_MIN : sizeof(off_t) == sizeof(int32_t) ? INT32_MIN : sizeof(off_t) == sizeof(int64_t) ? INT64_MIN : sizeof(off_t) == sizeof(intmax_t) ? INTMAX_MIN : 0; 

这同样可用于获得最大值。

  assert(sizeof(off_t) >= sizeof(int8_t) && sizeof(off_t) <= sizeof(intmax_t)); const off_t OFF_T_MAX = sizeof(off_t) == sizeof(int8_t) ? INT8_MAX : sizeof(off_t) == sizeof(int16_t) ? INT16_MAX : sizeof(off_t) == sizeof(int32_t) ? INT32_MAX : sizeof(off_t) == sizeof(int64_t) ? INT64_MAX : sizeof(off_t) == sizeof(intmax_t) ? INTMAX_MAX : 0; 

这可以使用autoconfcmake转换为宏。

我使用以下模式来解决问题(假设没有填充位):

 ((((type) 1 << (number_of_bits_in_type - 1)) - 1) << 1) + 1 

number_of_bits_in_typeCHAR_BIT * sizeof (type)一样派生,与其他答案一样。

我们基本上将1位“轻推”到位,同时避免使用符号位。

你可以看到它是如何工作的。 假设宽度为16位。 然后我们取1并将其向左移动16 - 2 = 14,产生位模式0100000000000000 。 我们小心翼翼地避免将1移入符号位。 接下来,我们从中减去1,获得0011111111111111 。 看看这是怎么回事? 我们将此左移1,获得0111111111111110 ,再次避开符号位。 最后我们加1,获得0111111111111111 ,这是最高签名的16位值。

如果你在有博物馆的地方工作,这应该适用于一个补码和符号幅度的机器。 如果你有填充位,它不起作用。 为此,您可以做的就是#ifdef ,或切换到编译器和预处理器之外的备用配置机制。