在编译时计算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
这里的客观主要获胜标准是什么?
John Dvorak 2014年

此外,在这里也没有针对语言的问题。您应该删除c ++标记。
骄傲的haskeller 2014年

Answers:


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

我在32位Linux上的GCC 4.6.0和Clang 2.8中对此进行了测试,并且两者都能产生正确的表。


太棒了,我当然没想到这一点。+1。
R. Martinho Fernandes

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(trunk 134121)。


关于C >> 1,不是将负值转移到正确的未指定行为吗?;)
fredoverflow 2011年

另外,您能否解释定义数组的部分?我在那儿迷路了……
fredoverflow

@弗雷德,你是对的。我还将做C一个unsigned long。常量数组定义为通过pack扩展进行初始化D...D是非类型模板参数包。一旦GCC支持它,您也可以使用声明该数组在类中static unsigned long constexpr crc_table[] = { D... };,但是GCC尚未解析括号内的类初始化器。好处是compute<>::crc_table[I]可以在代码的后面的常量表达式中使用。
Johannes Schaub-litb 2011年

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_tableconstexpr


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
如果您要这样做,为什么不将实际值硬编码到程序中呢?(当然,用另一种方法获得它们之后。)节省了编译时间。
马修(Matthew)读了

+1用于测试和times模板
fredoverflow 2011年

@Matthew:只有C ++ 03,别无选择。您可以像Fred一样使用预处理器,但这不会缩短编译时间。显然,他的预处理器使他的解决方案感到窒息:)
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. Martinho Fernandes

嗯,我没有足够注意这个问题的要求。+1,尽管我不确定我是否再喜欢这个问题。我喜欢实用的代码挑战:P
Matthew阅读

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. Martinho Fernandes

无需为此使用字符串混合,这是我们在D的标准库,genTables调用站点中将结果存储在const数据段中的方式。
Martin Nowak

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的每次调用都会扩展为四个常量(宏K)的四个片段(宏F),总共有16个“列”数据。

宏K是在原始问题的代码中由k索引的展开循环。它通过调用宏C来八次更新值c。

这种基于预处理器的解决方案在宏扩展期间会占用大量内存。我尝试通过增加宏扩展级别并使它更短一些,以使其更短一些。上面的代码在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位无符号长值)!

另一个编辑:

我根据另一个答案中的宏修改了C宏,从而挤出了额外的11个字节,并大大减小了完整的扩展宏大小。尽管2.7 MB不如54 MB(所有宏扩展的先前最终大小)那么糟糕,但它仍然意义重大。


这不是代码高尔夫,因此您不需要最小化字符数。
Ilmari Karonen 2012年

啊。就是这样 我的那部分不好。尽管我确实认为这种实现是可移植的(通过这种方式,我的意思是它符合C语言和预处理程序;其真正的可移植性取决于环境对宏扩展的确切限制)。
CasaDeRobison 2012年

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)};

永远不要遗漏数组的大小,因为编译器将随后验证您是否具有正确数量的元素。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.