2014-01-16 3 views
7

Я пишу приложение в C (gcc), которое делает много сравнения строк. Всегда одна неизвестная/динамическая строка с длинным списком константных строк времени компиляции. Итак, я решил, что я использую динамическую строку и сравниваю полученный хеш с предварительно вычисленными хэшами константных строк.Расширение макроса GCC для вызова другого макроса

Для этого у меня есть хэш-алгоритм в функции (для динамических строк выполнения) и как макрос, так что gcc оценивает хэш во время компиляции.

Я получил это:

#define HASH_CALC(h, s) ((h) * 33 + *(s)) 
#define HASH_CALC1(s) (HASH_CALC(hash_calc_start, s)) 
#define HASH_CALC2(s) (HASH_CALC(HASH_CALC1(s), s + 1)) 
#define HASH_CALC3(s) (HASH_CALC(HASH_CALC2(s), s + 2)) 
#define HASH_CALC4(s) (HASH_CALC(HASH_CALC3(s), s + 3)) 
#define HASH_CALC5(s) (HASH_CALC(HASH_CALC4(s), s + 4)) 
//--> cut ... goes till HASH_CALC32 

static const unsigned long hash_calc_start = 5381; 
unsigned long hash_str (const char* c); 

void func() { 
    //This string is not constant ... just in this case to show something 
    char dynStr = "foo"; 
    unsigned long dynHash = hash_str (dynStr); 

    //gcc produces a cmp with a constant number as foo is hashed during compile-time 
    if (dynHash == HASH_CALC3("foo")) { 
    } 
} 

Теперь на вопрос:

Можно ли создать макрос, который расширяется до HASH_CALCX (ов), где X является длина постоянной строки передается в макрос?

//Expands to HASH_CALC3("foo") 
if (dynHash == HASH_CALCX("foo")) { 
} 
//Expands to HASH_CALC6("foobar") 
if (dynHash == HASH_CALCX("foobar")) { 
} 

Я пробовал это, но он не работает.

#define HASH_STRLEN(x) (sizeof(x)/sizeof(x[0])-1) 
#define HASH_MERGE(x,y) x ## y 
#define HASH_MERGE2(x,y) HASH_MERGE(x,y) 
#define HASH_CALCX(s) (HASH_MERGE2(HASH_CALC, HASH_STRLEN(s))(s)) 

Спасибо!

+0

Не требуется. 'strlen' отлично работает для строковых констант, возвращая константу compiletime. – Damon

+0

@Damon Well ... #define HASH_CALCX (s) (HASH_MERGE2 (HASH_CALC, strlen (s)) (ы)) не работает, так как strlen ("") не расширяется. – Xatian

+0

Как определяется 'hash_calc_start'? '#define hash_calc_start 0'? – ericbn

ответ

1

К сожалению, в C разложение строк не является постоянным выражением.Недопустимо следующее:

switch (x) { 
case "a"[0]: 
    ... 
} 

Постоянные выражения в препроцессоре аналогичны. Следующее недействительно:

#if "a"[0] == 'a' 
    ... 
#endif 

Если вам не нужен использовать свои хэш-коду в switch заявлении, почему не предвычисления этих значений во время фазы инициализации вашей программы, результатов магазина в переменных, а затем просто использовать их в тестах ?

Если вы в них нуждаетесь в операциях switch Я предлагаю разделить строки на символы. В вашем случае это даст макросы, такие как:

#define HASH_CALC(...) HASH_CALC0(5381, __VA_ARGS__, 0, 0, 0, 0, 0) 
#define HASH_CALC0(p, x, ...) (x == 0 ? (p) : HASH_CALC1((p)*33+x, __VA_ARGS__))) 
#define HASH_CALC1(p, x, ...) (x == 0 ? (p) : HASH_CALC2((p)*33+x, __VA_ARGS__))) 
#define HASH_CALC2(p, x, ...) (x == 0 ? (p) : HASH_CALC3((p)*33+x, __VA_ARGS__))) 
#define HASH_CALC3(p, x, ...) (x == 0 ? (p) : HASH_CALC4((p)*33+x, __VA_ARGS__))) 

... 
#define HASH_CALC64(p, x, ...) (x == 0 ? (p) : -1 /* shall never be used */) 

Макро HASH_CALC ожидает переменное количество символов в качестве аргументов. Например, следующий правильный код:

switch (x) { 
case HASH_CALC('f', 'o', 'o'): 
    ... 
case HASH_CALC('f', 'o', 'o', 'b', 'a', 'r'): 
    ... 
} 
+0

Мне нравится ваш ответ, как будто вы сказали, что ваши макросы могут использоваться и в switch-statement. У вас только один вопрос: почему (x + 0) в макросе вместо x? – Xatian

+0

@xatian: Это трюк. Есть 64 макроса, потребляющих не более 64 аргументов. Однако, если аргументов меньше, оставшиеся макросы получают пустую строку в качестве параметра. Поэтому 'x' может быть пустым и даст синтаксическую ошибку, потому что выражение будет' (== 0? ...) 'С +0 это дает' ((+0) == 0? ...) ' , Это не 100% чистый :). Это заставляет меня думать, что вам, возможно, не нужно вводить конечные 0 в параметрах HASH_CALC. – Marian

+0

Я отредактировал исходное сообщение, чтобы избежать этих уродливых '(x + 0)'. – Marian

2

Вы могли бы использоваться тройной оператор:

#define HASH_CALCX(s)    \ 
    (strlen(s) == 5 ? HASH_CALC5(s) : \ 
    strlen(s) == 4 ? HASH_CALC4(s) : \ 
    strlen(s) == 3 ? HASH_CALC3(s) : \ 
    strlen(s) == 2 ? HASH_CALC2(s) : \ 
    strlen(s) == 1 ? HASH_CALC1(s) : some_error) 

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

Обновление: Выполненный пример с использованием gcc. Штраф, если уровень оптимизации установлен на 1, но не 0.

Пусть foox.c быть:

#include <string.h> 
#include <stdio.h> 

#define HASH_CALC(h, s) ((h) * 33 + *(s)) 
#define HASH_CALC1(s) (HASH_CALC(5381, s)) // hash_calc_start = 5381 
#define HASH_CALC2(s) (HASH_CALC(HASH_CALC1(s), s + 1)) 
#define HASH_CALC3(s) (HASH_CALC(HASH_CALC2(s), s + 2)) 
#define HASH_CALC4(s) (HASH_CALC(HASH_CALC3(s), s + 3)) 
#define HASH_CALC5(s) (HASH_CALC(HASH_CALC4(s), s + 4)) 

#define HASH_CALCX(s)    \ 
    (strlen(s) == 5 ? HASH_CALC5(s) : \ 
    strlen(s) == 4 ? HASH_CALC4(s) : \ 
    strlen(s) == 3 ? HASH_CALC3(s) : \ 
    strlen(s) == 2 ? HASH_CALC2(s) : \ 
    strlen(s) == 1 ? HASH_CALC1(s) : 0) 

int main(void) { 
    printf("%d\n", HASH_CALCX("foo")); 
    return 0; 
} 

Тогда gcc -S -O1 foox.c дает:

 .file  "foox.c" 
     .section .rodata.str1.1,"aMS",@progbits,1 
.LC0: 
     .string  "%d\n" 
     .text 
.globl main 
     .type  main, @function 
main: 
     leal  4(%esp), %ecx 
     andl  $-16, %esp 
     pushl  -4(%ecx) 
     pushl  %ebp 
     movl  %esp, %ebp 
     pushl  %ecx 
     subl  $20, %esp 
     movl  $193491849, 4(%esp) 
     movl  $.LC0, (%esp) 
     call  printf 
     movl  $0, %eax 
     addl  $20, %esp 
     popl  %ecx 
     popl  %ebp 
     leal  -4(%ecx), %esp 
     ret 
     .size  main, .-main 
     .ident  "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-54)" 
     .section .note.GNU-stack,"",@progbits 

Update 2: Как minor Усовершенствование, я бы определенно попытался добавить компиляцию «assert», чтобы убедиться, что переданы строки literal строки определенной длины на макрос, потому что я подвержен ошибкам. Например, изменить выше следующим образом:

#define ASSERT_zero(e) (!sizeof(struct{int:!!(e);})) 
#define HASH_CALCX(s) (ASSERT_zero(strlen(s) <= 5) + \ 
    (strlen(s) == 5 ? HASH_CALC5(s) : \ 
    etc 

ASSERT_zero() макрос похож на BUILD_BUG_ON_ZERO(), и использует "SizeOf битовое симулировать. Это дает либо:

  • ошибка компиляции, когда e является ложным, или
  • нулевого значения.

Этот ASSERT_zero() не работает для C++. И эта дополнительная проверка не будет работать для VS IIRC, потому что VS не считает strlen("foo") постоянной времени компиляции.

+0

Я могу подтвердить, что это работает -> Производит константу времени компиляции. Таким образом, это действительное временное решение, которое я мог бы использовать, если бы не было лучшего ответа (решения) ... спасибо вам до сих пор! (На самом деле мне даже не нравятся вещи HASH_CALC1/HASH_CALC2/HASH_CALC3. Таким образом, я получаю еще один макрос монстров) – Xatian

1

Хотя это удивительно, что можно сделать с помощью макросов, является то, что лучшим/самым ремонтопригоден способом сделать это?

Я был бы склонен помещать строки в другой файл и использовать скрипт perl для генерации хэшей и вывода исходного файла C++, содержащего строки и хэши, в некоторой удобной структуре данных.

Ваш make-файл может быть настроен для повторного запуска скрипта perl всякий раз, когда строки изменены.

Смежные вопросы