cvariadic-macros

Troubleshooting C Macro Expansion Compilation Errors about format specifiers


Recently, I was working to get and set some fields on a UCI file. To get the failure reason appropriately, I decided to use some macro as given below:

#define HANDLE_UCI_ERR(_cond_, _fmt_, ...)                  \
    do {                                                    \
        if (_cond_) {                                       \
            char *err_msg = NULL;                           \
            uci_get_errorstr(g_uci_ctx, &err_msg, _fmt_);   \
            ERR(err_msg, ##__VA_ARGS__);                    \
            free(err_msg);                                  \
            goto bail;                                      \
        }                                                   \
    } while (0)

Note, this is not relevant with the question, but to compile uci code I installed its library using the steps given here.

However, it gives me following compile error:

gcc trial.c -luci -o trial
trial.c: In function ‘main’:
trial.c:37:11: error: expected ‘)’ before ‘err_msg’
   37 |    MY_ERR(err_msg, ##__VA_ARGS__);   \
      |           ^~~~~~~
trial.c:14:38: note: in definition of macro ‘MY_PRINT’
   14 |   printf("[DM] " prefix " [%s:%d]: " fmt "\n", __FILE__, __LINE__, ##arg); \
      |                                      ^~~
trial.c:37:4: note: in expansion of macro ‘MY_ERR’
   37 |    MY_ERR(err_msg, ##__VA_ARGS__);   \
      |    ^~~~~~
trial.c:72:2: note: in expansion of macro ‘HANDLE_UCI_ERR’
   72 |  HANDLE_UCI_ERR(rc != UCI_OK, "Not able to load package %s", PACKAGE_STR);
      |  ^~~~~~~~~~~~~~
trial.c:14:10: warning: format ‘%s’ expects a matching ‘char *’ argument [-Wformat=]
   14 |   printf("[DM] " prefix " [%s:%d]: " fmt "\n", __FILE__, __LINE__, ##arg); \
      |          ^~~~~~~
trial.c:19:3: note: in expansion of macro ‘MY_PRINT’
   19 |   MY_PRINT("Error  : ", fmt, ##arg)
      |   ^~~~~~~~
trial.c:37:4: note: in expansion of macro ‘MY_ERR’
   37 |    MY_ERR(err_msg, ##__VA_ARGS__);   \
      |    ^~~~~~
trial.c:72:2: note: in expansion of macro ‘HANDLE_UCI_ERR’
   72 |  HANDLE_UCI_ERR(rc != UCI_OK, "Not able to load package %s", PACKAGE_STR);
      |  ^~~~~~~~~~~~~~
trial.c:14:29: note: format string is defined here
   14 |   printf("[DM] " prefix " [%s:%d]: " fmt "\n", __FILE__, __LINE__, ##arg); \
      |                            ~^
      |                             |
      |                             char *
trial.c:14:10: warning: format ‘%d’ expects a matching ‘int’ argument [-Wformat=]
   14 |   printf("[DM] " prefix " [%s:%d]: " fmt "\n", __FILE__, __LINE__, ##arg); \
      |          ^~~~~~~
trial.c:19:3: note: in expansion of macro ‘MY_PRINT’
   19 |   MY_PRINT("Error  : ", fmt, ##arg)
      |   ^~~~~~~~
trial.c:37:4: note: in expansion of macro ‘MY_ERR’
   37 |    MY_ERR(err_msg, ##__VA_ARGS__);   \
      |    ^~~~~~
trial.c:72:2: note: in expansion of macro ‘HANDLE_UCI_ERR’
   72 |  HANDLE_UCI_ERR(rc != UCI_OK, "Not able to load package %s", PACKAGE_STR);
      |  ^~~~~~~~~~~~~~
trial.c:14:32: note: format string is defined here
   14 |   printf("[DM] " prefix " [%s:%d]: " fmt "\n", __FILE__, __LINE__, ##arg); \
      |                               ~^
      |                                |
      |                                int

Compilation exited abnormally with code 1

To be honest, I am not sure what is the problem in the code. Therefore, I wanted to share reproducible version of it. Here is the simplified version:

#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <uci.h>

#define DEBUG_ERROR_LEVEL 2


int loglevel = 7;


#define MY_PRINT(prefix, fmt, arg...) do {              \
        printf("[DM] " prefix " [%s:%d]: " fmt "\n", __FILE__, __LINE__, ##arg); \
    } while (0)

#define MY_ERR(fmt, arg...)         \
    if (loglevel > DEBUG_ERROR_LEVEL)   \
        MY_PRINT("Error  : " fmt, ##arg)

#define HANDLE_UCI_ERR(_cond_, _fmt_, ...)                              \
    do {                                                            \
        if (_cond_) {                                           \
            char *err_msg = NULL;                           \
            uci_get_errorstr(g_uci_ctx, &err_msg, _fmt_);   \
            MY_ERR(err_msg, ##__VA_ARGS__);         \
            free(err_msg);                                  \
            goto bail;                                      \
        }                                                       \
    } while (0)

static const char *PACKAGE_STR = "package";

static struct uci_context *g_uci_ctx;



__attribute__((constructor))
void init_uci_context() {
    g_uci_ctx = uci_alloc_context();
    if (!g_uci_ctx) {
        exit(EXIT_FAILURE);
    }
    if (uci_set_confdir(g_uci_ctx, "/home/aziz.kavas@corp.airties.com/tmp/config/") != 0) {
        exit(EXIT_FAILURE);
    }
}

__attribute__((destructor))
void cleanup_uci_context() {
    if (g_uci_ctx) {
        uci_free_context(g_uci_ctx);
    }
}

int main() {
    int rc = 0;
    struct uci_package *pkg = NULL;

    rc = uci_load(g_uci_ctx, PACKAGE_STR, &pkg);
    HANDLE_UCI_ERR(rc != UCI_OK, "Not able to load package %s", PACKAGE_STR);

bail:
    uci_unload(g_uci_ctx, pkg);
    return rc;
}


To solve it, I can either update HANDLE_UCI_ERR macro as below:

#define HANDLE_UCI_ERR(cond, fmt, arg...)                \
    do {                                                 \
        if (cond) {                                      \
            char *err_msg = NULL;                        \
            uci_get_errorstr(g_uci_ctx, &err_msg, NULL); \
            MY_ERR("%s : " fmt, err_msg, ##arg);         \
            free(err_msg);                               \
            goto bail;                                   \
        }                                                \
    } while (0)

If someone could tell me what I'm missing, I'd really appreciate it.


Solution

  • This ...

            MY_PRINT("Error  : " fmt, ##arg)
    

    ... from your MY_ERR macro expands to invalid C code when fmt is or expands to an identifier, such as err_msg. You seem to be trying to perform a string concatenation, but that works only for string literals, and err_msg is not a string literal.

    The solution you came up with, or something similar, is the simplest fix, though if you are determined to use variadic macros then you should probably use the standard form for them, with __VA_ARGS__ rather than GCC-style named variable arguments.