Есть ли способ вычислить ширину целочисленного типа во время компиляции?
Размер целочисленного типа (или любого типа) в единицах char
/ байт легко вычисляется как sizeof(type)
, Распространенной идиомой является умножение на CHAR_BIT
чтобы найти число битов, занимаемых типом, но в реализациях с битами заполнения это не будет равно ширине в значении битов. Хуже того, код вроде:
x>>CHAR_BIT*sizeof(type)-1
на самом деле может иметь неопределенное поведение, если CHAR_BIT*sizeof(type)
больше, чем фактическая ширина type
,
Для простоты предположим, что наши типы без знака. Тогда ширина type
является ceil(log2((type)-1)
, Есть ли способ вычислить это значение как константное выражение?
7 ответов
Существует функциональный макрос, который может определять биты **** значения целочисленного типа ****, но только если вы уже знаете максимальное значение этого типа. Получите ли вы постоянную времени компиляции или нет, зависит от вашего компилятора, но я думаю, что в большинстве случаев ответ - да.
Благодарим Хальварда Б. Фурусета за его функциональный макрос 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 значениями (т.е. со всеми 1 в двоичном виде), как и следовало ожидать с любым значением MAX. Также обратите внимание, что, хотя легко получить константу времени компиляции для максимального значения целого типа без знака (IMAX_BITS((unsigned type)-1)
), на момент написания этой статьи я не знал ни одного способа сделать то же самое для целочисленного типа со знаком без вызова определенного поведения реализации. Если я когда-нибудь узнаю, я отвечу на свой собственный связанный вопрос, здесь:
Вопрос C: минимальные и максимальные значения off_t (и других целочисленных типов со знаком) - переполнение стека
Сравните макросы из <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
Первый подход, если вы знаете, какой у вас стандартный тип (то есть ваш тип не 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 */
.
.
.
Вы можете вычислить его во время выполнения с простым циклом, четко определенным и без опасности 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 или вашу целевую систему.
Для чего тебе это? Может быть, ЯГНИ?
Общее замечание состоит в том, что если вы полагаетесь на ширину типа данных в своих вычислениях, вы должны использовать явные типы данных ширины, определенные в <stdint.h>
например uint32_t
,
Попытка подсчитать байты в стандартных типах - это вопрос о том, что будет делать ваш "переносимый" код в случае переполнения.
Да, поскольку для всех практических целей количество возможных ширин ограничено:
#if ~0 == 0xFFFF
# define INT_WIDTH 16
#elif ~0 == 0xFFFFFFFF
# define INT_WIDTH 32
#else
# define INT_WIDTH 64
#endif
Обычно размер int
известен для данного компилятора / платформы. Если у вас есть макросы, которые идентифицируют компилятор / платформу, вы можете использовать их для условного определения INT_WIDTH
,
Вы можете взглянуть на <sys/types.h>
и его иждивенцы для примеров.