Обчислити таблицю CRC32 під час компіляції [закрито]


16

Довідник реалізація CRC32 обчислює таблицю пошуку під час виконання:

/* Table of CRCs of all 8-bit messages. */
unsigned long crc_table[256];

/* Flag: has the table been computed? Initially false. */
int crc_table_computed = 0;

/* Make the table for a fast CRC. */
void make_crc_table(void)
{
    unsigned long c;

    int n, k;
    for (n = 0; n < 256; n++) {
        c = (unsigned long) n;
        for (k = 0; k < 8; k++) {
            if (c & 1) {
                c = 0xedb88320L ^ (c >> 1);
            } else {
                c = c >> 1;
            }
        }
        crc_table[n] = c;
    }
    crc_table_computed = 1;
}

Чи можете ви обчислити таблицю під час компіляції, тим самим позбувшись функції та прапор статусу?


2
Який тут об'єктивний первинний критерій виграшу?
Джон Дворак

також тут нахмурені питання, пов'язані з мовою. ви повинні видалити тег c ++.
гордий haskeller

Відповіді:


12

Ось звичайне рішення C:

crc32table.c

#if __COUNTER__ == 0

    /* Initial setup */
    #define STEP(c) ((c)>>1 ^ ((c)&1 ? 0xedb88320L : 0))
    #define CRC(n) STEP(STEP(STEP(STEP(STEP(STEP(STEP(STEP((unsigned long)(n)))))))))
    #define CRC4(n) CRC(n), CRC(n+1), CRC(n+2), CRC(n+3)

    /* Open up crc_table; subsequent iterations will fill its members. */
    const unsigned long crc_table[256] = {

    /* Include recursively for next iteration. */
    #include "crc32table.c"

#elif __COUNTER__ < 256 * 3 / 4

    /* Fill the next four CRC entries. */
    CRC4((__COUNTER__ - 3) / 3 * 4),

    /* Include recursively for next iteration. */
    #include "crc32table.c"

#else

    /* Close crc_table. */
    };

#endif

Він спирається на нестандартний __COUNTER__макрос, а також на семантику оцінювання, де __COUNTER__він оцінюється до того, як він передається як аргумент макросу.

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

$ cpp crc32table.c | wc -c
4563276

Я перевірив це в GCC 4.6.0 і Clang 2.8 на 32-бітному Linux, і обидва створюють правильну таблицю.


Дивовижно, я точно цього не очікував. Майте +1.
Р. Мартіньо Фернандес

9

Основна петля

for (k = 0; k < 8; k++) {
    if (c & 1) {
        c = 0xedb88320L ^ (c >> 1);
    } else {
        c = c >> 1;
    }
}

можна перетворити на метафункцію:

template <unsigned c, int k = 8>
struct f : f<((c & 1) ? 0xedb88320 : 0) ^ (c >> 1), k - 1> {};

template <unsigned c>
struct f<c, 0>
{
    enum { value = c };
};

Потім 256 викликів до цієї мета-функції (для ініціалізатора масиву) генерується препроцесором:

#define A(x) B(x) B(x + 128)
#define B(x) C(x) C(x +  64)
#define C(x) D(x) D(x +  32)
#define D(x) E(x) E(x +  16)
#define E(x) F(x) F(x +   8)
#define F(x) G(x) G(x +   4)
#define G(x) H(x) H(x +   2)
#define H(x) I(x) I(x +   1)
#define I(x) f<x>::value ,

unsigned crc_table[] = { A(0) };

Якщо у вас встановлено Boost, генерувати ініціалізатор масиву трохи простіше:

#include <boost/preprocessor/repetition/enum.hpp>

#define F(Z, N, _) f<N>::value

unsigned crc_table[] = { BOOST_PP_ENUM(256, F, _) };

Нарешті, наступний тестовий драйвер просто друкує всі елементи масиву на консоль:

#include <cstdio>

int main()
{
    for (int i = 0; i < 256; ++i)
    {
        printf("%08x  ", crc_table[i]);
    }
}

6

Розчин C ++ 0x

template<unsigned long C, int K = 0>
struct computek {
  static unsigned long const value = 
    computek<(C & 1) ? (0xedb88320L ^ (C >> 1)) : (C >> 1), K+1>::value;
};

template<unsigned long C>
struct computek<C, 8> {
  static unsigned long const value = C;
};

template<int N = 0, unsigned long ...D>
struct compute : compute<N+1, D..., computek<N>::value> 
{ };

template<unsigned long ...D>
struct compute<256, D...> {
  static unsigned long const crc_table[sizeof...(D)];
};

template<unsigned long...D>
unsigned long const compute<256, D...>::crc_table[sizeof...(D)] = { 
  D...
};

/* print it */
#include <iostream>

int main() {
  for(int i = 0; i < 256; i++)
    std::cout << compute<>::crc_table[i] << std::endl;
}

Працює на GCC (4.6.1) та Clang (магістраль 134121).


Що стосується C >> 1, чи не зміщення негативних значень на правильну неуточнену поведінку? ;)
fredoverflow

Також, чи можете ви пояснити частину, де ви визначаєте масив? Я трохи загубився там ...
fredoverflow

@Fred ти маєш рацію. Я також повинен зробити . Постійний масив визначається для ініціалізації розширенням пакету . це пакет параметрів шаблону нетипового шаблону. Після того, як GCC його підтримує, можна також оголосити масив у своєму класі , але GCC ще не розбирає виділені в класі ініціалізатори. Перевага буде в тому, що вона може бути використана в постійних виразах пізніше в коді. Cunsigned longD...Dstatic unsigned long constexpr crc_table[] = { D... };compute<>::crc_table[I]
Йоханнес Шауб - ліб

5

C ++ 0x з constexpr. Працює над GCC4.6.1

constexpr unsigned long computek(unsigned long c, int k = 0) {
  return k < 8 ? computek((c & 1) ? (0xedb88320L ^ (c >> 1)) : (c >> 1), k+1) : c;
}

struct table {
  unsigned long data[256];
};

template<bool> struct sfinae { typedef table type; };
template<> struct sfinae<false> { };

template<typename ...T>
constexpr typename sfinae<sizeof...(T) == 256>::type compute(int n, T... t) { 
  return table {{ t... }}; 
}

template<typename ...T>
constexpr typename sfinae<sizeof...(T) <= 255>::type compute(int n, T... t) {
  return compute(n+1, t..., computek(n));
}

constexpr table crc_table = compute(0);

#include <iostream>

int main() {
  for(int i = 0; i < 256; i++)
    std::cout << crc_table.data[i] << std::endl;
}

Потім ви можете використовувати crc_table.data[X]під час компіляції, оскільки crc_tableє constexpr.


4

Це моя перша метапрограма :

#include <cassert>
#include <cstddef>

template <std::size_t N, template <unsigned long> class T, unsigned long In>
struct times : public T<times<N-1,T,In>::value> {};

template <unsigned long In, template <unsigned long> class T>
struct times<1,T,In> : public T<In> {};

template <unsigned long C>
struct iter {
    enum { value = C & 1 ? 0xedb88320L ^ (C >> 1) : (C >> 1) };
};

template <std::size_t N>
struct compute : public times<8,iter,N> {};

unsigned long crc_table[] = {
    compute<0>::value,
    compute<1>::value,
    compute<2>::value,
    // .
    // .
    // .
    compute<254>::value,
    compute<255>::value,
};

/* Reference Table of CRCs of all 8-bit messages. */
unsigned long reference_table[256];

/* Flag: has the table been computed? Initially false. */
int reference_table_computed = 0;

/* Make the table for a fast CRC. */
void make_reference_table(void)
{
    unsigned long c;

    int n, k;
    for (n = 0; n < 256; n++) {
        c = (unsigned long) n;
        for (k = 0; k < 8; k++) {
            if (c & 1) {
                c = 0xedb88320L ^ (c >> 1);
            } else {
                c = c >> 1;
            }
        }
        reference_table[n] = c;
    }
    reference_table_computed = 1;
}

int main() {
    make_reference_table();
    for(int i = 0; i < 256; ++i) {
        assert(crc_table[i] == reference_table[i]);
    }
}

Я "жорстко кодував" дзвінки до шаблону, який робить обчислення :)


1
Якщо ви збираєтеся робити це саме так, чому б вам просто не зафіксувати фактичні значення в програмі? (Звичайно, отримавши їх іншим методом.) Економить час компіляції.
Матвій

+1 для тесту та timesшаблону
fredoverflow

@Matthew: із лише C ++ 03, вибору мало. Ви можете використовувати препроцесор, як це робив Фред, але це не скоротить час компіляції. І, мабуть, його препроцесор задавлюється його рішенням :)
Р. Мартіньо Фернандес

@Matthew Сенс полягає в тому, щоб насправді обчислити його під час компіляції , а не вводити їх у жорсткий код . Відповідь Фреда створює масив цієї форми: unsigned crc_table[] = { f<0>::value , f<0 + 1>::value , f<0 + 2>::value , f<0 + 2 + 1>::value , f<0 + 4>::value , f<0 + 4 + 1>::value , f<0 + 4 + 2>::value , f<0 + 4 + 2 + 1>::value , f<0 + 8>::value ,за допомогою препроцесора. Це збирається приблизно стільки ж, скільки і моє. Якщо ви хочете, ви можете прочитати цей останній абзац як "Я розкрутив зовнішню петлю". Немає іншого вибору в C ++ 03
Р. Мартіньо Фернандес

Ах, я не приділяв належної уваги вимогам питання. +1, хоча я не впевнений, що питання мені більше подобається. Мені подобаються практичні виклики коду: P
Метью читайте

3

D

import std.stdio, std.conv;

string makeCRC32Table(string name){

  string result = "immutable uint[256]"~name~" = [ ";

  for(uint n; n < 256; n++){
    uint c = n;
    for(int k; k < 8; k++)
      c = (c & 1) ? 0xedb88320L ^ (c >> 1) : c >>1;
    result ~= to!string(c) ~ ", ";
  }
  return result ~ "];";
}

void main(){

  /* fill table during compilation */
  mixin(makeCRC32Table("crc_table"));

  /* print the table */
  foreach(c; crc_table)
    writeln(c);
}

Це насправді викликає сором C ++, чи не так?


2
Насправді я віддаю перевагу не строко типізованим рішенням. Це дуже схоже на час компіляції eval.
Р. Мартіньо Фернандес

Для цього не потрібно використовувати рядок mixin, ось як ми це робимо в стандартній бібліотеці D , genTables та call-site для зберігання результату в сегменті даних const.
Мартін Новак

3

C / C ++, 306 295 байт

#define C(c)((c)>>1^((c)&1?0xEDB88320L:0))
#define K(c)(C(C(C(C(C(C(C(C(c))))))))),
#define F(h,l)K((h)|(l+0))K((h)|(l+1))K((h)|(l+2))K((h)|(l+3))
#define R(h)F(h<<4,0)F(h<<4,4)F(h<<4,8)F(h<<4,12)
unsigned long crc_table[]={R(0)R(1)R(2)R(3)R(4)R(5)R(6)R(7)R(8)R(9)R(10)R(11)R(12)R(13)R(14)R(15)};

Працюючи в зворотному напрямку, ми завершуємо безпідписаний довгий масив з назвою crc_table. Ми можемо опустити розмір масиву, оскільки макроси гарантують, що в масиві точно 256 елементів. Ми ініціалізуємо масив із 16 'рядками' даних, використовуючи 16 викликів макросу R.

Кожне виклик R розширюється на чотири фрагменти (макрос F) з чотирьох констант (макро K) для загальної кількості 16 "стовпців" даних.

Макрос K - це розкручений цикл, індексований k в коді з початкового запитання. Він оновлює значення c вісім разів, викликаючи макрос C.

Це рішення на основі препроцесора використовує зовсім небагато пам'яті під час розширення макросу. Я спробував зробити його трохи коротшим, маючи додатковий рівень розширення макросу, і мій компілятор рвонув. Код вище компілюється (повільно) з Visual C ++ 2012 та g ++ 4.5.3 під Cygwin (Windows 7 64-бітова 8 Гб оперативної пам’яті).

Редагувати:

Фрагмент вище 295 байт, включаючи пробіл. Після розширення всіх макросів, крім C, він зростає до 9 918 байт. Зі збільшенням кожного рівня макросу C розмір швидко зростає:

  1. 25,182
  2. 54,174
  3. 109,086
  4. 212,766
  5. 407,838
  6. 773,406
  7. 1,455,390
  8. 2,721,054

Тож до моменту розширення всіх макросів цей маленький 295-байтний файл розшириться на понад 2,7 мегабайт коду, який потрібно скомпілювати для створення оригінального масиву 1024 байтів (якщо вважати 32 бітні неподписані довгі значення)!

Ще одна редакція:

Я змінив макрос С на основі макросу з іншої відповіді, щоб видавити зайві 11 байт, і значно зменшив повний розмір розширеного макросу. Хоча 2,7 Мб не так вже й погано, як 54 Мб (попередній кінцевий розмір усіх розширень макросу), він все ще є значним.


Це не код-гольф , тому вам не потрібно мінімізувати кількість символів.
Ільмарі Каронен

Ага. Так воно і є. Моє погано в цій частині. Хоча я думаю, що ця реалізація є портативною (під якою я маю на увазі, що вона відповідає мові C та препроцесору; її справжня портативність залежатиме від точних обмежень середовища щодо розширення макросу).
CasaDeRobison

3

Я б змінив попередню відповідь, замінивши останні три рядки на:

#define crc4( x)    crcByte(x), crcByte(x+1), crcByte(x+2), crcByte(x+3)
#define crc16( x)   crc4(x), crc4(x+4), crc4(x+8), crc4(x+12)
#define crc64( x)   crc16(x), crc16(x+16), crc16(x+32), crc16(x+48)
#define crc256( x)  crc64(x), crc64(x+64), crc64(x+128), crc64(x+192)

Де crcByte - це його макрос K без кінцевої коми. Потім побудуйте саму таблицю з:

static const unsigned long crc32Table[256] = { crc256( 0)};

І ніколи не залишайте розмір масиву, оскільки компілятор перевірить, чи є у вас правильна кількість елементів.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.