对C标准有任何限制,允许将函数实现为宏吗?
通常,除了提供函数声明之外,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++)
包围函数名。 所以,我的问题是:标准头可以定义哪种屏蔽宏有什么限制吗? 如果参数表达式有副作用,或者它们在技术上是否允许具有奇怪的行为(例如我们在上面看到),它们是否保证不会导致未定义的行为? 限制在哪里?
根据C11 7.1.4.1,“库函数的使用”,特别是下面引用的最后一部分:
标头中声明的任何函数可以另外实现为标头中定义的类函数宏,因此如果在包含标头时显式声明了库函数,则可以使用下面显示的技术之一来确保声明不是受此类宏的影响……出于同样的语法原因,即使它也被定义为宏,也允许获取库函数的地址。 使用
#undef
删除任何宏定义也将确保引用实际函数。 任何实现为宏的库函数的调用都应扩展为仅对其每个参数进行一次计算的代码,必要时用括号完全保护,因此使用任意表达式作为参数通常是安全的。
请注意,最后的“一般”很重要,并且有明确的例外情况。 C11 7.21.7.5.2说“ getc
函数等同于fgetc
,除非它实现为宏,它可能不止一次地评估stream
,因此参数永远不应该是带有副作用的表达式”,使用类似的语言对于putc
in C11 7.21.7.7.2。 在这种特殊情况下, stream
是一个FILE *
,所以在正常情况下,将它作为具有副作用的表达式会有点奇怪,但它可能会发生。 对于它们的宽字符对应物putwc()
和getwc()
也是如此。 我不知道这样的任何其他例外。
从这里: http : //www.qnx.com/developers/docs/6.5.0/index.jsp?topic =%2Fcom.qnx.doc.dinkum_en_ecpp%2Flib_over.html
无论表达式是执行宏扩展还是调用函数,具有副作用的参数都会以相同的方式进行评估。 函数getc和putc的宏是此规则的明确例外。
我发现了一些其他的引用说同样的事情(虽然没有直接引用C标准)。
所以似乎限制只有getc()
和putc()
可能会以你预见的方式造成破坏。 除了这两个,你应该是安全的,假设你的平台至少是理智的或符合要求的。