【问题标题】:C question: off_t (and other signed integer types) minimum and maximum valuesC题:off_t(和其他有符号整数类型)的最小值和最大值
【发布时间】:2011-05-29 17:05:25
【问题描述】:

我偶尔会遇到一种整数类型(例如 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 时发现了它。感谢克里斯蒂安·比尔:

#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 可以是:
符号位打开,所有值位都打开(符号和幅度)
符号位打开,所有值位关闭(1/2 补码)

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

#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 吗?对于一个/两个补码最小值,我需要一些方法来关闭所有值位,但保留符号位。在不知道值位数的情况下不知道如何执行此操作。符号位是否保证总是比最高有效值位高一个?

谢谢,如果这篇文章太长,请告诉我



编辑 2010 年 12 月 29 日下午 5 点 EST
正如 ehemient 在下面得到无符号类型最大值所回答的那样,(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 GB 整数:-)

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

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

【问题讨论】:

  • 确实,MAX_INT_VAL_STEP 会在有任何填充位时中断。我有一个相关的问题,我从来没有得到满意的答案:stackoverflow.com/questions/3957252/…
  • @R C 编译器针对哪些平台在有符号整数类型中有填充位?如有必要,也许可以使用特定于这些平台的#ifdef 来处理这些问题。
  • 从 C11 开始,您可以为此使用 _Generic 宏。

标签: c binary bit-manipulation bitwise-operators


【解决方案1】:

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

【讨论】:

  • 不幸的是,off_t ☹ 没有这样的常量,而且我们似乎真的需要它们用于签名类型……off_t 就是其中之一……
【解决方案2】:

仅限快速回答:

#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 定义了一个整数类型并且没有为它提供限制,这对我来说听起来有点笨拙。嘘他们。我可能会采用旧的“移植标头”方法,在这种方法中,您将可能随处可用的东西放在标头中,并记录有人可能应该在编译任何怪异实现的代码之前检查它。与他们通常必须做的让任何人的代码工作相比,他们会很乐意接受。]

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

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

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

      【讨论】:

        【解决方案5】:

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

        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
        • 很高兴您看到这个迟到的答案。如果我找到更好的解决方案,我一定会跟进。
        • @AnonymousQuestionGuy:我想出了另一个可能与您的问题相关的想法:stackoverflow.com/a/5761398/379897
        • 嗯。这对于编译时检查可能已经足够好了,例如如果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。我说的对吗?
        • 我不认为这个脚本解决了一个有用的问题;这相当于计算off_t 中有多少位,这很容易作为CHAR_BIT * sizeof (off_t) 获得。现在 ISO C 不需要它。整数类型可以在其中包含填充位,因此它的值位不是字节的倍数,例如 36 位字长和 34 位整数。 您的 POSIX shell 脚本甚至会在那里运行吗?是否会有一个带有 off_t 的 POSIX API 需要担心
        【解决方案6】:

        签名最大值:

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

        【讨论】:

        • 这假设缺少填充位。
        • 这是真的。我不熟悉使用填充位的 C 整数类型。
        • 它也假定为CHAR_BIT==8,但这很容易通过在CHAR_BIT 中替换为 8 来解决。
        • @R.. 有没有使用off_tCHAR_BIT != 8 的真实案例...? POSIX 规范要求 CHAR_BIT == 8off_t 通常用于 POSIX 系统,所以我认为这是一个非常安全的假设。
        • @CraigBarnes:问题不只是关于 off_t,而是推广到非 POSIX。
        【解决方案7】:

        从技术上讲,它不是一个宏,但在实践中,对于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 非常愚蠢地不需要的常量表达式,这是一个很好的解决方案。
        【解决方案8】:

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

        ((((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 &lt;&lt; 15 升级为 int,所以你输了……你必须要求更多的演员。
        • @mirabilos 我在表达式中有错字:应该有一个- 2。您可以从讨论中看出在 16 位情况下指的是 14 位移位。推广没问题;这是一条红鲱鱼。我们真的无能为力。如果(type)(unsigned short) 并且我们添加(type) 演员表到整个事情,它仍然会再次提升到int。此外,这显然是为了获得有符号类型的最大值,而不是无符号的。
        【解决方案9】:

        从 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);
        }
        

        (如果您想在没有限制的情况下这样做。h,请查看我在https://stackoverflow.com/a/53470064/1084774 的回答)。

        【讨论】:

          【解决方案10】:

          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) 视为与long long foo; TYPE_MAX(foo) 相同,并将选择值为@987654334 的选项@,给你最大。

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2019-01-02
            • 2023-04-02
            • 2011-01-04
            • 2013-06-27
            • 2012-12-26
            • 2016-10-06
            • 2012-11-20
            相关资源
            最近更新 更多