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

时间:2010-12-22 23:12:51

标签: c binary bit-manipulation bitwise-operators

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


对于无符号整数类型,我一直认为这很简单。最低0,最高~0。我已经读过几个不同的SO线程,建议使用-1而不是~0来实现可移植性。这里有一个有争议的有趣话题:
c++ - Is it safe to use -1 to set all bits to true? - Stack Overflow

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

#define UINT_WHATEVER_T_MAX ( ~ (uint_whatever_t) 0 )


有符号整数类型看起来像是一个更难以破解的坚果。我已经看到了几种不同的可能解决方案,但只有one似乎是可移植的。无论是那个还是不正确的。我在谷歌搜索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()将给出不正确的结果。我想知道是否有任何办法解决这个问题。

通过signed number representations on Wikipedia阅读我发现,对于所有三个有符号整数表示,任何有符号整数类型的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?对于一些/两个补码最小值,我需要一些方法来关闭所有的值位,但保持符号位。不知道如何在不知道值位数的情况下执行此操作。标志位是否保证始终比最重要的值位更重要?

谢谢,如果帖子太长,请告诉我



编辑12/29/2010美国东部时间下午5点
正如下面通过ephemient得到的无符号类型最大值,(unsigned type)-1~0或甚至~(unsigned type)0更正确。从使用-1时我可以收集的内容中,它与0-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千兆字节的整数,无论​​如何: - )

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

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

10 个答案:

答案 0 :(得分:5)

令人惊讶的是,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 )不一定好。

答案 1 :(得分:5)

我相信我已经终于解决了这个问题,但解决方案仅在configure时可用,而不是编译时或运行时,所以它仍然不知道。这是:

HEADERS="#include <sys/types.h>"
TYPE="off_t"
i=8
while : ; do
printf "%s\nstruct { %s x : %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段:

  

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

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

答案 2 :(得分:1)

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

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

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

答案 3 :(得分:1)

签名最多:

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

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

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

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

答案 4 :(得分:0)

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

答案 5 :(得分:0)

仅限快速回答:

#define UINT_WHATEVER_T_MAX ( ~ (uint_whatever_t) 0 )对我来说没问题,-1的偏好是uint_whatever_t = -1;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定义了一个整数类型而没有提供限制,那对我来说听起来有点麻烦。嘘他们。我可能会采用旧的“移植头”方法,在那里你可以将标题中可能无处不在的东西放在标题中,并记录某人应该在编译任何奇怪的实现上的代码之前检查它。与他们通常必须做的事情相比,他们很乐意接受这个代码。]

答案 6 :(得分:0)

从技术上讲,它不是一个宏,但实际上,无论符号表示如何,以下内容都应始终折叠为off_t或任何有符号类型的常量最小值。虽然我不确定什么不能使用双方的赞美,如果有的话。

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

#include <stdint.h>
#include <assert.h>

#include <sys/types.h>

  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将其转换为宏。

答案 7 :(得分:0)

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

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

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

我们基本上&#34;轻推&#34; 1位到位,同时避免符号位。

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

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

答案 8 :(得分:0)

从C11开始,您可以使用_Generic查找基础类型。在此之前,带有__builtin_choose_expr__typeof的{​​{1}}相当便携。

如果您不想使用其中任何一种,则可以根据其大小和签名来猜测类型。

__builtin_types_compatible_p

(如果您想即使没有限制也要这样做。h,请在https://stackoverflow.com/a/53470064/1084774上查看我的答案)。

答案 9 :(得分:0)

TL;DR:使用下面列出的头文件,然后使用 TYPE_MAX(someType) 获取 someType 使用的类型的最大值。

您可以使用 C11 引入的 _Generic 表达式。 您需要一个包含编译器支持的每个基本整数类型(如 charlong、...)的列表,每个类型定义的整数(最有可能是 uint8_toff_t ) 将被视为基础类型。

这是一个示例头文件:

#include <float.h>
#include <limits.h>
#include <stdint.h>
#include <stdbool.h>

#if UINTMAX_MAX==ULLONG_MAX
  #define TYPE_UINTMAX_MAX
  #define TYPE_UINTMAX_MIN
#else  //UINTMAX_MAX!=ULLONG_MAX
  #define TYPE_UINTMAX_MAX                    \
      , uintmax_t           : UINTMAX_MAX     \
      , intmax_t            : INTMAX_MAX      \

  #define TYPE_UINTMAX_MIN                    \
      , uintmax_t           : UINTMAX_MIN     \
      , intmax_t            : INTMAX_MIN      \

#endif //UINTMAX_MAX==ULLONG_MAX


#define TYPE_MAX(variable) _Generic          \
  (                                          \
    (variable)                               \
    , bool                : 1                \
    , char                : CHAR_MAX         \
    , unsigned char       : UCHAR_MAX        \
    , signed   char       : SCHAR_MAX        \
    , unsigned short      : USHRT_MAX        \
    , signed   short      : SHRT_MAX         \
    , unsigned int        : UINT_MAX         \
    , signed   int        : INT_MAX          \
    , unsigned long       : ULONG_MAX        \
    , signed   long       : LONG_MAX         \
    , unsigned long long  : ULLONG_MAX       \
    , signed   long long  : LLONG_MAX        \
    TYPE_UINTMAX_MAX                         \
                                             \
    , float               : FLT_MAX          \
    , double              : DBL_MAX          \
    , long double         : LDBL_MAX         \
  )

#define TYPE_MIN(variable) _Generic          \
  (                                          \
    (variable)                               \
    , bool                : 0                \
    , char                : CHAR_MIN         \
    , unsigned char       : 0                \
    , signed   char       : SCHAR_MIN        \
    , unsigned short      : 0                \
    , signed   short      : SHRT_MIN         \
    , unsigned int        : 0                \
    , signed   int        : INT_MIN          \
    , unsigned long       : 0                \
    , signed   long       : LONG_MIN         \
    , unsigned long long  : 0                \
    , signed   long long  : LLONG_MIN        \
    TYPE_UINTMAX_MIN                         \
                                             \
    , float               : -FLT_MAX         \
    , double              : -DBL_MAX         \
    , long double         : -LDBL_MAX        \
  )

假设 off_t 是用 typedef int64_t off_t 定义的,而 int64_t 是用 typedef long long int64_t 定义的,那么 C 编译器会将 off_t foo; TYPE_MAX(foo) 视为 {{1 }} 并将选择值为 long long foo; TYPE_MAX(foo) 的选项,为您提供最大值。

如果系统的类型具有未在此头文件中列出的其他本机类型,则在其他系统上创建一个为空的预处理器变量,但在具有该类型的系统上编译时包含该本机类型的值。然后将此预处理器变量添加到列表中。类似于此处使用 LLONG_MAX 完成的操作。