C вопрос: off_t (и другие целочисленные типы со знаком) минимальное и максимальное значения

Я иногда сталкиваюсь с целочисленным типом (например, POSIX знаковый целочисленный тип off_t) где было бы полезно иметь макрос для его минимальных и максимальных значений, но я не знаю, как сделать так, чтобы он был действительно переносимым.


Для целых типов без знака я всегда думал, что это просто. 0 для минимума и ~0 по максимуму. С тех пор я прочитал несколько разных тем, которые предлагают использовать -1 вместо ~0 для портативности. Интересная тема с некоторым утверждением здесь:
Безопасно ли использовать -1, чтобы установить все биты в true? - Переполнение стека

Однако даже после прочтения этой проблемы я все еще растерялся. Кроме того, я ищу что-то совместимое с C89 и C99, поэтому я не знаю, применяются ли те же методы. Скажем, у меня был тип uint_whatever_t, Разве я не могу сначала привести к 0, а затем поразрядно дополнить? Это было бы хорошо?

#define UINT_WHATEVER_T_MAX ( ~ (uint_whatever_t) 0 )


Подписанные целочисленные типы выглядят так, как будто они будут более крепким орешком. Я видел несколько разных возможных решений, но только одно из них выглядит переносимым. Либо это, либо это неправильно. Я нашел это во время поиска в Google для 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() дал бы неверный результат, если бы были биты заполнения. Интересно, есть ли способ обойти это?

Читая представления со знаком в Википедии, мне приходит в голову, что для всех трех представлений со знаком целого 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? И для минимума, дополняющего единицы / два, мне понадобится какой-то способ отключить все биты значения, но оставить бит знака включенным. Не знаю, как это сделать, не зная количества битов значения. Также гарантированно, что знаковый бит всегда будет на один значащий бит больше, чем старший значащий бит?

Спасибо и, пожалуйста, дайте мне знать, если это слишком длинный пост



РЕДАКТИРОВАТЬ 29/12/2010 5 вечера EST:
Как сказано ниже в ephemient, чтобы получить максимальное значение типа без знака, (unsigned type)-1 правильнее чем ~0 или даже ~(unsigned type)0, Из того, что я могу получить, когда вы используете -1, это то же самое, что 0-1, что всегда приведет к максимальному значению в типе без знака.

Кроме того, поскольку может быть определено максимальное значение типа без знака, можно определить, сколько битов значения имеет тип без знака. Благодарим Хальварда Б. Фурусета за его функциональный макрос 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. Пока кто-нибудь не реализует 4-гигабайтные целые числа:-)

Суть моего вопроса, однако, остается без ответа: как определить минимальное и максимальное значения типа со знаком с помощью макроса. Я все еще смотрю на это. Может быть, ответ - нет ответа.

Если вы не просматриваете этот вопрос в Stack Overflow, в большинстве случаев вы не сможете увидеть предложенные ответы, пока они не будут приняты. Предлагается просмотреть этот вопрос на Stack Overflow.

10 ответов

Я считаю, что наконец-то решил эту проблему, но решение доступно только на 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:

Выражение, которое определяет ширину битового поля, должно быть выражением целочисленной константы с неотрицательным значением, которое не превышает ширину объекта того типа, который был бы указан, если бы двоеточие и выражение были опущены. Если значение равно нулю, декларация не должна иметь декларатора.

Я был бы весьма рад, если это приведет к каким-либо идеям для решения проблемы во время компиляции.

Удивительно, но 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 ) не обязательно хорошо

Макс подписано:

#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. Это также позволяет избежать неопределенного поведения переполнения / занижения целого числа со знаком.

Для представлений величины знака это довольно легко (для типов, по крайней мере, таких широких, как int, тем не мение):

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

К сожалению, представления величины знака довольно тонки на земле;)

TL;DR: используйте файл заголовка, указанный ниже, а затем используйте TYPE_MAX(someType) чтобы получить максимум типа, используемого someType.

Вы можете использовать _Genericвыражение, представленное в C11. Вам нужен список всех базовых целочисленных типов (например, char, long, ...) ваш компилятор поддерживает каждое целое число с заданным типом (скорее всего, такие вещи, как uint8_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) и выберет вариант со значением LLONG_MAX, давая вам максимум.

Если у системы есть тип, имеющий другой собственный тип, не указанный в этом файле заголовка, создайте переменную препроцессора, которая пуста в других системах, но содержит значения для этого собственного типа при компиляции в системе с этим типом. А затем добавьте эту переменную препроцессора в список. Подобно тому, как это было сделано здесь с uintmax_t.

Я использовал следующий шаблон для решения проблемы (при условии, что нет битов заполнения):

((((type) 1 << (number_of_bits_in_type - 1)) - 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или любой подписанный тип, независимо от представления знака. Хотя я не уверен, что не использует два комплимента, если что-нибудь.

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 хоть.

Начиная с 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);
}

(Если вы хотите сделать это даже без ограничений. Ч., Пожалуйста, ознакомьтесь с моим ответом по адресу /questions/11570629/programmnoe-opredelenie-maksimalnogo-znacheniya-tselochislennogo-tipa-so-znakom/11570650#11570650).

Только быстрые ответы:

#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 определил целочисленный тип и не предоставил для него ограничений. Бу им. Я бы, вероятно, пошел со старым подходом "портирования заголовка", когда вы помещаете в заголовок то, что, вероятно, работает везде, и документируете, что кто-то, вероятно, должен это проверить, прежде чем компилировать код в любых причудливых реализациях. По сравнению с тем, что им обычно приходится делать, чтобы любой код заработал, они с радостью с этим будут жить.]

Возможно, вы захотите взглянуть на limit.h (добавлен в C99), этот заголовок содержит макросы, которые должны быть установлены в соответствии с диапазонами компилятора. (либо он предоставляется вместе со стандартной библиотекой, поставляемой вместе с компилятором, либо за правильную замену отвечает сторонняя стандартная библиотека)

Другие вопросы по тегам