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

13

我偶尔会遇到整数类型(例如 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 )


有符号整数类型看起来会更加困难。我见过几种不同的可能解决方案,但只有一个似乎是可移植的。要么就是它不正确。我在谷歌搜索 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中有有关整数可移植性问题的注释:

有符号整数类型是使用符号和大小、2的补码还是1的补码表示,以及非常规值是陷阱表示还是普通值(6.2.6.2)。

这似乎意味着只能使用这三种列出的带符号数表示。这个推论是正确的吗?这些宏是否与所有三种表示兼容?


其他想法:
如果存在填充位,函数式宏MAX_INT_VAL_STEP()似乎会给出错误的结果。我想知道是否有任何方法可以解决这个问题。

阅读维基百科上的signed number representations后,我发现对于所有三种带符号整数表示,任何带符号整数类型的最大值都将是:
符号位关闭,所有值位打开(所有三种)
而其最小值可能是:
符号位打开,所有值位打开(符号和大小)
符号位打开,所有值位关闭(一/二的补码)

我认为我可以通过执行此操作来测试符号和大小:

#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 5PM EST:
如下面ephemient所回答的,要获取无符号类型的最大值,(unsigned type)-1~0甚至比~(unsigned type)0更正确。据我所知,当你使用-1时,它与0-1是一样的,这将始终导致无符号类型中的最大值。

此外,由于可以确定无符号类型的最大值,因此可以确定无符号类型中有多少个值位。感谢Hallvard B. Furuseth发布的IMAX_BITS()函数宏,他在comp.lang.c的问题回复中发布了该宏。

/* 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中的位数。直到有人实现了4GB整数为止:-)

然而,我的问题的核心仍未得到解答:如何通过宏确定有符号类型的最小值和最大值。我仍在研究中。也许答案是没有答案。

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


实际上,如果存在任何填充位,MAX_INT_VAL_STEP 就会出错。我有一个相关的问题,从来没有得到令人满意的答案:https://dev59.com/3W865IYBdhLWcg3wKLNP - R.. GitHub STOP HELPING ICE
@R C编译器所针对的平台中,哪些有带符号整数类型的填充位?如果需要的话,可以使用一些特定于这些平台的#ifdef来处理它们。 - Kaz
自C11起,您可以使用“_Generic”宏来实现。 - 12431234123412341234123
10个回答

7

我相信我终于解决了这个问题,但是解决方案只能在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段:

指定位域宽度的表达式必须是一个整数常量表达式,其值为非负且不超过省略冒号和表达式时所指定类型对象的宽度。如果值为零,则声明不能有声明符。

如果这能在编译时解决问题,我会非常高兴。


R.. 感谢您的帖子,这是一个不错的想法。如果我有一天弄清楚了编译时问题,我会将其发布为答案,如果您弄清楚了,请也这样做。-AQG - Anonymous Question Guy
很高兴你看到了这个晚回答。如果我找到更好的解决方案,我一定会跟进的。 - R.. GitHub STOP HELPING ICE
@AnonymousQuestionGuy:我想到了另一个可能与你的问题相关的想法,可以在这里查看:https://dev59.com/XVPTa4cB1Zd3GeqPmMSb#5761398 - R.. GitHub STOP HELPING ICE
嗯。这可能足以进行编译时检查,例如如果86400L(17位精度)适合于int:为此,int需要至少18位的宽度,因此我可以使用struct { signed int int_at_least_18_bits_wide:18; };signed是必需的!)作为编译时断言,对吗? C99和n2731(C2x草案)仅需要诊断,程序可能会编译,但通常的struct { char ensure_foo[foo ? 1 : -1]; };是相同的...而且我相信两者都不使用UB或IB。关于这一点,我是正确的吗? - mirabilos
@Kaz:它们并不等价。sizeof也包括填充位。 - R.. GitHub STOP HELPING ICE
显示剩余3条评论

5

令人惊讶的是,在进行算术运算之前,C会将类型提升到至少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 )并不一定是可行的。


2

最大有符号数:

#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中技术上是编译器扩展。这也避免了有符号整数溢出/下溢的未定义行为。


这假设没有填充位。 - R.. GitHub STOP HELPING ICE
这是真的。我对使用填充位的C整数类型不熟悉。 - Aaron Campbell
1
还要假设 CHAR_BIT==8,但可以通过将 8 替换为 CHAR_BIT 来轻松解决该问题。 - R.. GitHub STOP HELPING ICE
@R.. 有没有真实的案例,即使 CHAR_BIT != 8,也会使用 off_t...? POSIX 规范要求 CHAR_BIT == 8,而 off_t 通常用于 POSIX 系统,因此我认为这是一个相当安全的假设。 - Craig Barnes
@CraigBarnes:这个问题不仅仅涉及到off_t,而且适用于非POSIX。 - R.. GitHub STOP HELPING ICE

1

对于补码表示法,这相当容易(至少对于与int一样宽的类型):

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

很不幸,补码表示法相对较少见 ;)


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


如果C编译器将三元表达式视为常量表达式,那么这是一个不错的解决方案,但ISO C并没有要求这样做,这相当愚蠢。 - Kaz

0

简而言之:使用下面列出的头文件,然后使用TYPE_MAX(someType)来获取someType所使用类型的最大值。

您可以使用C11引入的_Generic表达式。 您需要一个包含编译器支持的每个基本整数类型(如charlong等)的列表,每个typedefed整数(最有可能是像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        \
  )

假设使用typedef int64_t off_t定义了off_t,并且使用typedef long long int64_t定义了int64_t,那么C编译器将把off_t foo; TYPE_MAX(foo)视为与long long foo; TYPE_MAX(foo)相同,并选择具有值LLONG_MAX的选项,从而给出最大值。

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


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定义了一个整数类型但没有提供限制,那对我来说有点傻。我可能会采用旧的“移植头文件”方法,在头文件中放置在任何奇怪的实现上可能有效的内容,并记录某人在编译代码之前应该检查它。与他们通常为使任何人的代码正常工作所做的事情相比,他们会很高兴接受这一点。

0
我使用了以下模式来解决问题(假设没有填充位):
((((type) 1 << (number_of_bits_in_type - 2)) - 1) << 1) + 1

根据其他答案,number_of_bits_in_type 是通过 CHAR_BIT * sizeof (type) 推导出来的。

我们基本上是将 1 位"推"到正确的位置,同时避免符号位。

你可以看看这个过程。假设宽度为16位。然后,我们将1左移16-2=14位,得到位模式0100000000000000。我们小心地避免将1移动到符号位。接下来,我们从这个数字中减去1,得到0011111111111111。看看这个数字往哪里走?我们将其左移1位,得到0111111111111110,再次避免符号位。最后,我们加上1,得到0111111111111111,这是有符号的16位值的最高值。

如果你在一个有补码和反码机器的博物馆工作,这个方法应该也能够正常工作。但如果你的机器有填充位,这种方式就无法正常工作了。那么,唯一可行的方法可能只能是使用#ifdef,或者切换到编译器和预处理器之外的替代配置机制。

因此,对于off_t

// probably bad idea to use these symbol names

#define OFF_T_BITS (sizeof (off_t) * CHAR_BIT)
#define OFF_T_MAX (((((off_t) 1 << (OFF_T_BITS - 2)) - 1) << 1) + 1)

(unsigned short)1 << 15 被提升为 int,所以你会损失一些... 你需要进行更多的转换。 - mirabilos
@mirabilos 我的表达式中有一个错别字:应该有一个 - 2。从讨论中可以看出,这是针对16位情况下的14位移位。晋升不是任何问题;它是一个干扰项。我们真正无法做些什么。如果 (type)(unsigned short) 并且我们为整个事物添加了 (type) 强制转换,它仍将再次晋升为 int。此外,这显然旨在获取带符号类型的最大值,而不是无符号类型。 - Kaz

0

自从C11,您可以使用_Generic来查找底层类型。在此之前,__builtin_choose_expr与__typeof和__builtin_types_compatible_p相结合是相当可移植的。

如果您不想使用其中任何一个,您可以根据其大小和符号猜测类型。

#include <stdio.h>
#include <limits.h>
#define TP_MAX(Tp) ((Tp)-1>0 ? ( \
                        1==sizeof(Tp) ? ((Tp)2==1?1:UCHAR_MAX) \
                        : sizeof(unsigned short)==sizeof(Tp) ? USHRT_MAX \
                        : sizeof(unsigned int)==sizeof(Tp) ? UINT_MAX \
                        : sizeof(unsigned long)==sizeof(Tp) ? ULONG_MAX \
                        : sizeof(unsigned long long)==sizeof(Tp) ? ULLONG_MAX : 0 \
                   ) :  ( 1==sizeof(Tp) ? SCHAR_MAX \
                        : sizeof(short)==sizeof(Tp) ? SHRT_MAX \
                        : sizeof(int)==sizeof(Tp) ? INT_MAX \
                        : sizeof(long)==sizeof(Tp) ? LONG_MAX \
                        : sizeof(long long)==sizeof(Tp) ? LLONG_MAX : 0)) \


#define STC_ASSERT(X) ((void)(sizeof(struct { int stc_assert:(X)?1:-1; })))

int main()
{
    STC_ASSERT(TP_MAX(signed char)==SCHAR_MAX);
    STC_ASSERT(TP_MAX(short)==SHRT_MAX);
    STC_ASSERT(TP_MAX(int)==INT_MAX);
    STC_ASSERT(TP_MAX(long)==LONG_MAX);
    STC_ASSERT(TP_MAX(long long)==LLONG_MAX);
    STC_ASSERT(TP_MAX(unsigned char)==UCHAR_MAX);
    STC_ASSERT(TP_MAX(unsigned short)==USHRT_MAX);
    STC_ASSERT(TP_MAX(unsigned int)==UINT_MAX);
    STC_ASSERT(TP_MAX(unsigned long)==ULONG_MAX);
    STC_ASSERT(TP_MAX(unsigned long long)==ULLONG_MAX);
}

(如果您想在没有limits.h的情况下完成它,请查看我的答案https://dev59.com/XVPTa4cB1Zd3GeqPmMSb#53470064。)


-1
你可能想看一下limits.h(在C99中添加),该头文件提供了应设置为与编译器范围匹配的宏。(它可以随着编译器提供的标准库一起提供,或者由第三方标准库替代程序负责正确地获取它)

很遗憾,off_t 没有这样的常量 ☹,而且似乎我们确实需要它们来表示有符号类型...其中 off_t 就是其中之一... - mirabilos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接