Хеширование строки во время компиляции (препроцессор)

Есть ли способ создать хэш строки во время компиляции с помощью препроцессора C / C ++ (или даже метапрограммирования шаблона)?

e.g. UNIQUE_SALT("HelloWord", 3DES);

Идея в том, что HelloWorld не будет присутствовать в скомпилированном двоичном файле, а будет только хеш.

Изменить: многие из этих объявлений разбросаны по большой кодовой базе.


person Ash    schedule 13.05.2010    source источник
comment
Уникальная соль, сгенерированная во время компиляции, как бы упускает суть ...   -  person BlueRaja - Danny Pflughoeft    schedule 19.05.2010


Ответы (8)


С C ++ 0x это возможно, как описано в ответах в # 1 и #2.

В C ++ 03 не было обработки строки времени компиляции. С препроцессором вы не можете разделить строку на токены, с шаблонами вы не можете получить доступ к отдельным символам. Однако было обсуждение предполагаемого подхода с использованием C ++ 0x.

Что вы можете сделать для C ++ 03, так это передать строку посимвольно (возможно с использованием многосимвольных литералов):

foo = hash<3DES, str<'a','b','c'> >::result;
// or:
foo = hash<3DES, str<'abc','def'> >::result;

... или просто сделайте это перед сборкой.

person Georg Fritzsche    schedule 13.05.2010
comment
как работает hash ‹3DES, str‹ 'abc', 'def' ›› :: result и почему вы не можете использовать hash ‹3DES, str‹ 'verylong string', 'even long string' ›› :: result? - person Ash; 14.05.2010
comment
@Ashirus: это многосимвольные литералы, имеющие тип int и значение, определенное реализацией. Более длинные значения просто не поместятся в int, и вам все равно придется вручную получать отдельные символьные значения. - person Georg Fritzsche; 14.05.2010
comment
Извините за то, что мешал старый пост, но да, на C ++ это возможно. C ++ 0X делает следующий метод более чистым, но с помощью программирования мета-шаблонов вы можете делать это, хотя и посимвольно, как в моем примере ниже. Хотя, как вы отметили, в C ++ 0x вы можете обрабатывать больше символов за раз. - person leetNightshade; 23.12.2011
comment
@leet: Я уже сказал выше, что у вас не может быть обработки строк или строковых литералов во время компиляции, но вы можете обрабатывать списки символов. Это именно то, что вы сделали, и мой пример показывает. - person Georg Fritzsche; 03.01.2012
comment
@Georg Извините, я неверно истолковал вашу формулировку, я не был так буквально; вы правы, поскольку вы не можете обрабатывать строку, вы можете обрабатывать только отдельные 'c', 'h', 'a', 'r', 's'. В то время я думал в общем смысле, а не как программист, что можно обрабатывать строки, вам просто нужно вручную разбить их для обработки во время компиляции. - person leetNightshade; 04.01.2012
comment
Это просто неверный ответ. Строковые литералы времени компиляции могут быть проанализированы функциями, объявленными constexpr. Уловка в том, что вы не можете позволить им превратиться в указатели; вы должны хранить их как ссылки на массивы определенного размера, что обычно означает создание их шаблонных функций, чтобы можно было определить размер строки. - person Ian Ni-Lewis; 06.04.2016
comment
@ IanNi-Lewis: Вы заметили дату ответа? Не стесняйтесь предлагать вместо этого правки / обновления / ... - person Georg Fritzsche; 07.04.2016
comment
@GeorgFritzsche Не уверен, почему дата так важна. Конечно, ответу на два года, но он на четыре года моложе, чем эта ветка: ogre3d.org/forums/viewtopic.php?f=10&t=48471. Ссылки сейчас не работают, но TL; DR заключается в том, что вы можете анализировать строковые литералы, используя рекурсивные шаблоны. Я разместил рабочий код в ответе ниже. - person Ian Ni-Lewis; 11.04.2016
comment
@ IanNi-Lewis: Это обсуждение было до C ++ 0x, ему 6 лет. - person Georg Fritzsche; 12.04.2016

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

#define MY_HASH 123456789 

в файл .h, который затем будет # включен в ваше приложение.

person Community    schedule 13.05.2010
comment
Моя проблема не в создании хеша, а в поиске и замене этих строк их хешами во время компиляции. Единственный способ, которым я могу это сделать, - это использовать препроцессор. В системе управления версиями есть много таких объявлений UNIQUE_SALT (некоторая строка, xxx). - person Ash; 13.05.2010
comment
@Ashirus Ну, я все еще думаю, что лучший способ справиться с ними - это централизовать их, а затем каким-то образом сгенерировать необходимые определения. - person ; 13.05.2010
comment
Сделать предварительную сборку очень просто! В конце концов, это просто сопоставление / замена шаблонов. Любой язык сценариев позволит вам это сделать, и он станет таким же прозрачным, как и использование препроцессора. - person Matthieu M.; 13.05.2010
comment
@ Нил. Они не могут быть централизованными. Они разбросаны по всему исходному коду (который составляет более 1M LOC и не находится под моим контролем). Таково проблемное пространство. - person Ash; 14.05.2010
comment
@Matthieu: Но тогда мне потребовалось бы, чтобы 10 000 разработчиков кодовой базы запустили перед компиляцией специальный дополнительный инструмент - он должен быть частью стандартной цепочки инструментов. - person Ash; 14.05.2010
comment
@ Все, я ценю эти альтернативные подходы, но на самом деле вы просите меня переопределить проблемное пространство и предполагаете, что у меня есть уровень контроля над кодовой базой и цепочкой инструментов, которого у меня просто нет, но в любом случае спасибо. - person Ash; 14.05.2010

Хотя это неправильный ответ на вопрос, см. Эту запись в блоге для примера хеш-функции для строк длиной до 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 ().

person michaeljt    schedule 03.09.2014

Это можно сделать с помощью Boost.MPL, но это может быть не тот тип хэша, который вам нужен.

http://arcticinteractive.com/2009/04/18/compile-time-string-hashing-boost-mpl/

person Eddy Pronk    schedule 13.05.2010
comment
Спасибо, это примерно так близко, как это возможно, но я думаю, согласно ответу Георга, текущий C ++ просто не способен. Я думаю, что другие подобные вопросы здесь, на SO, должны были меня предупредить об этом. Если бы я мог принять два ответа, я бы принял и ваш. - person Ash; 14.05.2010

Даже если это невозможно (разумно) сделать с помощью препроцессора, если вы использовали строковый литерал или объявили его как static const и не создали на него никаких устойчивых ссылок, компилятор, скорее всего, продолжит и выполнит все математические вычисления для генерации result и опустите строку в объектном файле, если вы компилируете с оптимизацией. Самая сложная часть этого заключается в том, что вы не можете сделать код для инициализации глобальной или статической переменной слишком сложным, иначе компилятор скажет: «Эй, ты! Разве вы не знаете, что нельзя использовать цикл for вне функции? ? ".

person nategoose    schedule 13.05.2010
comment
Это хороший момент, однако я должен быть уверен, что здесь нет ссылок, так как существует много объявлений, а размер двоичного файла очень важен. - person Ash; 17.05.2010
comment
Я действительно не думаю, что ваш компилятор собирается оптимизировать хэш-алгоритм, который больше не существует, даже если все данные являются константными - они умны, но даже близко не так умны. - person Elemental; 19.05.2010

Я наткнулся на решение, использующее старый добрый стандарт C ++ (я не уверен, какую версию он рассматривает, но давайте просто скажем, что это решение работает в 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, я немного ограничен в том, что касается моего текущего проекта.

person leetNightshade    schedule 07.04.2011

Ответы, утверждающие, что строки не могут быть проанализированы во время компиляции, неверны. Указатели на символы не могут быть проанализированы во время компиляции, но строковые литералы не являются указателями на символы; они представляют собой массивы символов, длина которых является частью типа. Об этом легко забыть, потому что в большинстве случаев гораздо полезнее позволить им превратиться в 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.
}

Это должно вас начать. Очевидно, что сам хеш должен быть достаточно простым для вычислений во время компиляции, но это, вероятно, нормально.

person Ian Ni-Lewis    schedule 06.04.2016

Вот как я делаю этот строковый хэш времени компиляции с помощью C ++ 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`
person jayatubi    schedule 06.09.2016