コンパイル時に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 14

回答:


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引数を2回評価し、CRC8つのネストされた呼び出しを使用するため、コードサイズに小さな組み合わせの爆発があることに注意してください。

$ cpp crc32table.c | wc -c
4563276

これを32ビットLinux上のGCC 4.6.0およびClang 2.8でテストしましたが、どちらも正しいテーブルを生成します。


素晴らしい、私は確かにこれを期待していなかった。+1します。
R.マルティーニョフェルナンデス

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

@フレッドあなたは正しいです。私もCunsigned longます。定数配列は、パック展開によって初期化されるように定義されていますD...D非タイプのテンプレートパラメータパックです。GCCでサポートされると、クラスで配列を宣言することもできますstatic unsigned long constexpr crc_table[] = { D... };が、GCCはクラス内の初期化子をまだ解析しません。利点はcompute<>::crc_table[I]、コードの後半で定数式内で使用できることです。
ヨハネスシャウブ-litb

5

C ++ 0x with 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
そのようにする場合、実際の値をプログラムにハードコードしないのはなぜですか?(もちろん、別の方法で取得した後。)コンパイル時間を節約します。
マシューリード

テストとtimesテンプレートの+1
fredoverflow

@Matthew:C ++ 03だけでは、選択肢はほとんどありません。フレッドのようにプリプロセッサを使用できますが、コンパイル時間は短縮されません。そして、どうやら、彼のプリプロセッサは彼の解決策に窒息します:)
R. Martinho Fernandes

@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には他に選択肢はありません
R.マルティーニ・フェルナンデス

ああ、私は質問の要件に十分な注意を払っていませんでした。+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ます。
R.マルティーニョフェルナンデス

このために文字列ミックスインを使用する必要はありません。Dの標準ライブラリ、genTables、および呼び出しサイトで結果を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個の要素があることを保証するため、配列のサイズを省略できます。マクロRの16回の呼び出しを使用して、16行のデータで配列を初期化します。

Rの各呼び出しは、4つの定数(マクロK)の4つのフラグメント(マクロF)に展開され、合計16の「列」のデータになります。

マクロKは、元の質問のコードでkによってインデックス付けされた展開されたループです。マクロCを呼び出して、値cを8回更新します。

このプリプロセッサベースのソリューションは、マクロ展開中にかなりのメモリを使用します。余分なレベルのマクロ展開を行うことで少し短くしようとしましたが、私のコンパイラはうまくいきませんでした。上記のコードは、Cygwin(Windows 7 64ビット8GB RAM)の下でVisual C ++ 2012とg ++ 4.5.3の両方で(ゆっくりと)コンパイルされます。

編集:

上記のフラグメントは、空白を含む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ビットの符号なしlong値を想定)!

別の編集:

別の回答のマクロに基づいてCマクロを変更して、余分な11バイトを圧縮し、拡張されたマクロサイズ全体を大幅に縮小しました。2.7 MBは、54 MB(すべてのマクロ展開の前の最終サイズ)ほど悪くはありませんが、それでも重要です。


これはcode-golfではないため、文字数を最小化する必要はありません。
イルマリカロネン

あ。そうです。その部分で私の悪い。この実装は移植性があると思います(つまり、C言語とプリプロセッサに準拠していることを意味します。実際の移植性は、環境のマクロ展開の厳密な制限に依存します)。
CasaDeRobison

3

最後の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)};

そして、コンパイラーは正しい量の要素があることを確認するので、配列のサイズを決して省かないでください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.