Есть ли способ вычислить ширину целочисленного типа во время компиляции?
Размер целочисленного типа (или любого типа) в единицах 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>
и его иждивенцы для примеров.