Есть ли способ вычислить ширину целочисленного типа во время компиляции?

Размер целочисленного типа (или любого типа) в единицах char/bytes легко вычисляется как sizeof(type). Общая идиома заключается в умножении на CHAR_BIT, чтобы найти количество бит, занятых типом, но при реализации с битами заполнения это не будет равно ширине битов значения. Хуже того, код вроде:

x>>CHAR_BIT*sizeof(type)-1

может иметь поведение undefined, если CHAR_BIT*sizeof(type) больше фактической ширины type.

Для простоты предположим, что наши типы не имеют знака. Тогда ширина type равна ceil(log2((type)-1). Есть ли способ вычислить это значение как постоянное выражение?

Ответ 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-гигабайтные целые числа, в любом случае: -)


И кредит Эрику Сосману для этой альтернативной версии, которая должна работать с менее чем 2040 бит:
(EDIT 1/3/2011 11:30 PM EST: Оказывается, эта версия была также написана Халвардом Б. Фюресом)

/* Number of bits in inttype_MAX, or in any (1<<k)-1 where 0 <= k < 2040 */
#define IMAX_BITS(m) ((m)/((m)%255+1) / 255%255*8 + 7-86/((m)%255+12))


Помните, что хотя ширина целых чисел без знака равна количеству битов значения, ширина знакового целочисленного типа одна больше (§6.2.6.2/6). Это имеет особое значение как в моем первоначальном комментарии к вашему вопросу, я неправильно заявил, что макрос IMAX_BITS() вычисляет ширину, когда он фактически вычисляет количество битов значения. Извини за это!

Итак, например, IMAX_BITS(INT64_MAX) создаст константу времени компиляции 63. Однако в этом примере мы имеем дело с подписанным типом, поэтому вы должны добавить 1 для учетной записи знакового бита, если хотите фактическую ширину int64_t, что, конечно, 64.

В отдельном обсуждении comp.lang.c пользователь с именем blargg дает разбивку о том, как работает макрос:
Re: использование препроцессора для подсчета бит в целых типах...

Обратите внимание, что макрос работает только с значениями 2 ^ n-1 (т.е. все 1s в двоичном формате), как и ожидалось с любым значением MAX. Также обратите внимание, что, хотя легко получить константу времени компиляции для максимального значения целочисленного типа без знака (IMAX_BITS((unsigned type)-1)), на момент написания я не знаю, как сделать то же самое для подписанного целочисленный тип, не вызывающий поведение, определенное реализацией. Если я когда-либо узнаю, я отвечу на свой собственный вопрос, связанный со мной, здесь:
C вопрос: off_t (и другие знаковые целочисленные типы) минимальные и максимальные значения - переполнение стека

Ответ 2

Сравните макросы с <limits.h> с известными максимальными значениями для определенной цельной ширины:

#include <limits.h>

#if UINT_MAX == 0xFFFF
#define INT_WIDTH 16
#elif UINT_MAX == 0xFFFFFF
#define INT_WIDTH 24
#elif ...
#else
#error "unsupported integer width"
#endif

Ответ 3

Первый подход, если вы знаете, какой стандартный тип у вас есть (поэтому ваш тип не равен typedef), переходите к макросам {U}INT_MAX и проверяйте возможные размеры.

Если у вас этого нет, для неподписанных типов это относительно просто концептуально. Для вашего любимого типа T просто выполните (T)-1 и выполните макрос проверки монстров, который проверяет все возможные значения с помощью ?:. Так как тогда это только выражения времени компиляции, любой достойный компилятор оптимизирует это, оставляя вас только с интересующим вас значением.

Это не работает в #if и т.д. из-за типа, но этого нельзя избежать простым способом.

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

Изменить:. Чтобы немного проиллюстрировать это, я даю несколько выписок из того, что вы может сделать, чтобы этот подход (для неподписанных типов) не генерировался слишком большие выражения в P99 У меня есть что-то вроде

#ifndef P99_HIGH2
# if P99_UINTMAX_WIDTH == 64
#  define P99_HIGH2(X)                                         \
((((X) & P00_B0) ? P00_S0 : 0u)                              \
 | (((X) & P00_B1) ? P00_S1 : 0u)                            \
 | (((X) & P00_B2) ? P00_S2 : 0u)                            \
 | (((X) & P00_B3) ? P00_S3 : 0u)                            \
 | (((X) & P00_B4) ? P00_S4 : 0u)                            \
 | (((X) & P00_B5) ? P00_S5 : 0u))
# endif
#endif
#ifndef P99_HIGH2
# if P99_UINTMAX_WIDTH <= 128
#  define P99_HIGH2(X)                                         \
((((X) & P00_B0) ? P00_S0 : 0u)                              \
 | (((X) & P00_B1) ? P00_S1 : 0u)                            \
 | (((X) & P00_B2) ? P00_S2 : 0u)                            \
 | (((X) & P00_B3) ? P00_S3 : 0u)                            \
 | (((X) & P00_B4) ? P00_S4 : 0u)                            \
 | (((X) & P00_B5) ? P00_S5 : 0u)                            \
 | (((X) & P00_B6) ? P00_S6 : 0u))
# endif
#endif

где магические константы определяются с последовательностью #if на начало. Там важно не выставлять слишком большие константы для компиляторов, которые не могут их обрабатывать.

/* The preprocessor always computes with the precision of uintmax_t */
/* so for the preprocessor this is equivalent to UINTMAX_MAX       */
#define P00_UNSIGNED_MAX ~0u

#define P00_S0 0x01
#define P00_S1 0x02
#define P00_S2 0x04
#define P00_S3 0x08
#define P00_S4 0x10
#define P00_S5 0x20
#define P00_S6 0x40

/* This has to be such ugly #if/#else to ensure that the            */
/* preprocessor never sees a constant that is too large.            */
#ifndef P99_UINTMAX_MAX
# if P00_UNSIGNED_MAX == 0xFFFFFFFFFFFFFFFF
#  define P99_UINTMAX_WIDTH 64
#  define P99_UINTMAX_MAX 0xFFFFFFFFFFFFFFFFU
#  define P00_B0 0xAAAAAAAAAAAAAAAAU
#  define P00_B1 0xCCCCCCCCCCCCCCCCU
#  define P00_B2 0xF0F0F0F0F0F0F0F0U
#  define P00_B3 0xFF00FF00FF00FF00U
#  define P00_B4 0xFFFF0000FFFF0000U
#  define P00_B5 0xFFFFFFFF00000000U
#  define P00_B6 0x0U
# endif /* P00_UNSIGNED_MAX */
#endif /* P99_UINTMAX_MAX */
#ifndef P99_UINTMAX_MAX
# if P00_UNSIGNED_MAX == 0x1FFFFFFFFFFFFFFFF
#  define P99_UINTMAX_WIDTH 65
#  define P99_UINTMAX_MAX 0x1FFFFFFFFFFFFFFFFU
#  define P00_B0 0xAAAAAAAAAAAAAAAAU
#  define P00_B1 0xCCCCCCCCCCCCCCCCU
#  define P00_B2 0xF0F0F0F0F0F0F0F0U
#  define P00_B3 0xFF00FF00FF00FF00U
#  define P00_B4 0xFFFF0000FFFF0000U
#  define P00_B5 0xFFFFFFFF00000000U
#  define P00_B6 0x10000000000000000U
# endif /* P00_UNSIGNED_MAX */
#endif /* P99_UINTMAX_MAX */
.
.
.

Ответ 4

Да, поскольку для всех практических целей количество возможных ширины ограничено:

#if ~0 == 0xFFFF
# define INT_WIDTH 16
#elif ~0 == 0xFFFFFFFF
# define INT_WIDTH 32
#else
# define INT_WIDTH 64
#endif

Ответ 5

Вы можете рассчитать его во время выполнения с помощью простого цикла, четко определенного и без опасности UB:

unsigned int u;
int c;

for (c=0, u=1; u; c++, u<<=1);

total_bits   = CHAR_BIT * sizeof(unsigned int);
value_bits   = c;
padding_bits = total_bits - value_bits;

Самый простой способ - проверить свои модульные тесты (у вас есть их, правда?), что value_bits идентичен вашему текущему определению INT_WIDTH.

Если вам действительно нужно вычислить его во время компиляции, я бы пошел с одним из заданных каскадов # if- # elif, либо тестируя UINT_MAX, либо вашу целевую систему.

Для чего вам это нужно? Может быть, YAGNI?

Ответ 6

Общее наблюдение заключается в том, что если вы полагаетесь на ширину типа данных в своих расчетах, вы должны использовать типы данных явной ширины, определенные в <stdint.h> например uint32_t.

Попытка подсчета байтов в стандартных типах попросит вопрос о том, что ваш "портативный" код будет делать в случае переполнения.

Ответ 7

Обычно размер int известен для данного компилятора/платформы. Если у вас есть макросы, которые определяют компилятор/платформу, вы можете использовать их для условного определения INT_WIDTH.

Вы можете посмотреть <sys/types.h> и его иждивенцы для примеров.