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

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

x>>CHAR_BIT*sizeof(type)-1

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

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

Ответы [ 7 ]

10 голосов
/ 04 января 2011

Существует функционально-подобный макрос, который может определять биты значения **** целочисленного типа ****, но только если вы уже знаете максимальное значение этого типа.Получите ли вы постоянную времени компиляции или нет, зависит от вашего компилятора, но я думаю, что в большинстве случаев ответ будет положительным.

Благодарность Холлварду Б. Фурусету за его функциональный макрос 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 битов:
(РЕДАКТИРОВАНИЕ 1/3/2011 23:30 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)), на момент написания этой статьи я не знаю, как сделать то же самое для подписанногоцелочисленный тип без вызова реализации, определенного поведения.Если я когда-нибудь узнаю, я отвечу на свой собственный вопрос SO, здесь:
C вопрос: минимальные и максимальные значения off_t (и другие целочисленные типы со знаком) - переполнение стека

5 голосов
/ 18 октября 2010

Сравните макросы из <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
2 голосов
/ 18 октября 2010

Первый подход, если вы знаете, какой у вас стандартный тип (т. Е. Ваш тип не 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 */
.
.
.
0 голосов
/ 18 октября 2010

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

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

0 голосов
/ 18 октября 2010

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

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

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

Зачем тебе это нужно? Может быть, ЯГНИ?

0 голосов
/ 18 октября 2010

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

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

0 голосов
/ 18 октября 2010

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

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