Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GCC macro expansion to call another macro

Tags:

c

gcc

macros

I am writing an application in C (gcc) which does a lot of string-comparison. Always one unknown/dynamic-string with a long list of compile-time constant strings. So I figured I hash the dynamic string and compare the resulting hash with precomputed hashes of the constant strings.

The do this I have the hash-algorithm in a function (for the dynamic runtime-strings) and as a macro so that gcc evaluates the hash during compile-time.

I got this:

#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")) {
    }
}

Now to the question:

Is it possible to create a macro which expands to HASH_CALCX(s) where X is the length of the constant string passed to the macro?

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

I tried this but it does not work.

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

Thank you!

like image 489
Xatian Avatar asked Jan 16 '14 14:01

Xatian


People also ask

Can a macro call another macro in C?

Here is an example of how to run another macro from a macro using the Call Statement. Just type the word Call then space, then type the name of the macro to be called (run). The example below shows how to call Macro2 from Macro1. It's important to note that the two macros DO NOT run at the same time.

Can a macro define another macro?

You cannot define macros in other macros, but you can call a macro from your macro, which can get you essentially the same results.

What is the use of macro expansion?

Macro expansion is an integral part of eval and compile . Users can also expand macros at the REPL prompt via the expand REPL command; See Compile Commands. Macros can also be expanded programmatically, via macroexpand , but the details get a bit hairy for two reasons. The second complication involves eval-when .

Can you supply more than one argument in a macro call?

3.3 Macro Arguments To invoke a macro that takes arguments, you write the name of the macro followed by a list of actual arguments in parentheses, separated by commas. The invocation of the macro need not be restricted to a single logical line—it can cross as many lines in the source file as you wish.


1 Answers

You could used the ternary operator:

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

For this to be viable would depend the compiler's optimization reducing this expression to a single numerical constant.

Update: Worked-out example using gcc. Fine if the optimization level is set to 1, but not for 0.

Let foox.c be:

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

Then gcc -S -O1 foox.c gives:

        .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: As a minor enhancement, I would definitely try to add a compile-time 'assert' to verify that only literal strings of a certain length are passed to the macro, because I am error-prone. For example, modify the above to read:

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

The ASSERT_zero() macro is similar to BUILD_BUG_ON_ZERO(), and uses the 'sizeof bitfield' trick. It yields either:

  • a compile error, when e is false, or
  • the value zero.

This ASSERT_zero() doesn't work for C++. And this extra check won't work for VS IIRC, because VS doesn't regard strlen("foo") as a compile-time constant.

like image 150
Joseph Quinsey Avatar answered Sep 28 '22 23:09

Joseph Quinsey