排查C宏展开中与格式说明符相关的编译错误
原UCI错误处理宏的问题分析
原错误处理宏定义
最初为捕获UCI操作失败原因定义的宏如下:
#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)
编译错误信息
编译时出现如下错误:
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__); \n | ^~~~~~~ trial.c:14:38: note: in definition of macro ‘MY_PRINT’ 14 | printf("[DM] " prefix " [%s:%d]: " fmt "\n", __FILE__, __LINE__, ##arg); \n | ^~~ trial.c:37:4: note: in expansion of macro ‘MY_ERR’ 37 | MY_ERR(err_msg, ##__VA_ARGS__); \n | ^~~~~~ 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); \n | ^~~~~~~ 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__); \n | ^~~~~~ 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); \n | ~^ | | | 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); \n | ^~~~~~~ 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__); \n | ^~~~~~ 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); \n | ~^ | | | int Compilation exited abnormally with code 1
可复现的简化代码
#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; }
修改后的正确宏
通过调整HANDLE_UCI_ERR宏解决了编译问题:
#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)
原代码的问题分析
宏展开的语法错误
原宏中调用MY_ERR(err_msg, ##__VA_ARGS__)时,MY_ERR展开后会变成MY_PRINT("Error : " err_msg, ##__VA_ARGS__),而C语言不支持直接用空格拼接字符串指针和字符串字面量,这直接导致了编译报错expected ‘)’ before ‘err_msg’。格式字符串与参数的逻辑混乱
MY_ERR的第一个参数要求是格式字符串,但原代码传入的err_msg是普通字符串。当printf把err_msg当作格式字符串处理时,后续传入的PACKAGE_STR会被当作该格式的参数,但err_msg并没有对应的占位符,从而引发格式不匹配的编译警告。uci_get_errorstr用法错误
原代码中给uci_get_errorstr传入_fmt_,相当于让UCI库用该格式字符串生成错误信息,之后又把生成好的err_msg当作格式字符串传给MY_ERR,属于重复且错误的格式处理逻辑。修改后传入NULL直接获取原始错误信息,再通过"%s : " fmt的格式字符串拼接错误信息与自定义提示,才是正确的用法。
内容的提问来源于stack exchange,提问作者Aziz Kavas
相关产品推荐
相关产品推荐

