C question: off_t (and other signed integer types) minimum and maximum values
I occasionally will come across an integer type (e.g. POSIX signed integer type off_t
) where it would be helpful to have a macro for its minimum and maximum values, but I don't know how to make one that is truly portable.
0
for the minimum and ~0
for the maximum. I have since read of several different SO threads which suggest using -1
instead of ~0
for portability. An interesting thread with some contention is here:
c++ - Is it safe to use 开发者_如何学Python-1 to set all bits to true? - Stack Overflow
However even after reading about this issue I'm still confused. Also, I'm looking for something both C89 and C99 compliant so I don't know if the same methods apply. Say I had a type of uint_whatever_t
. Couldn't I just cast to 0 first and then bitwise complement? Would this be ok?:
#define UINT_WHATEVER_T_MAX ( ~ (uint_whatever_t) 0 )
Signed integer types look like they'll be a tougher nut to crack. I've seen several different possible solutions but only one appears to be portable. Either that or it's incorrect. I found it while googling for an OFF_T_MAX and OFF_T_MIN. Credit to 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)
I couldn't find anything regarding the different allowable types of signed integer representations in C89, but C99 has notes for integer portability issues in §J.3.5:
Whether signed integer types are represented using sign and magnitude, two’s complement, or ones’ complement, and whether the extraordinary value is a trap representation or an ordinary value (6.2.6.2).
That would seem to imply that only those three listed signed number representations can be used. Is the implication correct, and are the macros above compatible with all three representations?
Other thoughts: It seems that the function-like macro
MAX_INT_VAL_STEP()
would give an incorrect result if there were padding bits. I wonder if there is any way around this.
Reading through signed number representations on Wikipedia it occurs to me that for all three signed integer representations any signed integer type's MAX would be:
sign bit off, all value bits on (all three) And its MIN would be either: sign bit on, all value bits on (sign and magnitude) sign bit on, all value bits off (ones/twos complement)I think I could test for sign and magnitude by doing this:
#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 */ )
Then as sign and magnitude is sign bit on and all value bits on wouldn't the minimum for off_t in that case be ~ (off_t) 0
? And for ones/twos complement minimum I would need some way to turn all the value bits off but leave the sign bit on. No idea how to do this without knowing the number of value bits. Also is the sign bit guaranteed to always be one more significant than the most significant value bit?
Thanks and please let me know if this is too long a post
EDIT 12/29/2010 5PM EST:
As answered below by ephemient to get the unsigned type max value,(unsigned type)-1
is more correct than ~0
or even ~(unsigned type)0
. From what I can gather when you use -1 it is just the same as 0-1 which will always lead to the maximum value in an unsigned type.
Also, because the maximum value of an unsigned type can be determined it is possible to determine how many value bits are in an unsigned type. Credit to Hallvard B. Furuseth for his IMAX_BITS() function-like macro that he posted in reply to a question on 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) computes the number of bits in an int, and IMAX_BITS((unsigned_type)-1) computes the number of bits in an unsigned_type. Until someone implements 4-gigabyte integers, anyway:-)
The heart of my question however remains unanswered: how to determine the minimum and maximum values of a signed type via macro. I'm still looking into this. Maybe the answer is there is no answer.
If you are not viewing this question on StackOverflow in most cases you cannot see the proposed answers until they are accepted. It is suggested to view this question on StackOverflow.
I believe I have finally solved this problem, but the solution is only available at configure
-time, not compile-time or runtime, so it's still not idea. Here it is:
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))
The idea comes from 6.7.2.1 paragraph 3:
The expression that specifies the width of a bit-field shall be an integer constant expression with a nonnegative value that does not exceed the width of an object of the type that would be specified were the colon and expression omitted. If the value is zero, the declaration shall have no declarator.
I would be quite pleased if this leads to any ideas for solving the problem at compile-time.
Surprisingly, C promotes types up to int
before arithmetic operations, with results being at least int
sized. (Similarly oddities include 'a'
character literal having type int
, not 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 */
So #define UINT_WHATEVER_T_MAX ( ~ (uint_whatever_t) 0 )
isn't necessarily okay.
Signed max:
#define GENERIC_S_MAX(stype) ((stype) ((1ULL << ((sizeof(stype) * 8) - 1)) - 1ULL))
Assuming your system uses two's complement, the signed min should be:
#define GENERIC_S_MIN(stype) ((stype) -1 - GENERIC_S_MAX(stype))
These should be totally portable, except that long long is technically a compiler extension in C89. This also avoids the undefined behavior of over/underflowing a signed integer.
For sign-magnitude representations, it's fairly easy (for types at least as wide as int
, anyway):
#define SM_TYPE_MAX(type) (~(type)-1 + 1)
#define SM_TYPE_MIN(type) (-TYPE_MAX(type))
Unfortunately, sign-magnitude representations are rather thin on the ground ;)
Quick answers only:
#define UINT_WHATEVER_T_MAX ( ~ (uint_whatever_t) 0 )
looks OK to me, the preference for -1 is that uint_whatever_t = -1;
is more concise than uint_whatever_t = ~(uint_whatever_t)0;
(CHAR_BIT * sizeof(t))
looks not strictly conforming to me. You're right about padding bits, so this value might be considerably more than the width of the type unless Posix says otherwise about off_t
.
In contrast, the fixed-width integer types in C99 must not have padding bits, so for intN_t
you're on firmer ground using the size to deduce the width. They're also guaranteed two's complement.
That would seem to imply that only those three listed signed number representations can be used. Is the implication correct
Yes. 6.2.6.2/2 lists the three permissible meanings of the sign bit, and hence the three permissible signed number representations.
is the sign bit guaranteed to always be one more significant than the most significant value bit
It's indirectly required to be more significant than the value bits, by the fact (6.2.6.2/2 again) that "Each bit that is a value bit shall have the same value as the same bit in the object representation of the corresponding unsigned type". So the value bits must be a contiguous range starting at the least significant.
However, you can't portably set just the sign bit. Read 6.2.6.2/3 and /4, about negative zeros, and note that even if the implementation uses a representation that has them in principle, it doesn't have to support them, and there's no guaranteed way of generating one. On a sign+magnitude implementation, the thing you want is a negative zero.
[Edit: oh, I misread, you only need to generate that value after you've ruled out sign+magnitude, so you could still be OK.
To be honest, it sounds a bit numpty to me if Posix has defined an integer type and not provided limits for it. Boo to them. I'd probably go with the old, "porting header" approach, where you put the thing that probably works everywhere in a header, and document that someone should probably check it before compiling the code on any freakish implementations. Compared with what they normally have to do to get anybody's code to work, they'll happily live with that.]
It's technically not a macro, but in practice the following should always be folded into a constant minimum for off_t
, or any signed type, regardless of sign representation. Although I'm not sure what doesn't use two's compliment, if anything.
POSIX requires a signed integer type for off_t
, so the C99 signed exact width values should be sufficient. Some platforms actually define OFF_T_MIN
(OSX), but POSIX unfortunately doesn't require it.
#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;
The same is usable to obtain the maximum value.
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;
This could be turned into a macro using autoconf
or cmake
though.
Since C11 you can use _Generic to find the underlying type. Before then __builtin_choose_expr
with __typeof
and __builtin_types_compatible_p
was fairly portable.
If you don't want to use any of those, you could guess the type based on its size and signedness.
#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);
}
(If you want to do it even without limits.h, please check out my answer at https://stackoverflow.com/a/53470064/1084774).
TL;DR: Use the header file listed below and then use TYPE_MAX(someType)
to get the maximum of the type used by someType.
You can use the _Generic
expression, introduced with C11.
You need a list of every base integer type (like char
, long
, ...) your compiler supports, every typedefed integer (most likely things like uint8_t
, off_t
) will be treated like the underlying type.
Here is an example header file:
#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 \
)
Let's say off_t
is defined with typedef int64_t off_t
and int64_t
is defined with typedef long long int64_t
, then the C compiler will treat off_t foo; TYPE_MAX(foo)
the same as long long foo; TYPE_MAX(foo)
and will chose the option with the value LLONG_MAX
, giving you the maximum.
If a system has another native type not listed in this header file, make a preprocessor variable that is empty on other systems but contains the values for that native type when you compile on a system with that type. And then add this preprocessor variable to the list, similar to how it was done here with uintmax_t
.
I have used the following pattern to solve the problem (assuming there are no padding bits):
((((type) 1 << (number_of_bits_in_type - 2)) - 1) << 1) + 1
The number_of_bits_in_type
is derived as CHAR_BIT * sizeof (type)
as in the other answers.
We basically "nudge" the 1 bits into place, while avoiding the sign bit.
You can see how this works. Suppose that the width is 16 bits. Then we take 1 and shift it left by 16 - 2 = 14, producing the bit pattern 0100000000000000
. We carefully avoided shifting a 1
into the sign bit. Next, we subtract 1 from this, obtaining 0011111111111111
. See where this is going? We shift this left by 1 obtaining 0111111111111110
, again avoiding the sign bit. Finally we add 1, obtaining 0111111111111111
, which is the highest signed 16 bit value.
This should work fine on one's complement and sign-magnitude machines, if you work in a museum where they have such things. It doesn't work if you have padding bits. For that, probably all you can do is #ifdef
, or switch to alternative configuration mechanisms outside of the compiler and preprocessor.
So, for 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)
You probably want to look at limits.h (added in C99) this header provides macros that should be set to match the compiler's ranges. (either it is provided along with the Standard library that came with the compiler, or a third party standard library replacement is responsible for getting it right)
精彩评论