Использование алгоритма CRC32 для хеширования строки во время компиляции

В основном я хочу, чтобы в моем коде это было:

 Engine.getById(WSID('some-id'));

Что должно быть преобразовано

 Engine.getById('1a61bc96');

непосредственно перед компиляцией в asm. Итак, во время компиляции.

Это моя попытка

constexpr int WSID(const char* str) {
    boost::crc_32_type result;
    result.process_bytes(str,sizeof(str));
    return result.checksum();
}

Но я получаю это при попытке скомпилировать с MSVC 18 (CTP, ноябрь 2013 г.)

error C3249: illegal statement or sub-expression for 'constexpr' function

Как я могу получить функцию WSID, используя этот или любой другой способ, если это выполняется во время компиляции?

Пробовал это: Хеширование строки времени компиляции

 warning C4592: 'crc32': 'constexpr' call evaluation failed; function will be called at run-time

РЕДАКТИРОВАТЬ:

Впервые я услышал об этой технике в Game Engine Architecture от Джейсона Грегори. Я связался с автором, который мне любезно ответил:

Что мы делаем, так это пропускаем наш исходный код через специальный небольшой препроцессор, который ищет текст в форме SID('xxxxxx') и преобразует все, что находится между одинарными кавычками, в его хешированный эквивалент в виде шестнадцатеричного литерала (0xNNNNNNNN). [...]

Вы могли бы сделать это с помощью макроса и / или некоторого метапрограммирования шаблона, хотя, как вы говорите, сложно заставить компилятор выполнять такую ​​работу за вас. Это не невозможно, но написать собственный инструмент проще и гибче. [...]

Также обратите внимание, что мы выбрали одинарные кавычки для SID('xxxx') литералов. Это было сделано для того, чтобы мы могли получить разумную подсветку синтаксиса в наших редакторах кода, но если что-то пойдет не так и какой-то необработанный код когда-либо передаст его компилятору, он выдаст синтаксическую ошибку, потому что одинарные кавычки обычно зарезервированы для односимвольные литералы.

Также обратите внимание, что очень важно, чтобы ваш маленький инструмент предварительной обработки кэшировал строки в какой-либо базе данных, чтобы можно было искать исходные строки с помощью хэш-кода. Когда вы отлаживаете свой код и проверяете переменную StringId, отладчик обычно показывает вам довольно непонятный хэш-код. Но с базой данных SID вы можете написать плагин, который преобразует эти хэш-коды обратно в их строковые эквиваленты. Таким образом, вы увидите SID ('foo') в окне просмотра, а не 0x75AE3080 [...]. Кроме того, игра должна иметь возможность загружать ту же базу данных, чтобы она могла выводить на экран строки вместо шестнадцатеричных хэш-кодов для целей отладки [...].

Но хотя препроцесс имеет некоторые основные преимущества, это означает, что мне нужно подготовить какую-то систему вывода измененных файлов (они будут храниться в другом месте, а затем нам нужно сообщить MSVC). Так что это может усложнить задачу компиляции. Есть ли способ предварительно обработать файл с помощью Python, например, без головной боли? Но вопрос не в этом, и меня все еще интересует использование функции времени компиляции (в отношении кеша я мог бы использовать индекс идентификатора)


person Vinz243    schedule 23.02.2015    source источник
comment
См. Ответы здесь: stackoverflow.com/questions/3226211/   -  person Nim    schedule 23.02.2015
comment
@Nim Значит, мне нужно в одну строчку переписать?   -  person Vinz243    schedule 23.02.2015
comment
@ Vinz243 Если вы используете C ++ 11, а не -14, да. Если, тем не менее, вы не сможете убедить компилятор запустить функцию, не являющуюся constexpr, во время компиляции.   -  person Columbo    schedule 23.02.2015
comment
Кроме того, мы говорим о строках или эта многосимвольная константа является частью вашего намерения?   -  person Columbo    schedule 23.02.2015
comment
Моя точка зрения заключалась в том, что я не думаю, что вы можете это сделать (потому что компьютер boost crc) довольно сложен и вряд ли будет способен constexpr. Если вы действительно хотели сделать это время компиляции, вам придется немного подправить шаблон ...   -  person Nim    schedule 23.02.2015
comment
Несколько лет назад для этого был кодекс гольфа. codegolf.stackexchange.com / questions / 3268 /   -  person Moby Disk    schedule 23.02.2015
comment
Хм ... Так что забудьте про constexpr и используйте макросы процессора.   -  person Vinz243    schedule 23.02.2015
comment
@MobyDisk, но это дает мне только таблицу, как мне тогда хешировать строку?   -  person Vinz243    schedule 23.02.2015
comment
Первый тип сообщения об ошибке, недопустимый для constexpr, является неправильным или вводящим в заблуждение: std::string const& является буквальным типом (поскольку он является ссылкой) и, следовательно, разрешен в качестве типа для параметра функции функции constexpr. clang ++ и g ++ также принимают его (в режиме C ++ 11).   -  person dyp    schedule 23.02.2015
comment
@dyp исправил первую ошибку, спасибо! См. Править   -  person Vinz243    schedule 23.02.2015


Ответы (3)


Вот решение, которое полностью работает во время компиляции, но также может использоваться во время выполнения. Это смесь constexpr, шаблонов и макросов. Вы можете изменить некоторые имена или поместить их в отдельный файл, поскольку они довольно короткие.

Обратите внимание, что я повторно использовал код из этого ответа для создания таблицы CRC, и я основывался на коде из эту страницу для реализации.

Я не тестировал его на MSVC, так как в настоящее время он не установлен в моей виртуальной машине Windows, но я считаю, что он должен работать или, по крайней мере, работать с тривиальными изменениями.

Вот код, вы можете использовать функцию crc32 напрямую или функцию WSID, которая более точно соответствует вашему вопросу:

#include <cstring>
#include <cstdint>
#include <iostream>

// Generate CRC lookup table
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};};

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

constexpr unsigned crc_table[] = { A(0) };

// Constexpr implementation and helpers
constexpr uint32_t crc32_impl(const uint8_t* p, size_t len, uint32_t crc) {
    return len ?
            crc32_impl(p+1,len-1,(crc>>8)^crc_table[(crc&0xFF)^*p])
            : crc;
}

constexpr uint32_t crc32(const uint8_t* data, size_t length) {
    return ~crc32_impl(data, length, ~0);
}

constexpr size_t strlen_c(const char* str) {
    return *str ? 1+strlen_c(str+1) : 0;
}

constexpr int WSID(const char* str) {
    return crc32((uint8_t*)str, strlen_c(str));
}

// Example usage
using namespace std;

int main() {
    cout << "The CRC32 is: " << hex << WSID("some-id") << endl;
}

Первая часть заботится о создании таблицы констант, а crc32_impl - это стандартная реализация CRC32, преобразованная в рекурсивный стиль, который работает с constexpr C ++ 11. Тогда crc32 и WSID - просто простые оболочки для удобства.

person tux3    schedule 02.03.2015
comment
Это довольно красиво. Не могли бы вы немного подробнее объяснить, что вы делаете с классами шаблонов? Похоже, вы рекурсивно наследуете до k = 0, а затем определяете окончательный параметр шаблона как значение? Я плохо разбираюсь в специализации классов шаблонов и в том, что вы здесь делаете. - person jschultz410; 02.03.2015
comment
@ jschultz410 мы, по сути, используем одну и ту же технику дважды, с constexpr и с шаблонами. Мы превращаем эталонную реализацию в рекурсивную функцию. Шаблоны реализуют основной цикл for (k = 0; k < 8; k++){...}, но используют рекурсию, а макросы заменяют внешний цикл for (n = 0; n < 256; n++) {...}, просто вызывая шаблон 256 раз. - person tux3; 02.03.2015
comment
Спасибо! Но там написано warning C4592: 'crc32_impl': 'constexpr' call evaluation failed; function will be called at run-time и то же самое для crc32: / - person Vinz243; 02.03.2015
comment
@ Vinz243 В комментарии к этому сообщению говорится, что команда компилятора подтвердила, что это предупреждение является ложным - оценка constexpr в любом случае будет успешной. и в этом отчете об ошибке говорится, что она исправлена. Работает ли он с последней версией MSVC, и вы пробовали пройти через отладчик? - person tux3; 02.03.2015
comment
Мне нужно заставить vs2012 работать, чтобы получить отладчик (проблема с лицензией) - person Vinz243; 02.03.2015
comment
@ Vinz243, хорошо. Возможно, было бы разумно перейти на VS 2013 или даже на более свежие CTP, если вы хотите использовать более современные функции. - person tux3; 02.03.2015
comment
Попробую превью VS 2015 :) - person Vinz243; 02.03.2015
comment
@ Vinz243 молодец! Внес пару изменений, может теперь будет работать лучше. - person tux3; 02.03.2015
comment
Я скажу вам как можно скорее (то есть завтра или в среду :(). Не могу дождаться! - person Vinz243; 02.03.2015
comment
Visual Studio 2015 не выдавала никаких предупреждений. Однако при входе в строку с помощью отладчика он переходит к WSID и другим функциям: '((это заняло почти 10 мс) - person Vinz243; 03.03.2015
comment
@ Vinz243 странно, а если в main сделать constexpr int result = WSID("some-id"); cout<<result<<endl;? Он должен либо запускаться во время компиляции, либо не компилироваться вообще. Возможно, MSVC не использует constexpr, если вы его не заставляете, но если он соответствует стандарту, он не может вычислить переменную constexpr во время выполнения: / - person tux3; 03.03.2015
comment
Я получил fatal error C1001: An internal error has occurred in the compiler. 1> (compiler file 'f:\dd\vctools\compiler\cxxfe\sl\p1\c\constexpr.cpp', line 1208) - person Vinz243; 03.03.2015
comment
@ Vinz234 Welp. Если MSVC 2015 по-прежнему не справляется с constexpr, получить функцию CRC32 во время компиляции будет непросто: / Я попытаюсь установить ее и запустить несколько тестов, если хотите, я удивлен, что это займет так мало времени внутренняя ошибка из-за этого. - person tux3; 03.03.2015
comment
Я назначил вам награду, учитывая ваш балл, хотя это не сработало на MSVC. Я предпочитаю назначать награды, а не терять их - person Vinz243; 07.03.2015
comment
@ Vinz243 Спасибо! Мне очень жаль, что MSVC вылетает, я этого не ожидал. Если подумать, вместо этого может сработать идея инструмента предварительной обработки. - person tux3; 07.03.2015
comment
Как сказал @KevinKeane! Но пока я перехожу на нереальный, так что ... Мы сможем разделить награды - person Vinz243; 07.03.2015
comment
это потрясающе! хорошая реализация. на самом деле очень похоже на то, что я искал :) - person Alexander Oh; 19.11.2015

Если кому-то интересно, я закодировал функцию генератора таблиц CRC-32 и функцию генератора кода, используя функции constexpr в стиле C ++ 14. В результате, на мой взгляд, получается гораздо более обслуживаемый код, чем многие другие попытки, которые я видел в Интернете, и он находится далеко-далеко от препроцессора.

Теперь он использует специальный std :: array 'clone', называемый cexp :: array, потому что G ++, похоже, не добавил ключевое слово constexpr к своему оператору доступа / записи неконстантного ссылочного индекса.

Однако он довольно легкий, и, надеюсь, ключевое слово будет добавлено в std :: array в ближайшем будущем. Но пока очень простая реализация массива выглядит следующим образом:

namespace cexp
{

    // Small implementation of std::array, needed until constexpr
    // is added to the function 'reference operator[](size_type)'
    template <typename T, std::size_t N>
    struct array {
        T m_data[N];

        using value_type = T;
        using reference = value_type &;
        using const_reference = const value_type &;
        using size_type = std::size_t;

        // This is NOT constexpr in std::array until C++17
        constexpr reference operator[](size_type i) noexcept {
            return m_data[i];
        }

        constexpr const_reference operator[](size_type i) const noexcept {
            return m_data[i];
        }

        constexpr size_type size() const noexcept {
            return N;
        }
    };

}

Теперь нам нужно сгенерировать таблицу CRC-32. Я основал алгоритм на некотором коде Hacker's Delight, и его, вероятно, можно расширить для поддержки многих других алгоритмов CRC. Но, увы, мне потребовалась только стандартная реализация, так что вот она:

// Generates CRC-32 table, algorithm based from this link:
// http://www.hackersdelight.org/hdcodetxt/crc.c.txt
constexpr auto gen_crc32_table() {
    constexpr auto num_bytes = 256;
    constexpr auto num_iterations = 8;
    constexpr auto polynomial = 0xEDB88320;

    auto crc32_table = cexp::array<uint32_t, num_bytes>{};

    for (auto byte = 0u; byte < num_bytes; ++byte) {
        auto crc = byte;

        for (auto i = 0; i < num_iterations; ++i) {
            auto mask = -(crc & 1);
            crc = (crc >> 1) ^ (polynomial & mask);
        }

        crc32_table[byte] = crc;
    }

    return crc32_table;
}

Затем мы сохраняем таблицу в глобале и выполняем элементарную статическую проверку. Скорее всего, эту проверку можно улучшить, и нет необходимости хранить ее в глобальном.

// Stores CRC-32 table and softly validates it.
static constexpr auto crc32_table = gen_crc32_table();
static_assert(
    crc32_table.size() == 256 &&
    crc32_table[1] == 0x77073096 &&
    crc32_table[255] == 0x2D02EF8D,
    "gen_crc32_table generated unexpected result."
);

Теперь, когда таблица сгенерирована, пора сгенерировать коды CRC-32. Я снова основал алгоритм на ссылке Hacker's Delight, и на данный момент он поддерживает только ввод из c-строки.

// Generates CRC-32 code from null-terminated, c-string,
// algorithm based from this link:
// http://www.hackersdelight.org/hdcodetxt/crc.c.txt 
constexpr auto crc32(const char *in) {
    auto crc = 0xFFFFFFFFu;

    for (auto i = 0u; auto c = in[i]; ++i) {
        crc = crc32_table[(crc ^ c) & 0xFF] ^ (crc >> 8);
    }

    return ~crc;
}

Для завершения я генерирую один код CRC-32 ниже и статически проверяю, имеет ли он ожидаемый результат, а затем распечатываю его в поток вывода.

int main() {
    constexpr auto crc_code = crc32("some-id");
    static_assert(crc_code == 0x1A61BC96, "crc32 generated unexpected result.");

    std::cout << std::hex << crc_code << std::endl;
}

Надеюсь, это поможет всем, кто хотел добиться генерации CRC-32 во время компиляции или даже в целом.

person Deus Sum    schedule 09.04.2016

Ответ @ tux3 довольно приятный! Однако сложно поддерживать, потому что вы в основном пишете свою собственную реализацию CRC32 в командах препроцессора.

Другой способ решить ваш вопрос - сначала вернуться и понять необходимость требования. Если я вас правильно понимаю, похоже, дело в производительности. В этом случае есть второй момент времени, когда вы можете вызвать свою функцию без ущерба для производительности: во время загрузки программы. В этом случае вы будете обращаться к глобальной переменной вместо передачи константы. С точки зрения производительности, после инициализации оба должны быть идентичными (const извлекает 32 бита из вашего кода, глобальная переменная извлекает 32 бита из обычного места в памяти).

Вы можете сделать что-то вроде этого:

static int myWSID = 0;

// don't call this directly
static int WSID(const char* str) {
  boost::crc_32_type result;
  result.process_bytes(str,sizeof(str));
  return result.checksum();
}

// Put this early into your program into the
// initialization code.
...
myWSID = WSID('some-id');

В зависимости от вашей общей программы вам может потребоваться встроенный метод доступа для извлечения значения.

Если незначительное влияние на производительность допустимо, вы также должны написать свою функцию таким образом, в основном используя шаблон singleton.

// don't call this directly
int WSID(const char* str) {
  boost::crc_32_type result;
  result.process_bytes(str,sizeof(str));
  return result.checksum();
}

// call this instead. Note the hard-coded ID string.
// Create one such function for each ID you need to
// have available.
static int myWSID() {
   // Note: not thread safe!
   static int computedId = 0;
   if (computedId == 0)
      computedId = WSID('some-id');
   return computedId;
}

Конечно, если причиной запроса оценки времени компиляции является нечто иное (например, нежелание появления некоторого идентификатора в скомпилированном коде), эти методы не помогут.

Другой вариант - использовать предложение Джейсона Грегори о настраиваемом препроцессоре. Это можно сделать довольно чисто, если собрать все IDS в отдельный файл. Этот файл не обязательно должен иметь синтаксис C. Я бы дал ему расширение, например .wsid. Пользовательский препроцессор генерирует из него файл .H.

Вот как это могло выглядеть:

idcollection.wsid (перед пользовательским препроцессором):

some_id1
some_id2
some_id3

Ваш препроцессор сгенерирует следующий idcollection.h:

#define WSID_some_id1 0xabcdef12
#define WSID_some_id2 0xbcdef123
#define WSID_some_id3 0xcdef1234

И в своем коде вы бы назвали

Engine.getById(WSID_some_id1);

Несколько замечаний по этому поводу:

  • Это предполагает, что все исходные идентификаторы могут быть преобразованы в действительные идентификаторы. Если они содержат специальные символы, вашему препроцессору может потребоваться дополнительная настройка.
  • Я заметил несоответствие в вашем исходном вопросе. Ваша функция возвращает int, но Engine.getById, похоже, принимает строку. Предлагаемый мной код всегда будет использовать int (легко изменить, если вы хотите всегда строку).
person Kevin Keane    schedule 05.03.2015
comment
Это должен был быть комментарий. Вы вставляете мой код, который не дает прямого ответа на вопрос (это скорее обходной путь). Но +1 за второе предложение. - person Vinz243; 07.03.2015
comment
Мне, наверное, следовало разбить это на комментарий и отдельный ответ. Да, я использовал ваш код в качестве отправной точки. Ключевое различие между моей версией и вашей заключается в том, что она запускается только один раз во время инициализации. Я смотрю на такие запросы, как ваш: у вас есть основная проблема, которую нужно решить, и я предположил, в чем проблема, и придумал альтернативное решение для этого. Если это не поможет - не беда. Итак, второе предложение. Спасибо за голос! - person Kevin Keane; 07.03.2015
comment
Это могло мне помочь. Но вопрос группы вопросов был и crc32 во время компиляции. Отсюда комментарий к первой части. - person Vinz243; 07.03.2015