我偶尔会遇到一个整数类型(例如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。
答案 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;
虽然可以使用autoconf
或cmake
将其转换为宏。
答案 7 :(得分:0)
我使用以下模式来解决问题(假设没有填充位):
((((type) 1 << (number_of_bits_in_type - 1)) - 1) << 1) + 1
number_of_bits_in_type
与CHAR_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
表达式。
您需要一个包含编译器支持的每个基本整数类型(如 char
、long
、...)的列表,每个类型定义的整数(最有可能是 uint8_t
、off_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
完成的操作。