Calcule la tabla CRC32 en tiempo de compilación [cerrado]


16

La implementación de referencia de CRC32 calcula una tabla de búsqueda en tiempo de ejecución:

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

¿Se puede calcular la tabla en tiempo de compilación, eliminando así la función y el indicador de estado?


2
¿Cuál es el criterio objetivo primario ganador aquí?
John Dvorak

también, las preguntas específicas del idioma están mal vistas aquí. debes eliminar la etiqueta c ++.
orgulloso Haskeller

Respuestas:


12

Aquí hay una solución C simple:

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

Se basa en la __COUNTER__macro no estándar , así como en una semántica de evaluación donde __COUNTER__se evalúa antes de pasarla como argumento a una macro.

Tenga en cuenta que, dado que STEPevalúa su argumento dos veces, y CRCusa ocho invocaciones anidadas de él, hay una pequeña explosión combinatoria en el tamaño del código:

$ cpp crc32table.c | wc -c
4563276

Probé esto en GCC 4.6.0 y Clang 2.8 en Linux de 32 bits, y ambos producen la tabla correcta.


Impresionante, ciertamente no esperaba esto. Tener un +1.
R. Martinho Fernandes

9

El bucle central

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

se puede convertir en una metafunción:

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

Luego, el preprocesador genera 256 llamadas a esta metafunción (para el inicializador de matriz):

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

Si tiene instalado Boost, generar el inicializador de matriz es un poco más simple:

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

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

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

Finalmente, el siguiente controlador de prueba simplemente imprime todos los elementos de la matriz en la consola:

#include <cstdio>

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

6

Una solución 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;
}

Funciona en GCC (4.6.1) y Clang (trunk 134121).


Al respecto C >> 1, ¿no está cambiando los valores negativos al comportamiento correcto no especificado? ;)
fredoverflow

Además, ¿puede explicar la parte donde define la matriz? Estoy un poco perdido allí ...
fredoverflow

@ Fred tienes razón. También haré Ca unsigned long. La matriz constante está definida para ser inicializada por la expansión del paquete D.... Des un paquete de parámetros de plantilla que no es de tipo. Una vez que GCC lo admite, también se puede declarar la matriz en clase con static unsigned long constexpr crc_table[] = { D... };, pero GCC aún no analiza los inicializadores en clase. El beneficio será que compute<>::crc_table[I]podría usarse dentro de expresiones constantes más adelante en el código.
Johannes Schaub - litb

5

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

Luego puede usar crc_table.data[X]en tiempo de compilación porque crc_tablees constexpr.


4

Este es mi primer metaprograma :

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

"Codifiqué" las llamadas a la plantilla que hace el cálculo :)


1
Si lo vas a hacer de esa manera, ¿por qué no codificarías los valores reales en el programa? (Después de obtenerlos con otro método, por supuesto). Ahorra tiempo de compilación.
Mateo leyó el

+1 para la prueba y la timesplantilla
fredoverflow

@Matthew: con solo C ++ 03, hay pocas opciones. Podría usar el preprocesador, como lo hizo Fred, pero eso no acortará el tiempo de compilación. Y aparentemente, su preprocesador se ahoga con su solución :)
R. Martinho Fernandes

@Matthew El punto es calcularlo realmente en tiempo de compilación , no tenerlos codificados. La respuesta de Fred genera una matriz de esta forma: 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 el preprocesador. Toma casi tanto tiempo compilar como el mío. Si lo desea, puede leer el último párrafo como "Desenrollé el bucle externo". No hay otra opción en C ++ 03
R. Martinho Fernandes

Ah, no estaba prestando suficiente atención a los requisitos de la pregunta. +1, aunque ya no estoy seguro de que me guste la pregunta. Me gusta mi código de desafíos prácticos: P
Matthew Lee el

3

re

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

Realmente avergüenza a C ++, ¿no?


2
En realidad, prefiero las soluciones no tipadas. Esto se parece mucho al tiempo de compilación eval.
R. Martinho Fernandes

No es necesario usar una combinación de cadenas para esto, así es como lo hacemos en la biblioteca estándar de D , genTables y call-site para almacenar el resultado en el segmento de datos constante.
Martin Nowak

3

C / C ++, 306 295 bytes

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

Trabajando en reversa, terminamos con una matriz larga sin signo llamada crc_table. Podemos omitir el tamaño de la matriz ya que las macros se asegurarán de que haya exactamente 256 elementos en la matriz. Inicializamos la matriz con 16 'filas' de datos utilizando 16 invocaciones de la macro R.

Cada invocación de R se expande en cuatro fragmentos (macro F) de cuatro constantes (macro K) para un total de 16 'columnas' de datos.

La macro K es el bucle desenrollado indexado por k en el código de la pregunta original. Actualiza el valor c ocho veces invocando la macro C.

Esta solución basada en un preprocesador utiliza bastante memoria durante la expansión de macro. Traté de hacerlo un poco más corto al tener un nivel adicional de expansión de macro y mi compilador vomitó. El código anterior se compila (lentamente) con Visual C ++ 2012 y g ++ 4.5.3 en Cygwin (Windows 7 64 bit 8GB RAM).

Editar:

El fragmento anterior es de 295 bytes, incluido el espacio en blanco. Después de expandir todas las macros, excepto C, crece a 9.918 bytes. A medida que se expande cada nivel de macro C, el tamaño crece rápidamente:

  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

Entonces, para cuando se hayan expandido todas las macros, ¡ese pequeño archivo de 295 bytes se expande en más de 2.7 megabytes de código que se debe compilar para generar la matriz original de 1024 bytes (suponiendo valores largos sin signo de 32 bits)!

Otra edición:

Modifiqué la macro C basada en una macro de otra respuesta para exprimir 11 bytes adicionales y reduje en gran medida el tamaño de la macro expandida completa. Si bien 2.7 MB no es tan malo como 54 MB (el tamaño final anterior de toda expansión macro), sigue siendo significativo.


Esto no es código golf , por lo que no necesita minimizar el recuento de caracteres.
Ilmari Karonen

Ah Así es. Mi mal por esa parte. Aunque creo que esta implementación es portátil (con lo cual quiero decir que conforma el lenguaje C y el preprocesador; su verdadera portabilidad dependería de los límites exactos del entorno en la expansión de macro).
CasaDeRobison

3

Modificaría la respuesta anterior reemplazando las últimas tres líneas 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)

Donde crcByte es su macro K sin la coma final. Luego construya la tabla en sí con:

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

Y nunca omita el tamaño de la matriz, ya que el compilador verificará que tiene la cantidad correcta de elementos.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.