Как сделать преобразование набора битов / байтового массива в c
Учитывая массив, unsigned char q[32]="1100111..."
,
как я могу генерировать 4-байтовый набор битов, unsigned char p[4]
так, что бит этого набора битов равен значению внутри массива, например, первый байт p[0]= "q[0] ... q[7]"; 2-й байт p[1]="q[8] ... q[15]" и т. Д.
а также как сделать это в противоположном направлении, т. е. с учетом набора битов, сгенерировать массив?
мое собственное испытание для первой части.
unsigned char p[4]={0};
for (int j=0; j<N; j++)
{
if (q[j] == '1')
{
p [j / 8] |= 1 << (7-(j % 8));
}
}
Правильно ли это? какие условия проверить? Есть ли лучший способ?
РЕДАКТИРОВАТЬ - 1
Интересно, если выше эффективный способ? Поскольку размер массива может быть до 4096 или даже больше.
4 ответа
Я не думаю, что это будет работать. Вы сравниваете каждый "бит" с 1
когда это действительно должно быть '1'
, Вы также можете сделать его немного более эффективным, избавившись от if
:
unsigned char p[4]={0};
for (int j=0; j<32; j++)
{
p [j / 8] |= (q[j] == `1`) << (7-(j % 8));
}
Движение в обратном направлении тоже довольно просто. Просто замаскируйте каждый бит, который вы установили ранее.
unsigned char q[32]={0};
for (int j=0; j<32; j++) {
q[j] = p[j / 8] & ( 1 << (7-(j % 8)) ) + '0';
}
Вы заметите творческое использование (boolean) + '0'
конвертировать между 1/0 и '1'/'0'.
Во-первых, используйте strtoul
чтобы получить 32-битное значение. Затем преобразуйте порядок байтов в big-endian с помощью htonl
, Наконец, сохраните результат в вашем массиве:
#include <arpa/inet.h>
#include <stdlib.h>
/* ... */
unsigned char q[32] = "1100111...";
unsigned char result[4] = {0};
*(unsigned long*)result = htonl(strtoul(q, NULL, 2));
Есть и другие способы.
Но мне не хватает <arpa/inet.h>
!
Затем вам нужно знать, каков порядок байтов вашей платформы. Если это большой порядок байтов, то htonl
ничего не делает и может быть опущен. Если это мало-порядковый, то htonl
просто:
unsigned long htonl(unsigned long x)
{
x = (x & 0xFF00FF00) >> 8) | (x & 0x00FF00FF) << 8);
x = (x & 0xFFFF0000) >> 16) | (x & 0x0000FFFF) << 16);
return x;
}
Если вам повезет, ваш оптимизатор может увидеть, что вы делаете, и превратить его в эффективный код. Если нет, то, по крайней мере, все это реализуемо в регистрах и O(log N).
Если вы не знаете, каков порядок следования байтов в вашей платформе, вам необходимо определить его:
typedef union {
char c[sizeof(int) / sizeof(char)];
int i;
} OrderTest;
unsigned long htonl(unsigned long x)
{
OrderTest test;
test.i = 1;
if(!test.c[0])
return x;
x = (x & 0xFF00FF00) >> 8) | (x & 0x00FF00FF) << 8);
x = (x & 0xFFFF0000) >> 16) | (x & 0x0000FFFF) << 16);
return x;
}
Может быть long
8 байт!
Что ж, OP подразумевает 4-байтовые входы с размером их массива, но 8-байтовыми long
выполнимо:
#define kCharsPerLong (sizeof(long) / sizeof(char))
unsigned char q[8 * kCharsPerLong] = "1100111...";
unsigned char result[kCharsPerLong] = {0};
*(unsigned long*)result = htonl(strtoul(q, NULL, 2));
unsigned long htonl(unsigned long x)
{
#if kCharsPerLong == 4
x = (x & 0xFF00FF00UL) >> 8) | (x & 0x00FF00FFUL) << 8);
x = (x & 0xFFFF0000UL) >> 16) | (x & 0x0000FFFFUL) << 16);
#elif kCharsPerLong == 8
x = (x & 0xFF00FF00FF00FF00UL) >> 8) | (x & 0x00FF00FF00FF00FFUL) << 8);
x = (x & 0xFFFF0000FFFF0000UL) >> 16) | (x & 0x0000FFFF0000FFFFUL) << 16);
x = (x & 0xFFFFFFFF00000000UL) >> 32) | (x & 0x00000000FFFFFFFFUL) << 32);
#else
#error Unsupported word size.
#endif
return x;
}
За char
это не 8 бит (DSP любят это делать), вы сами по себе. (Вот почему это было большим делом, когда серия DSP SHARC имела 8-битные байты; это значительно облегчило перенос существующего кода, потому что, скажите, C делает ужасную работу по поддержке переносимости.)
А как насчет буферов произвольной длины? Нет смешных указателей, пожалуйста.
Главное, что можно улучшить с помощью версии OP, - это переосмыслить внутренности цикла. Вместо того, чтобы думать о выходных байтах как о фиксированном регистре данных, думайте о нем как о сдвиговом регистре, где каждый последующий бит сдвинут в правый конец (LSB). Это избавит вас от всех этих разделов и модов (которые, мы надеемся, оптимизированы для сдвигов).
Для здравомыслия я бросаю unsigned char
за uint8_t
,
#include <stdint.h>
unsigned StringToBits(const char* inChars, uint8_t* outBytes, size_t numBytes,
size_t* bytesRead)
/* Converts the string of '1' and '0' characters in `inChars` to a buffer of
* bytes in `outBytes`. `numBytes` is the number of available bytes in the
* `outBytes` buffer. On exit, if `bytesRead` is not NULL, the value it points
* to is set to the number of bytes read (rounding up to the nearest full
* byte). If a multiple of 8 bits is not read, the last byte written will be
* padded with 0 bits to reach a multiple of 8 bits. This function returns the
* number of padding bits that were added. For example, an input of 11 bits
* will result `bytesRead` being set to 2 and the function will return 5. This
* means that if a nonzero value is returned, then a partial byte was read,
* which may be an error.
*/
{ size_t bytes = 0;
unsigned bits = 0;
uint8_t x = 0;
while(bytes < numBytes)
{ /* Parse a character. */
switch(*inChars++)
{ '0': x <<= 1; ++bits; break;
'1': x = (x << 1) | 1; ++bits; break;
default: numBytes = 0;
}
/* See if we filled a byte. */
if(bits == 8)
{ outBytes[bytes++] = x;
x = 0;
bits = 0;
}
}
/* Padding, if needed. */
if(bits)
{ bits = 8 - bits;
outBytes[bytes++] = x << bits;
}
/* Finish up. */
if(bytesRead)
*bytesRead = bytes;
return bits;
}
Это ваша ответственность, чтобы убедиться, inChars
имеет нулевое значение Функция вернется к первому'0'
или же '1'
символ, который он видит, или если у него заканчивается буфер вывода. Некоторые примеры использования:
unsigned char q[32] = "1100111...";
uint8_t buf[4];
size_t bytesRead = 5;
if(StringToBits(q, buf, 4, &bytesRead) || bytesRead != 4)
{
/* Partial read; handle error here. */
}
Это просто читает 4 байта и перехватывает ошибку, если не может.
unsigned char q[4096] = "1100111...";
uint8_t buf[512];
StringToBits(q, buf, 512, NULL);
Это просто преобразует то, что он может, и устанавливает остаток в 0 бит.
Эту функцию можно было бы сделать лучше, если бы C имел возможность break
из более чем одного уровня цикла или switch
; в нынешнем виде мне нужно было бы добавить значение флага, чтобы получить тот же эффект, что и беспорядок, или мне пришлось бы добавить goto
, от которого я просто отказываюсь.
Если вы ищете максимальную эффективность, попробуйте использовать следующие методы:
замещать if
вычитанием '0'
(кажется, вы можете предположить, что ваши входные символы могут быть только 0
или же 1
). Также обработайте входные данные от более низких индексов до более высоких.
for (int c = 0; c < N; c += 8)
{
int y = 0;
for (int b = 0; b < 8; ++b)
y = y * 2 + q[c + b] - '0';
p[c / 8] = y;
}
Замените индексы массива автоматически увеличивающими указателями:
const char* qptr = q;
unsigned char* pptr = p;
for (int c = 0; c < N; c += 8)
{
int y = 0;
for (int b = 0; b < 8; ++b)
y = y * 2 + *qptr++ - '0';
*pptr++ = y;
}
Разверните внутренний цикл:
const char* qptr = q;
unsigned char* pptr = p;
for (int c = 0; c < N; c += 8)
{
*pptr++ =
qptr[0] - '0' << 7 |
qptr[1] - '0' << 6 |
qptr[2] - '0' << 5 |
qptr[3] - '0' << 4 |
qptr[4] - '0' << 3 |
qptr[5] - '0' << 2 |
qptr[6] - '0' << 1 |
qptr[7] - '0' << 0;
qptr += 8;
}
Одновременная обработка нескольких входных символов (с использованием битовых трюков или инструкций MMX) - это имеет большой потенциал ускорения!
В соответствии с вашим примером это не похоже на читаемость, и после (позднего) обновления мое решение выглядит очень похоже на Chriszuma, за исключением отсутствия скобок из-за порядка операций и добавления символа!! для обеспечения 0 или 1.
const size_t N = 32; //N must be a multiple of 8
unsigned char q[N+1] = "11011101001001101001111110000111";
unsigned char p[N/8] = {0};
unsigned char r[N+1] = {0}; //reversed
for(size_t i = 0; i < N; ++i)
p[i / 8] |= (q[i] == '1') << 7 - i % 8;
for(size_t i = 0; i < N; ++i)
r[i] = '0' + !!(p[i / 8] & 1 << 7 - i % 8);
printf("%x %x %x %x\n", p[0], p[1], p[2], p[3]);
printf("%s\n%s\n", q,r);