Calcola la tabella CRC32 in fase di compilazione [chiuso]


16

L' implementazione di riferimento di CRC32 calcola una tabella di ricerca in fase di esecuzione:

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

Puoi calcolare la tabella in fase di compilazione, eliminando così la funzione e il flag di stato?


2
Qual è il criterio vincente primario oggettivo qui?
John Dvorak,

inoltre, le domande specifiche sulla lingua sono disapprovate qui. dovresti rimuovere il tag c ++.
orgoglioso haskeller il

Risposte:


12

Ecco una semplice soluzione 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

Si basa sulla __COUNTER__macro non standard , nonché su una semantica di valutazione in cui __COUNTER__viene valutata prima di essere passata come argomento a una macro.

Si noti che, poiché STEPvaluta due volte il suo argomento e ne CRCutilizza otto invocazioni nidificate, esiste una piccola esplosione combinatoria nella dimensione del codice:

$ cpp crc32table.c | wc -c
4563276

Ho provato questo in GCC 4.6.0 e Clang 2.8 su Linux a 32 bit ed entrambi producono la tabella corretta.


Fantastico, di certo non me lo aspettavo. Prendi un +1.
R. Martinho Fernandes,

9

Il ciclo principale

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

può essere convertito in una meta-funzione:

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

Quindi, 256 chiamate a questa meta-funzione (per l'inizializzatore di array) vengono generate dal preprocessore:

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

Se hai installato Boost, generare l'inizializzatore di array è un po 'più semplice:

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

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

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

Infine, il seguente driver di test stampa semplicemente tutti gli elementi dell'array sulla console:

#include <cstdio>

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

6

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

Funziona su GCC (4.6.1) e Clang (tronco 134121).


Per quanto riguarda C >> 1, non è spostare i valori negativi sul giusto comportamento non specificato? ;)
fredoverflow

Inoltre, puoi spiegare la parte in cui definisci l'array? Mi sono perso un po 'lì ...
Fredoverflow,

@Fred hai ragione. Farò anche Ca unsigned long. L'array costante è definito per essere inizializzato dall'espansione del pacchetto D.... Dè un pacchetto di parametri del modello non di tipo. Una volta che GCC lo supporta, si può anche dichiarare l'array in classe con static unsigned long constexpr crc_table[] = { D... };, ma GCC non analizza ancora gli inizializzatori in classe rinforzati. Il vantaggio sarà che compute<>::crc_table[I]potrebbe essere utilizzato all'interno di espressioni costanti più avanti nel codice.
Johannes Schaub - litb

5

C ++ 0x con constexpr. Funziona su 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;
}

È quindi possibile utilizzare crc_table.data[X]in fase di compilazione perché crc_tableè constexpr.


4

Questo è il mio primo metaprogramma :

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

Ho "codificato" le chiamate al modello che esegue il calcolo :)


1
Se lo farai in questo modo, perché non dovresti semplicemente codificare i valori effettivi nel programma? (Dopo averli ottenuti con un altro metodo, ovviamente.) Risparmia sui tempi di compilazione.
Matteo Leggi il

+1 per il test e il timesmodello
fredoverflow

@Matthew: con solo C ++ 03, c'è poca scelta. Potresti usare il preprocessore, come ha fatto Fred, ma ciò non ridurrà i tempi di compilazione. E a quanto pare, il suo preprocessore soffoca sulla sua soluzione :)
R. Martinho Fernandes,

@Matthew Il punto è di calcolarlo effettivamente in fase di compilazione , non di averli codificati. La risposta di Fred genera una matrice di questo modulo: 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 ,usando il preprocessore. Ci vuole circa il tempo per compilare come il mio. Se vuoi, puoi leggere l'ultimo paragrafo come "Ho srotolato il ciclo esterno". Non c'è altra scelta in C ++ 03
R. Martinho Fernandes,

Ah, non stavo prestando sufficiente attenzione ai requisiti della domanda. +1, anche se non sono sicuro che la domanda mi piaccia più. Mi piacciono le sfide del mio codice pratico: P
Matthew Leggi il

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

Fa davvero vergognare il C ++, vero?


2
In realtà, preferisco le soluzioni non tipizzate. Questo assomiglia molto al tempo di compilazione eval.
R. Martinho Fernandes,

Non è necessario utilizzare un mix di stringhe per questo, ecco come lo facciamo nella libreria standard di D , genTables e call-site per memorizzare il risultato nel segmento di dati const.
Martin Nowak,

3

C / C ++, 306 295 byte

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

Lavorando al contrario, finiamo con un array lungo senza segno chiamato crc_table. Possiamo omettere le dimensioni dell'array poiché le macro assicureranno che ci siano esattamente 256 elementi nell'array. Inizializziamo l'array con 16 "righe" di dati utilizzando 16 invocazioni della macro R.

Ogni invocazione di R si espande in quattro frammenti (macro F) di quattro costanti (macro K) per un totale di 16 "colonne" di dati.

La macro K è il ciclo srotolato indicizzato da k nel codice della domanda originale. Aggiorna il valore c otto volte invocando la macro C.

Questa soluzione basata su preprocessore utilizza un bel po 'di memoria durante l'espansione delle macro. Ho provato a renderlo un po 'più breve avendo un ulteriore livello di espansione macro e il mio compilatore ha vomitato. Il codice sopra si compila (lentamente) sia con Visual C ++ 2012 che g ++ 4.5.3 sotto Cygwin (Windows 7 64 bit 8 GB RAM).

Modificare:

Il frammento sopra è di 295 byte incluso lo spazio bianco. Dopo aver espanso tutte le macro ad eccezione di C, aumenta a 9.918 byte. Man mano che si espande ogni livello di macro C, le dimensioni aumentano rapidamente:

  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

Quindi, quando tutte le macro sono state espanse, quel piccolo file da 295 byte si espande in oltre 2,7 megabyte di codice che deve essere compilato per generare l'array originale da 1024 byte (assumendo valori lunghi senza segno a 32 bit)!

Un'altra modifica:

Ho modificato la macro C in base a una macro da un'altra risposta per comprimere altri 11 byte e ho ridotto notevolmente le dimensioni della macro espansa. Mentre 2.7 MB non è così male come 54 MB (la dimensione finale precedente di tutta l'espansione macro), è comunque significativa.


Non si tratta di code-golf , quindi non è necessario minimizzare il conteggio dei personaggi.
Ilmari Karonen,

Ah. Così è. Mio male da quella parte. Anche se penso che questa implementazione sia portatile (con questo intendo che è conforme al linguaggio C e al preprocessore; la sua vera portabilità dipenderebbe dai limiti esatti dell'ambiente sull'espansione delle macro).
CasaDeRobison,

3

Modificherei la risposta precedente sostituendo le ultime tre righe con:

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

Dove crcByte è la sua macro K senza la virgola finale. Quindi crea la tabella stessa con:

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

E non tralasciare mai le dimensioni dell'array poiché il compilatore verificherà quindi di disporre della quantità corretta di elementi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.