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

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


Для неподписанных целых типов я всегда думал, что это просто. 0 для минимума и ~0 для максимума. С тех пор я прочитал несколько разных потоков SO, которые предлагают использовать -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.5.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 в этом случае be ~ (off_t) 0? И для одного/двухкратного минимума мне понадобится какой-то способ отключить все биты значений, но оставить бит знака включенным. Не знаю, как это сделать, не зная количества битов значения. Кроме того, знак бит гарантированно всегда будет более значительным, чем бит наиболее значимого значения?

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



РЕДАКТИРОВАТЬ 12/29/2010 5PM EST:
Как было сказано ниже, чтобы получить максимальное значение 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, в большинстве случаев вы не можете видеть предлагаемые ответы до тех пор, пока они не будут приняты. Предлагается qaru.site/info/9929/....

Ответы

Ответ 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 ) не всегда в порядке.

Ответ 2

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

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

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

Ответ 3

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

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

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

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

Ответ 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;

Это может быть превращено в макрос, используя autoconf или cmake, хотя.

Ответ 8

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

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

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