最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c - Call function in pre-compile time - Stack Overflow

programmeradmin3浏览0评论

Is it possible call a function inside a macro, due to resolve the field in pre-compile time?

For example, I have a structure with 2 fields, a constant char array (a string) and a uint32_t variable that should be the CRC32 of the string, something like that:

#define NEW_ROW(str) \
    {.theString = str, .theCRC = PRECOMPILER_CRC32_FUNCTION(str)}

typedef struct {
    const char *theString;
    const uint32_t theCRC;
} t_MyTable;

const t_MyTable MyTable[] = {
    NEW_ROW("First string"),
    NEW_ROW("Second string"),
    NEW_ROW("Third string"),
    NEW_ROW("Fourth string")
};

I use Cmake compiler.

Is it possible teach the precompiler for call PRECOMPILER_CRC32_FUNCTION(str) before the compilation?

Is it possible call a function inside a macro, due to resolve the field in pre-compile time?

For example, I have a structure with 2 fields, a constant char array (a string) and a uint32_t variable that should be the CRC32 of the string, something like that:

#define NEW_ROW(str) \
    {.theString = str, .theCRC = PRECOMPILER_CRC32_FUNCTION(str)}

typedef struct {
    const char *theString;
    const uint32_t theCRC;
} t_MyTable;

const t_MyTable MyTable[] = {
    NEW_ROW("First string"),
    NEW_ROW("Second string"),
    NEW_ROW("Third string"),
    NEW_ROW("Fourth string")
};

I use Cmake compiler.

Is it possible teach the precompiler for call PRECOMPILER_CRC32_FUNCTION(str) before the compilation?

Share Improve this question edited Mar 19 at 20:37 Thomas Dickey 54.8k8 gold badges78 silver badges108 bronze badges asked Mar 19 at 11:29 Maurizio ScianMaurizio Scian 131 silver badge2 bronze badges 4
  • 2 No, but the real question is how to compute the CRC of the string at build-time. I bet it can be achieved using macros. Or you could have a program generate the structure (incl the CRC) and then include the generated file. – ikegami Commented Mar 19 at 11:32
  • 7 This sounds like a job for a code generation step in your build. – Ian Abbott Commented Mar 19 at 11:33
  • 1 @Maurizio Scian, Not possible in general, yet post the definition of PRECOMPILER_CRC32_FUNCTION() as sometimes it is possible to convert that function into an equivalent pre-processor code. – chux Commented Mar 19 at 11:52
  • 1 What is the purpose of that all? Do you need a separate CRC for each of the strings or would a CRC for all of them be sufficient? In my last project in previous company we used linker functionality of TI clang tool chain to generate a table with CRC values for all ROM sections. Then at startup we iterated over that table and calculated CRC for the stored address ranges. – Gerhardh Commented Mar 19 at 12:11
Add a comment  | 

2 Answers 2

Reset to default 5

It is not possible for a C program to call a C function at compile time, but it is possible to write a program to generate some C source code, run that program and then compile it.

For example, the following C program gentable can generate the lines of the table. For convenience, I used the crc32 function from Zlib, hence the #include <zlib.h>, and the program needs to be linked with the z library:

gentable.c

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <zlib.h>

#define MYSTRINGS \
    X("First string") \
    X("Second string") \
    X("Third string") \
    X("Fourth string")

static void genline(FILE *fp, const char *asis, const char *binary)
{
    size_t len = strlen(binary);
    uint32_t crc = crc32_z(0, (const unsigned char *)binary, len);

    fprintf(fp, "{ %s, 0x%"PRIx32" },\n", asis, crc);
}


static const char *progname = "gentable";

int main(int argc, char **argv)
{
    FILE *fp;

    if (argc) {
        progname = argv[0];
    }
    if (argc > 2) {
        fprintf(stderr, "usage: %s [OUTPUTFILE]\n", progname);
        exit(2);
    }
    if (argc > 1) {
        fp = fopen(argv[1], "w");
        if (fp == NULL) {
            perror(argv[1]);
            exit(1);
        }
    } else {
        fp = stdout;
    }
#define X(s) genline(fp, #s, s);
    MYSTRINGS
#undef X
    if (fp != stdout) {
        fclose(fp);
    }
}

Some interesting details:

  1. The macro MYSTRINGS includes all the string literals with each string literal used as the parameter of the macro X(s). The macro X(s) is defined further down at the point it is required. This is known as the "X macro" technique.
  2. The macro X(s) is defined as #define X(s) genline(fp, #s, s);. This uses the preprocessor # operator to stringify the argument s. Since it is called with a string literal, it will produce a string literal representation of the source string literal. For example, the argument "First string" will be converted to the string literal "\"First string\"".
  3. The genline function is called with a FILE * stream pointer, the string literal representation of the string literal, and the compiled binary representation of the string. The CRC code is generated from the compiled binary representation of the string. It prints the string literal representation of the string and the CRC code as a line of text of the form { "some string", 0xdeadbeef },
  4. The main() function expands the MYSTRINGS macro to a sequence of calls to the genline() function, which will output a row of source code for the string. The expansion of the X(s) macro ends with a semicolon to separate these calls to the genline() function.

The program has an optional OUTPUTFILE command-line argument. If present, the output will the written to the named file. For example, this will write the output to file my_table.cdata:

$ ./gentable my_table.cdata

Here is the output from the program in generated source file my_table.cdata:

{ "First string", 0x7c5b638b },
{ "Second string", 0xc2de7700 },
{ "Third string", 0xe978905f },
{ "Fourth string", 0x7b9353d8 },

The file my_table.cdata can be #included into the program source for your program to fill in the contents of the MyTable[] array:

demo.c

#include <stdio.h>
#include <inttypes.h>

typedef struct {
    const char *theString;
    const uint32_t theCRC;
} t_MyTable;

const t_MyTable MyTable[] = {
#include "my_table.cdata"
};

#define ARRAY_LENGTH(x) (sizeof (x) / sizeof *(x))

int main(void)
{
    for (size_t i = 0; i < ARRAY_LENGTH(MyTable); i++) {
        printf("%s\n%#"PRIX32"\n", MyTable[i].theString, MyTable[i].theCRC);
    }
}

Output from the above program demo:

First string
0X7C5B638B
Second string
0XC2DE7700
Third string
0XE978905F
Fourth string
0X7B9353D8

With CMake, the following CMakeFiles.txt will first build the gentables program, then run it to generate the my_table.cdata source, and then build the demo program:

CMakeFiles.txt

cmake_minimum_required(VERSION 3.10)
project(demo)

add_custom_command(
    COMMAND gentable my_table.cdata
    DEPENDS gentable
    OUTPUT my_table.cdata
    COMMENT "Creating string CRC table..."
)

add_executable(gentable gentable.c)
target_link_libraries(gentable z)

add_executable(demo demo.c my_table.cdata)
target_include_directories(demo PRIVATE ${CMAKE_CURRENT_BINARY_DIR})

EDIT

One limitation of the above gentable.c source is that it uses strlen to determine the length of the string, which will not work if there are extra null characters in the string. It is easy to modify it to use the size of the string literal, subtracting 1 to discard the final terminating null. Change the genline function to the following:

static void genline(FILE *fp, const char *asis, size_t len, const char *binary)
{
    uint32_t crc = crc32_z(0, (const unsigned char *)binary, len);

    fprintf(fp, "{ %s, 0x%"PRIx32" },\n", asis, crc);
}

And change the X(s) macro to the following:

#define X(s) genline(fp, #s, sizeof(s) - 1, s);

I had a very similar issue, wanting to calculate crc32(__FILE__) at compile time. This is not possible as there is AFAIK no way to iterate over a string with preprocessor.

In your case, code generation is likely the best way to go. In Cmake, it will look something like this:

add_custom_command(
    OUTPUT generated/my_table.h
    COMMAND generate_my_table.sh meta/my_table.txt
    DEPENDS meta/my_table.txt
)

The OUTPUT and DEPENDS tell Cmake to rerun the command if meta/my_table.txt changes or generated/my_table.h does not yet exist.

发布评论

评论列表(0)

  1. 暂无评论