C вопрос: off_t (и другие целочисленные типы со знаком) минимальное и максимальное значения - PullRequest
11 голосов
/ 23 декабря 2010

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


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

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

#define UINT_WHATEVER_T_MAX ( ~ (uint_whatever_t) 0 )


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

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

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

Ответы [ 9 ]

5 голосов
/ 03 мая 2011

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

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

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

5 голосов
/ 23 декабря 2010

Удивительно, но 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 голос
/ 16 октября 2015

Макс. Со знаком:

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

1 голос
/ 23 декабря 2010

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

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

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

0 голосов
/ 25 ноября 2018

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

(Если вы хотите сделать это даже без ограничений. Ч, пожалуйста, проверьте мой ответ на https://stackoverflow.com/a/53470064/1084774).

0 голосов
/ 04 декабря 2015

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

((((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, или переключиться на альтернативные механизмы конфигурации вне компилятора и препроцессора.

0 голосов
/ 11 ноября 2015

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

0 голосов
/ 23 декабря 2010

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

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

0 голосов
/ 23 декабря 2010

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

...