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

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

Для неподписанных целых типов я всегда думал, что это просто. 0 для минимума и ~0 для максимума. С тех пор я прочитал несколько разных streamов SO, которые предлагают использовать -1 вместо ~0 для переносимости. Интересная тема с некоторыми утверждениями здесь:
c ++ – Можно ли использовать -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.5.5:

Составляются ли подписанные целочисленные типы с использованием знака и величины, дополнения двух или одного дополнения, и является ли экстраординарное значение ловушным представлением или обычным значением (6.2.6.2).

Это, по-видимому, подразумевает, что могут использоваться только те три перечисленные подписанные числа. Является ли импликация правильной, а macros выше совместимы со всеми тремя представлениями?


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

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


EDIT 12/29/2010 5 вечера EST :
Как было сказано ниже с помощью ephemient для получения максимального значения unsigned type, (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. Пока кто-то не реализует целых 4 гигабайта, так или иначе 🙂

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

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

Удивительно, что C продвигает типы до int перед арифметическими операциями, причем результаты имеют размер не менее int . (Аналогичные странности include в 'a' символ '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 ) не обязательно в порядке.

Я считаю, что я, наконец, решил эту проблему, но решение доступно только в режиме configure -time, а не во время компиляции или времени выполнения, так что это все еще не идея. Вот:

 HEADERS="#include " TYPE="off_t" i=8 while : ; do printf "%s\nstruct { %sx : %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:

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

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

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

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

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

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

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

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

[Edit: oh, я неправильно читаю, вам нужно только создать это значение после того, как вы исключили знак + значение, поэтому вы все равно можете быть в порядке.

Честно говоря, это звучит несколько пустым, если Posix определил целочисленный тип и не предоставил ему ограничений. Бу им. Я бы, вероятно, пошел со старым подходом «портирования заголовка», где вы помещаете вещь, которая, вероятно, работает повсюду в заголовке, и документируйте, что кто-то должен, вероятно, проверить ее перед компиляцией кода на любые причудливые реализации. По сравнению с тем, что они обычно должны делать, чтобы заставить чей-то код работать, они с радостью будут жить с этим.]

Подписано max:

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

Это технически не макрос, но на практике следующее должно всегда сворачиваться в постоянный минимум для off_t или любого подписанного типа, независимо от представления знака. Хотя я не уверен, что не использует комплимент два, если угодно.

Для POSIX требуется целочисленный тип со off_t для off_t , поэтому должны быть достаточные значения точной ширины C99. Некоторые платформы фактически определяют OFF_T_MIN (OSX), но POSIX, к сожалению, не требует этого.

 #include  #include  #include  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 .

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

 ((((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 . Смотрите, где это происходит? Мы сдвинем это налево, получив 0111111111111110 , снова избегая знакового бита. Наконец, мы добавим 1, получив 0111111111111111 , который является наивысшим подписанным 16-битным значением.

Это должно отлично работать на компьютерах с дополнением и знаками, если вы работаете в музее, где есть такие вещи. Это не работает, если у вас есть биты заполнения. Для этого, вероятно, все, что вы можете сделать, это #ifdef или переключиться на альтернативные механизмы настройки вне компилятора и препроцессора.