2016-06-28 7 views
6

Я сейчас работаю над проектом, в котором мне нужны битовые наборы. Я использую массив uint64_t для битового набора.C - BitArray - установить один бит uint64_t

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

uint64_t index = 42; 
bArr[index/64] |= (((uint64_t)1)<<(index%64)); 

Я могу переписать деление и по модулю с некоторым умным и и bithift операций, но я обеспокоен литой 1. Мне нужен этот прилив, поскольку в противном случае 1 рассматривается как 32-битный блок. Как видно из этого примера, вы получаете неправильный результат без литья:

uint64_t bArr[4];       // 256 bits 
bArr[0] = bArr[1] = bArr[2] = bArr[3] = 0; // Set to 0 

uint64_t i = 255; 
bArr[i/64] = (bArr[i/64] | (((uint64_t)1)<<(i%64))); 

uint32_t i2; 
for (i2 = 0; i2 < 256; i2++) { 
    if ((bArr[i2/64] & (((uint64_t)1)<<(i2%64))) != 0) { 
    printf("bArray[%" PRIu32 "] = 1\n", i2); 
    } 
} 

Могу ли я обойти этот прилив умным способом? Я думал, что производительность, вероятно, страдает от броска на каждый читать/писать ...

+0

Do * not * переписать раздел и по модулю быть «умным»; компилятор, безусловно, достаточно умен, чтобы уже сделать эти оптимизации для вас. Также рассмотрите использование 'CHAR_BIT * sizeof bArr [0]' вместо '64', чтобы избежать магических чисел. – unwind

+0

@unwind Спасибо за подсказку. Я проверю его с помощью моего кода. Скорее всего, это так. – Matthias

+0

Если вы ищете скорость, предоставьте таблицу 'const uint64_t' с 64 различными константами ULL (1 предварительно перемещенными во все возможные места) и проиндексируйте их. – tofro

ответ

4

В результате типа << оператора является типом левого операнда (после целых поощрений), поэтому вы должны использовать правильный тип: 1 имеет типа int но вам нужен тип uint64_t.

Вы можете использовать:

(uint64_t) 1 

или

UINT64_C(1) /* you have to include <stdint.h> */ 

или

1ULL 

(для последнего предполагающей unsigned long long является 64-бит в вашей системе, которая, скорее всего,).

Но все они эквивалентны: все эти выражения являются целыми константными выражениями, а их значение вычисляется во время компиляции, а не во время выполнения.

+0

Замечательно, что это происходит во время компиляции и не влияет на производительность (только угадывает код) ... – Matthias

+1

Подробный 'unsigned long long' не менее 64-бит, поэтому' bArr [index/64] | = 1ULL << (индекс% 64), 'безусловно, будет работать. Предполагая, что 'unsigned long long' является 64-битным, не требуется. – chux

+0

@chux Будет ли он прерываться, когда 'unsigned long long' больше 64-битного? Я не совсем уверен, что происходит. Не могли бы вы объяснить, что происходит без броска? Я увидел неправильный вывод и предполагал, что ему нужно что-то делать с шириной (и, следовательно, успешно попробовал бросок). Я хотел бы знать, почему код сломался ... – Matthias

1

Если я правильно вас понимаю, вам нужен буквальный 1 длиной не менее 64 бит. Вы можете get this без каких-либо бросков, написав 1ull вместо 1. Это создает литерал unsigned long long со значением 1. Однако тип не гарантирует, что он не будет длиннее 64-битного, поэтому, если вы полагаетесь на то, что он точно соответствует 64-битам, вам может понадобиться бросок в конце концов.

3

Литой само по себе не влияет на производительность. Это компиляция времени, которая сообщает компилятору тип выражения.

В этом случае они представляют собой целые числа и выражения, поэтому влияние производительности не должно быть.

+0

О, ты снова :) Итак, как вы, вероятно, помните, это в значительной степени ваш код (только с 'uint64_t'). Также знаете ли вы, какой размер, вероятно, лучше всего использовать для массива 'uint64_t',' uint32_t', 'uint16_t' или даже' uint8_t'? У меня есть размер кеша L1/L2, но я мало знаю об этом ... – Matthias

+0

* cast [...] - это компиляция времени, которая сообщает компилятору тип выражения * it is not правильно сказать, что бросок - это компиляция времени, когда в большинстве случаев это не так. – ouah

3

C предлагает макросы для этого, которые расширят целочисленную константу до типа int_leastN_t.

INTN_C(value) 
UINTN_C(value) 

Пример

#include <stdint.h>. 
bArr[index/64] |= UINT64_C(1) << (index%64); 

В общем лучше, чтобы избежать литья. Иногда кастинг неожиданно вызывает выражение более узкое, чем ожидалось.


Выгода UINT64_C: uint_least_64_t/int_least_64_t типов должны существовать (C99). int64_t/uint64_t являются необязательными.

+0

Хороший макрос, спасибо за подсказку :) В этом случае кастинг должен быть прекрасным, так как я всегда работаю над 'uin64_t' правильно? – Matthias

+0

Есть ли какая-либо польза в выборе 'INT64_C' над литой в' (int64_t) '? – a3f

+1

@ a3f Ответ отредактирован для решения проблемы. Когда N является> = 'unsigned/int' width и' (u) intN_t' существуют, конечно, 'INTN_C()' и casting '(intN_t)' ведут себя одинаково. – chux