Компиляция (препроцессор) хэширования строки
Есть ли способ создать хэш строки во время компиляции с использованием препроцессора C/С++ (или даже шаблона-метапрограммирования)?
например. UNIQUE_SALT("HelloWord", 3DES);
Идея в том, что HelloWorld не будет присутствовать в скомпилированном двоичном файле, просто хеш.
Изменить: многие из этих объявлений распространяются по большой базе кода.
Ответы
Ответ 1
С С++ 0x это возможно, как описано в ответах # 1 и # 2.
В С++ 03 не было обработки строки времени компиляции. С препроцессором вы не можете выделить строку в токенах, при этом шаблоны не могут получить доступ к одиночным символам. Однако обсуждался предполагаемый подход с использованием С++ 0x.
Что вы могли бы сделать для С++ 03 - передать строку по-символу (возможно, используя многосимвольные литералы):
foo = hash<3DES, str<'a','b','c'> >::result;
// or:
foo = hash<3DES, str<'abc','def'> >::result;
... или просто сделать это как шаг предварительной сборки.
Ответ 2
Почему бы не создать хэш-часть процесса сборки? Вы можете написать простой командный файл для генерации хэша (если у вас есть программа для выполнения такой вещи - если нет, напишите одну) и выведите директиву препроцессора, например:
#define MY_HASH 123456789
в файл .h, который затем получает # включен в ваше приложение.
Ответ 3
Это можно сделать с помощью Boost.MPL, но это может быть не тип хэша, который вы используете.
http://arcticinteractive.com/2009/04/18/compile-time-string-hashing-boost-mpl/
Ответ 4
Хотя это не является правильным ответом на вопрос, см. эту запись в блоге для примера хеш-функции для строк длиной до 256 символов, реализованных исключительно как макрос C:
http://lolengine.net/blog/2011/12/20/cpp-constant-string-hash
Вот фактический код из блога:
#include <string.h>
#include <stdint.h>
#include <stdio.h>
#define H1(s,i,x) (x*65599u+(uint8_t)s[(i)<strlen(s)?strlen(s)-1-(i):strlen(s)])
#define H4(s,i,x) H1(s,i,H1(s,i+1,H1(s,i+2,H1(s,i+3,x))))
#define H16(s,i,x) H4(s,i,H4(s,i+4,H4(s,i+8,H4(s,i+12,x))))
#define H64(s,i,x) H16(s,i,H16(s,i+16,H16(s,i+32,H16(s,i+48,x))))
#define H256(s,i,x) H64(s,i,H64(s,i+64,H64(s,i+128,H64(s,i+192,x))))
#define HASH(s) ((uint32_t)(H256(s,0,0)^(H256(s,0,0)>>16)))
Если вы заранее знаете, что будете использовать его только для статических строк, вы можете заменить strlen() на sizeof().
Ответ 5
Даже если это невозможно (разумно) сделать с препроцессором, если вы использовали строковый литерал или объявили его как static const
и не создали никаких длительных ссылок на него, компилятор, скорее всего, продолжит работу и сделает все математика для генерации результата и опускания строки в объектном файле, если вы компилируете с оптимизацией. Самая сложная часть этого заключается в том, что вы не можете заставить код инициализировать глобальную или статическую переменную слишком сложно или компилятор скажет: "Эй, вы! Не знаете, вы не можете использовать цикл for вне функции?".
Ответ 6
Я наткнулся на решение, используя хороший 'ol С++ standard (я не уверен, какую версию он рассмотрел, но позвольте сказать, что это решение работает в Visual Studio). Здесь ссылка: ссылка.
Кроме того, здесь короткая версия функции JSHash, использующая вышеупомянутый метод. Показанный здесь поддерживает до 4 символов, хотя вы можете добавить столько, сколько хотите.
template<const char A = 0, const char B = 0, const char C = 0, const char D = 0>
struct cHash
{
template<const char C, size_t hash = 1315423911>
struct HashCalc
{
enum { value = (C == 0) ? hash : hash ^ ((hash << 5) + C + (hash >> 2)) };
};
enum { value = HashCalc<D,HashCalc<C,HashCalc<B,HashCalc<A>::value>::value>::value>::value };
};
Как уже отмечалось, поскольку это хэш времени компиляции, вы можете сделать что-то вроде этого:
namespace Section
{
enum Enum
{
Player = cHash<'p','l','a','y'>::value
};
}
Это не самое элегантное решение, поэтому я планирую сделать больше исследований в этой области, однако, поскольку это единственное, что я получил, чтобы работать в VisualStudio2010, я немного ограничен, насколько это касается моего текущего проекта,
Ответ 7
Ответы, утверждающие, что строки не могут быть проанализированы во время компиляции, неверны. Указатели символов не могут быть проанализированы во время компиляции, но строковые литералы не являются указателями на символы; это массивы символов, длина которых является частью типа. Легко забыть об этом, потому что в большинстве случаев гораздо более полезно позволить им распасться на char *. Но они так не начинаются.
А, но как определить функцию, которая принимает массив символов фиксированной длины, особенно если мы действительно будем использовать его для строк произвольной длины? То, что вывод шаблона шаблона приходит очень удобно:
template<size_t L>
constexpr int hash(const char (&str)[L], int n = L - 1) {
// hash goes here. You can define recursively as a
// function of str[n] and hash(str, n-1). Remember to
// treat 0 as a special case.
}
Это должно заставить вас начать. Очевидно, что сам хэш должен быть достаточно простым для вычисления времени компиляции, но это, вероятно, нормально.
Ответ 8
Вот как я делаю этот хэш-код компиляции с С++ 0x:
class StringHash
{
public:
template <unsigned N, unsigned I>
struct HashHelper
{
constexpr static unsigned Calculate(const char (&str)[N])
{
return (HashHelper<N, I - 1>::Calculate(str) ^ (str[I - 1] & 0xFF)) * StringHash::PRIME;
}
};
template <unsigned N>
struct HashHelper<N, 1>
{
constexpr static unsigned Calculate(const char (&str)[N])
{
return (StringHash::OFFSET ^ (str[0] & 0xFF)) * StringHash::PRIME;
}
};
template<unsigned N>
constexpr static unsigned StaticHash(const char (&str)[N])
{
return HashHelper<N, N>::Calculate(str);
}
static const unsigned OFFSET = 0x01234567;
static const unsigned PRIME = 0x89ABCDEF;
}
Использование:
static hash = StringHash::StaticHash("Hello"); // You may even use this expression in `switch case`