컴파일 타임에 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

또한 언어 별 질문이 여기에 뿌려집니다. 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

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 (트렁크 134121)에서 작동합니다.


관련하여 C >> 1, 우측없는 동작에 부정적인 값을 변화되지 않는 이유는 무엇입니까? ;)
fredoverflow

또한 배열을 정의하는 부분을 설명 할 수 있습니까? 나는 거기에서 조금 길을 잃었다 ...
fredoverflow at

@Fred 당신이 맞아요. 나는 또한을 만들 C것이다 unsigned long. 상수 배열은 팩 확장으로 초기화되도록 정의됩니다 D.... D유형이 아닌 템플릿 매개 변수 팩입니다. GCC가이를 지원하면을 사용하여 클래스를 배열로 선언 할 수도 static unsigned long constexpr crc_table[] = { D... };있지만 GCC는 괄호로 묶인 클래스 초기화 프로그램을 아직 구문 분석하지 않습니다. compute<>::crc_table[I]코드의 뒷부분에있는 상수 식 내에서 사용할 수 있다는 이점이 있습니다 .
Johannes Schaub-litb

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

그런 다음 is 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 읽기

테스트 및 times템플릿 +1
fredoverflow

@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 Read

3

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

이를 위해 문자열 믹스 인을 사용할 필요가 없습니다 . 결과를 const 데이터 세그먼트에 저장하기 위해 D의 표준 라이브러리, genTablescall-site 에서 수행하는 방법이 있습니다.
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이라는 부호없는 long 배열로 마무리합니다. 매크로는 배열에 정확히 256 개의 요소가 있도록 배열의 크기를 생략 할 수 있습니다. 매크로 R의 16 개의 호출을 사용하여 16 개의 '행'데이터로 배열을 초기화합니다.

R의 각 호출은 총 16 개의 '열'데이터에 대해 4 개의 상수 (매크로 K)의 4 개의 단편 (매크로 F)으로 확장됩니다.

매크로 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.7MB 이상의 코드로 확장되어 원래 1024 바이트 배열을 생성하기 위해 컴파일되어야합니다 (32 비트 부호없는 긴 값을 가정)!

또 다른 편집 :

다른 답변의 매크로를 기반으로 C 매크로를 수정하여 추가 11 바이트를 짜내고 전체 확장 매크로 크기를 크게 줄였습니다. 2.7MB는 54MB (모든 매크로 확장의 이전 최종 크기)만큼 나쁘지는 않지만 여전히 중요합니다.


이것은 code-golf 가 아니므로 문자 수를 최소화 할 필요가 없습니다.
Ilmari Karonen

아 그렇습니다. 그 부분에 대한 나의 나쁜. 비록이 구현이 이식 가능하다고 생각하지만 (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)};

컴파일러는 올바른 양의 요소가 있는지 확인하므로 배열의 크기를 절대로 두지 마십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.