GCC编译宏定义报错:粘贴预处理令牌无效问题求助
问题背景
我定义了如下宏:
#define GET_GENERIC_VAL(val_type, fmt_printf, prefix, suffix) \ val_type tmp_ ## val_type; \ rtc = (*(INOBJECT**)pObj)->getGeneric(meth, &(tmp_ ## val_type)); \ if (rtc == -1) { \ TRACE("Error: unable to get method for " #val_type " attribute " << attribute << " of object " << name); \ return -1; \ } \ sprintf(valueStr, fmt_printf, (prefix)(tmp_ ## val_type ## suffix));
并在switch语句中调用该宏:
switch (var_type) { case 'u' : { GET_GENERIC_VAL(UINT ,"%lu",UINT , + 0 ); break; } case 'i' : { GET_GENERIC_VAL(INT ,"%ld",INT , + 0 ); break; } case 's' : { GET_GENERIC_VAL(STRING ,"%s" ,const char *, + '\0' ); break; } case 'n' : { GET_GENERIC_VAL(NUMBER ,"%s" ,const char *, .toString()); break; } case 'b' : { GET_GENERIC_VAL(BYTESTRING,"%s" ,const char *, .toString()); break; } case 'd' : { GET_GENERIC_VAL(DATE ,"%s" ,const char *, .toString()); break; } case 't' : { GET_GENERIC_VAL(TIME ,"%s" ,const char *, .toString()); break; } default : { TRACE("Unknown type of attribute : type '" << var_type << "' for attribute '" << attribute << "' of object " << name << "."); return -1; } }
使用GCC编译时出现以下错误:
error: pasting "tmp_UINT" and "+" does not give a valid preprocessing token error: pasting "tmp_INT" and "+" does not give a valid preprocessing token error: pasting "tmp_STRING" and "+" does not give a valid preprocessing token error: pasting "tmp_NUMBER" and "." does not give a valid preprocessing token error: pasting "tmp_BYTESTRING" and "." does not give a valid preprocessing token error: pasting "tmp_DATE" and "." does not give a valid preprocessing token error: pasting "tmp_TIME" and "." does not give a valid
该代码在SUN平台编译正常,但在Linux的GCC下报错,恳请协助解决此问题。
问题原因
GCC的预处理器对##(Token拼接操作符)的规则比Sun编译器严格得多:##要求拼接前后的两个部分必须能组成一个合法的C语言Token。比如tmp_UINT是一个标识符Token,而+是运算符Token,这两个无法拼接成一个合法Token,所以GCC直接报错;而Sun编译器可能允许这种不规范的拼接,或者在预处理阶段做了额外的兼容处理。
你的宏里最后一行的tmp_ ## val_type ## suffix是问题核心:当suffix是+ 0或者.toString()时,##会强行把tmp_UINT和+、tmp_NUMBER和.拼接,这完全不符合C语言的Token规则。
解决方案
修改宏的实现,去掉suffix部分的Token拼接,把它改成直接跟在变量后面的表达式,不需要用##连接。调整后的宏如下:
#define GET_GENERIC_VAL(val_type, fmt_printf, prefix, expr_suffix) \ val_type tmp_ ## val_type; \ rtc = (*(INOBJECT**)pObj)->getGeneric(meth, &(tmp_ ## val_type)); \ if (rtc == -1) { \ TRACE("Error: unable to get method for " #val_type " attribute " << attribute << " of object " << name); \ return -1; \ } \ sprintf(valueStr, fmt_printf, (prefix)(tmp_ ## val_type expr_suffix));
然后修改switch中的调用方式,把原来的suffix参数改成直接的表达式后缀(去掉##需要的拼接形式,直接写在变量后面的内容):
switch (var_type) { case 'u' : { GET_GENERIC_VAL(UINT, "%lu", UINT, + 0); break; } case 'i' : { GET_GENERIC_VAL(INT, "%ld", INT, + 0); break; } case 's' : { GET_GENERIC_VAL(STRING, "%s", const char *, + '\0'); break; } case 'n' : { GET_GENERIC_VAL(NUMBER, "%s", const char *, .toString()); break; } case 'b' : { GET_GENERIC_VAL(BYTESTRING, "%s", const char *, .toString()); break; } case 'd' : { GET_GENERIC_VAL(DATE, "%s", const char *, .toString()); break; } case 't' : { GET_GENERIC_VAL(TIME, "%s", const char *, .toString()); break; } default : { TRACE("Unknown type of attribute : type '" << var_type << "' for attribute '" << attribute << "' of object " << name << "."); return -1; } }
说明
调整后的宏把原来的suffix参数改名为expr_suffix,并且在最后一行去掉了##,直接写成tmp_ ## val_type expr_suffix——这样预处理器会先拼接出变量名(比如tmp_UINT),然后直接把expr_suffix的内容(比如+ 0)跟在变量后面,形成合法的表达式(tmp_UINT + 0),而不是强行拼接Token。这种写法既符合C语言标准,也能被GCC和Sun编译器同时兼容。
内容的提问来源于stack exchange,提问作者Pravej Khan

