我一直在阅读 C 标头上的 The Open Group Base Specifications 和 Posix Programmers 手册页,这句话反复出现“以下内容应声明为函数,也可以定义为宏”,我不确定这意味着什么。从声明的函数中创建宏有什么意义。这不会造成命名空间冲突吗?另外,您为什么还要这样做?
2 回答
宏从不与其他命名空间冲突;首先处理宏并自动“赢”。
标准的 C 标准库部分的介绍部分,第7.1 节介绍- 特别是§7.1.2、§7.1.3和¶7.1.4 - 涵盖了许多要点。
有些函数可以通过宏更有效地实现,而不会产生完整函数调用的开销。该标准的规则允许实现为此类函数定义宏,但需要注意一些警告。一是宏的参数不能被多次使用;另一个是标准中定义的每个函数都必须有一个函数,即使它也被实现为宏。
这些规则允许实施的灵活性,而不会给实施者或实施的用户增加负担。
§7.1.2 标准标题
¶4 ...如果使用,标题应包含在任何外部声明或定义之外,并且应首先包含在第一次引用它声明的任何函数或对象之前,或者它定义的任何类型或宏。然而,如果一个标识符在多个头文件中声明或定义,则第二个和随后的相关头文件可能包含在对该标识符的初始引用之后。程序不应有任何名称与当前定义的关键字在包含标题之前或在扩展标题中定义的任何宏时名称相同的宏。
¶5 本节中描述的类对象宏的任何定义都应扩展为在必要时由括号完全保护的代码,以便它在任意表达式中分组,就好像它是单个标识符一样。
¶6库函数的任何声明都应具有外部链接。
¶7.1.3 保留标识符
1 每个标头声明或定义其相关子条款中列出的所有标识符,并可选地声明或定义其相关未来库方向子条款中列出的标识符和标识符,这些标识符始终保留用于任何用途或用作文件范围标识符。
以下划线和大写字母或另一个下划线开头的所有标识符始终保留用于任何用途。
所有以下划线开头的标识符始终保留用作普通和标记名称空间中具有文件范围的标识符。
如果包含任何关联的头文件,则以下任何子条款(包括未来的库方向)中的每个宏名称都保留用于指定用途;除非另有明确说明(见 7.1.4)。
以下任何子条款(包括未来的库方向)和 errno 中的所有具有外部链接的标识符始终保留用作具有外部链接的标识符。184)
如果包含任何关联的标头,则保留在以下任何子条款(包括未来的库方向)中列出的具有文件范围的每个标识符用作宏名称和具有相同名称空间中的文件范围的标识符。
¶2没有保留其他标识符。如果程序在保留标识符的上下文中声明或定义标识符(7.1.4 允许的除外),或将保留标识符定义为宏名称,则行为未定义。
¶3 如果程序删除(使用
#undef
)上面列出的第一组中标识符的任何宏定义,则行为未定义。184)具有外部链接的保留标识符列表包括
math_errhandling
、setjmp
、va_copy
和va_end
。¶7.1.4 库函数的使用
¶1 除非在随后的详细描述中另有明确说明,否则以下每个语句均适用:如果函数的参数具有无效值(例如函数域之外的值,或地址空间之外的指针)程序,或空指针,或指向不可修改存储的指针(当相应的参数不是 const 限定时)或具有可变数量参数的函数不期望的类型(提升后),行为未定义。如果函数参数被描述为一个数组,则实际传递给函数的指针应具有一个值,以便所有地址计算和对对象的访问(如果指针确实指向此类数组的第一个元素,这将是有效的)实际上是有效的。头文件中声明的任何函数都可以额外实现为头文件中定义的类函数宏,因此如果在包含头文件时显式声明库函数,则可以使用下面显示的技术之一来确保声明不受这样一个宏的影响。函数的任何宏定义都可以通过将函数的名称括在括号中来在本地抑制,因为该名称后面没有表示宏函数名称扩展的左括号。出于相同的语法原因,即使库函数也被定义为宏,也允许获取库函数的地址。185)使用
#undef
删除任何宏定义也将确保引用实际函数。作为宏实现的库函数的任何调用都应扩展为仅对其每个参数求值一次的代码,必要时由括号完全保护,因此使用任意表达式作为参数通常是安全的。186)同样,以下子条款中描述的那些类似函数的宏可以在可以调用具有兼容返回类型的函数的任何地方的表达式中调用。187)所有列出为扩展为整数常量表达式的类对象宏应另外适用于#if 预处理指令。¶2 如果可以在不引用头文件中定义的任何类型的情况下声明库函数,则也允许声明函数并在不包括其关联头文件的情况下使用它。
185)这意味着实现应该为每个库函数提供一个实际函数,即使它还为该函数提供了一个宏。
186)此类宏可能不包含相应函数调用所做的序列点。
187)因为外部标识符和一些以下划线开头的宏名称是保留的,所以实现可以为这些名称提供特殊的语义。例如,标识符
_BUILTIN_abs
可用于指示abs
函数的内联代码的生成。因此,适当的标题可以指定:#define abs(x) _BUILTIN_abs(x)
对于其代码生成器将接受它的编译器。以这种方式,希望保证给定库函数(例如
abs
将是真正的函数)的用户可以编写#undef abs
实现的头文件是提供宏实现
abs
还是内置实现。函数的原型,它在任何宏定义之前并被任何宏定义隐藏,因此也被揭示了。
添加到¶7.1.4的重点
这些规则概述了实现者(编译器的,尤其是 C 库的)和使用该实现的程序员之间协定的要点。
标准库中的函数必须作为函数存在,因为即使您没有#include
任何标准头文件也可以使用它们。也就是说,链接器必须能够找到该函数,并且您可以自己插入声明,只要它是正确的声明并且您不包含任何声明该函数的头文件。
但是,某些功能可以更有效地实现为宏。(putc
并且是两个常见的例子。)如果是这样的话,实现可以通过包含有效的宏定义isdigit
来自由地利用它。
宏名和函数在 C 中不在同一个命名空间中,所以不会产生冲突。宏将在使用函数的地方进行扩展,这意味着外部函数通常不会被使用,但是有两个重要的方式定义为“类函数”宏的符号不会被宏扩展:
如果在宏定义中使用了符号,因为 C 不允许递归宏展开。这对于常用调用案例可以是宏的函数很有用,使用库函数作为后备。(例如,
ctype
如果当前语言环境不是 C 语言环境,函数可以定义为调用函数的宏。如果使用的宏名称没有带括号的参数列表。这允许您获取函数的地址,即使它具有宏实现。您(或标准库)可以通过使用冗余括号来强制使用函数而不是宏:
(isdigit)(my_char)