Derleme zamanında CRC32 tablosunu hesaplama [kapalı]


16

CRC32 referans uygulama çalışma zamanında bir arama tablosu hesaplar:

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

Tabloyu derleme zamanında hesaplayabilir, böylece işlevden ve durum işaretinden kurtulabilir misiniz?


2
Burada nesnel birincil kazanma kriteri nedir?
John Dvorak

Ayrıca, dile özgü sorular burada kaşlarını çattı. c ++ etiketini kaldırmalısınız.
gururlu haskeller

Yanıtlar:


12

İşte düz bir C çözümü:

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

Standart dışı __COUNTER__makroya __COUNTER__ve bir makroya argüman olarak geçmeden önce değerlendirilen bir değerlendirme semantiğine dayanır .

O beri Not STEPdeğerlendirir onun iki katı argüman ve CRCkullanımları bunun sekiz iç içe çağırmaları, kod boyutunda küçük bir kombinasyon patlama oluyor:

$ cpp crc32table.c | wc -c
4563276

Bunu 32 bit Linux'ta GCC 4.6.0 ve Clang 2.8'de test ettim ve her ikisi de doğru tabloyu üretti.


Harika, kesinlikle bunu beklemiyordum. +1.
R. Martinho Fernandes

9

Çekirdek döngü

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

bir meta işleve dönüştürülebilir:

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

Ardından, ön işlemci tarafından bu meta işlevine (dizi başlatıcısı için) 256 çağrı oluşturulur:

#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 yüklüyse, dizi başlatıcısını oluşturmak biraz daha basittir:

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

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

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

Son olarak, aşağıdaki test sürücüsü tüm dizi öğelerini konsola yazdırır:

#include <cstdio>

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

6

C ++ 0x çözümü

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) ve Clang (gövde 134121) üzerinde çalışır.


İlgili C >> 1sağ belirtilmemiş davranışa negatif değerler kayması değil midir? ;)
fredoverflow

Ayrıca, diziyi tanımladığınız bölümü açıklayabilir misiniz? Orada biraz kayboldum ...
fredoverflow

@ Haklısın. Ben de yapacaktır Cbir unsigned long. Sabit dizi, paket genişletme tarafından başlatılacak şekilde tanımlanır D.... D, tür olmayan bir şablon parametre paketidir. GCC bunu desteklediğinde, dizi ile sınıfta da bildirilebilir static unsigned long constexpr crc_table[] = { D... };, ancak GCC henüz sınıf içi başlatıcıları ayrıştırmaz. Yarar compute<>::crc_table[I], kodun ilerleyen bölümlerinde sabit ifadeler içinde kullanılabilmesidir.
Johannes Schaub - litb

5

C ++ 0x ile constexpr. GCC4.6.1 üzerinde çalışır

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

Daha sonra kullanabilirsiniz crc_table.data[X]çünkü derleme zamanında crc_tableolduğunu constexpr.


4

Bu benim ilk meta programım :

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

Hesaplamayı yapan şablona çağrıları "hardcoded" :)


1
Bunu bu şekilde yapacaksanız, neden gerçek değerleri programa kodlamasınız ki? (Elbette başka bir yöntemle elde ettikten sonra.) Derleme zamanından tasarruf sağlar.
Matthew,

Test ve timesşablon için +1
fredoverflow 29:11

@ Mathew: sadece C ++ 03 ile çok az seçenek var. Fred'in yaptığı gibi önişlemciyi kullanabilirsiniz, ancak bu derleme süresini kısaltmaz. Ve görünüşe göre, ön işlemcisi çözümünü boğuyor :)
R. Martinho Fernandes

@Matthew Asıl olarak bunu derleme zamanında hesaplamak , onları sabit kodlamak değil. Fred'in cevabı şu formun bir dizisini oluşturur: 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 ,önişlemciyi kullanma. Benimki kadar derlemek uzun sürüyor. İsterseniz, son paragrafı "Dış döngüyü açtım" olarak okuyabilirsiniz. C ++ 03
R. Martinho'da

Ah, sorunun gereklerine yeterince dikkat etmiyordum. +1, ama artık soruyu sevdiğimden emin değilim. Kod zorluklarımın pratik olmasını seviyorum: P
Matthew Read

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

Gerçekten C ++ utanç verici, değil mi?


2
Aslında, dizgi olmayan tip çözümleri tercih ediyorum. Bu derleme zamanı gibi görünüyor eval.
R. Martinho Fernandes

Bunun için bir string mixin kullanmaya gerek yok, sonucu D'nin standart kütüphanesinde, genTable'larda ve çağrı sitesinde bunu const veri segmentinde saklamak için nasıl yaptığımız .
Martin Nowak

3

C / C ++, 306 295 bayt

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

Tersine çalışarak, crc_table adında imzasız uzun bir dizi ile ayrılırız. Makroların dizide tam olarak 256 öğe olmasını sağlayacağından dizinin boyutunu atlayabiliriz. Diziyi 16 R makro çağrısını kullanarak 16 'satır' veri ile başlatırız.

Her R çağrılması, toplam 16 'sütun' veri için dört sabitten (makro K) dört parçaya (makro F) genişler.

K makrosu, orijinal sorudaki kodda k tarafından dizine eklenmiş açılmamış döngüdür. C makroyu çağırarak c değerini sekiz kez günceller.

Bu önişlemci tabanlı çözüm, makro genişletme sırasında biraz bellek kullanır. Ekstra bir makro genişletme düzeyine sahip olarak biraz daha kısa yapmaya çalıştım ve derleyicim kustu. Yukarıdaki kod Cygwin (Windows 7 64 bit 8GB RAM) altında hem Visual C ++ 2012 hem de g ++ 4.5.3 ile derlenir (yavaşça).

Düzenle:

Yukarıdaki parça, beyaz boşluk dahil 295 bayttır. C hariç tüm makroları genişlettikten sonra 9.918 bayta kadar büyür. Her bir C makrosu düzeyi genişletildikçe, boyut hızla büyür:

  1. 25182
  2. 54174
  3. 109086
  4. 212766
  5. 407838
  6. 773406
  7. 1455390
  8. 2721054

Bu nedenle, tüm makrolar genişletildiğinde, bu 295 baytlık dosya, orijinal 1024 bayt dizisini oluşturmak için derlenmesi gereken 2.7 megabayttan fazla koda genişler (32 bit işaretsiz uzun değerler varsayarak)!

Başka bir düzenleme:

Fazladan bir 11 bayt sıkıştırmak için C makrosunu başka bir cevaptan bir makroya göre değiştirdim ve tam genişletilmiş makro boyutunu büyük ölçüde azalttım. 2,7 MB, 54 MB (tüm makro genişletmelerinin önceki son boyutu) kadar kötü olmasa da, yine de önemlidir.


Bu kod golf değildir , bu nedenle karakter sayısını en aza indirmenize gerek yoktur.
Ilmari Karonen

Ah. İşte bu. Bu taraftaki kötülerim. Bu uygulamanın taşınabilir olduğunu düşünmeme rağmen (yani C dilini ve önişlemciyi uyumlu kılıyor; gerçek taşınabilirliği, ortamın makro genişlemedeki kesin sınırlarına bağlı olacaktır).
CasaDeRobison

3

Son üç satırı değiştirerek önceki yanıtı değiştirirdim:

#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, son virgül içermeyen K makrosu. Sonra tabloyu kendisiyle oluşturun:

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

Ve derleyicinin boyutunu asla dışarıda bırakmayın çünkü derleyici doğru miktarda öğeye sahip olduğunuzu doğrular.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.