通常,除了提供函数声明之外,C标准头文件还可以提供“屏蔽宏”以使事情更快。例如,如果我包含ctype.h
,则头文件将声明
int isdigit(int c);
但它也可能用宏来掩盖声明。我相信根据C标准,这是一个可移植的isdigit
宏:
#define isdigit(c) ((c) >= '0' && (c) <= '9')
当然,这个宏也很危险,因为如果在定义宏时执行此操作会引入未定义的行为:
int c = 'A';
printf("%d\n", isdigit(c++));
为避免UB在这个假设的情况下,我必须用parens:(isdigit)(c++)
包围函数名。所以,我的问题是:标准头可以定义什么样的屏蔽宏有什么限制吗?如果参数表达式具有副作用,或者它们在技术上是否允许具有奇怪的行为,如上所述,它们是否可以保证不会导致未定义的行为?限制在哪里?
答案 0 :(得分:5)
根据C11 7.1.4.1,“库函数的使用”,特别是下面引用的最后一部分:
标头中声明的任何函数都可以另外实现为a 函数式宏在头文件中定义,所以如果是库函数 当包含其标题时,会明确声明 下面显示的技术可用于确保声明不是 受到这样一个宏的影响....出于同样的句法原因,它是 允许获取库函数的地址,即使它也是 定义为宏。使用
#undef
删除任何宏定义 还将确保引用实际功能。任何 调用作为宏实现的库函数 扩展到只编译一次其每个参数的代码, 在必要时用括号完全保护,所以一般来说 安全地使用任意表达式作为参数。
请注意,最后的“一般”很重要,并且有明确的例外情况。 C11 7.21.7.5.2说“getc
函数等价于fgetc
,但如果它是作为宏实现的,它可能会多次评估stream
,所以参数应该是从来不是一个带有副作用的表达“,在C11 7.21.7.7.2中用putc
的相似语言。在这种特殊情况下,stream
是FILE *
,所以在正常情况下,将此作为具有副作用的表达式会有点奇怪,但它可能会发生。对于广泛的角色对手putwc()
和getwc()
也是如此。我不知道这样的任何其他例外。
答案 1 :(得分:1)
具有副作用的参数以相同的方式评估表达式是执行宏扩展还是调用函数。函数getc和putc的宏是此规则的明确例外。
我发现了其他一些关于同一事情的引用(虽然没有直接引用C标准)。
所以似乎限制是只有getc()
和putc()
可能会以你预见的方式造成破坏。除了这两个,你应该是安全的,假设你的平台至少是一个理智的或符合的。