【问题标题】:Use of macro overrides for functions对函数使用宏覆盖
【发布时间】:2020-07-17 04:57:04
【问题描述】:

我正在阅读 C 库中标头的实现,在那里我遇到了函数的宏覆盖以及函数声明。我想知道这有什么用,即应该使用宏还是函数,需要覆盖什么?

编辑: 示例:

/* ctype.h standard header */
#ifndef _CTYPE
#define _CTYPE
/* Ctype code b i t s */
#define 0x200 /* extra alphabetic */
#define _XS 0x100 /* extra space */
#define _BB 0x80 /* BEL, BS, etc. */
#define _CN 0x40 /*CR, FF, HT, NL, V T */
#define _DI 0x20 /* '0'_' 9' */
#define _LO 0x10 /* 'a'_'2'*/
#define _PU 0x08 /* punctuation */
#define _SP 0x04 /* space */
#define _UP 0x02 /* 'A' _ ' Z ' */
#define _XD 0x01 /* 'Or_'9', 'A'_'Fr, ' a r _ ' f r * /
/* ********declarations********** */
int isalnum(int) , isalpha (int) , iscntrl (iny) , isdigit (int) ;
int isgraph (int) , islower (int) , isprint (int) , ispunct (int) ;
int isspace (int) , isupper (int) , isxdigit (int) ;
int tolower (int) , toupper (int) ;
extern const short *_Ctype, *_Tolower, *_Toupper;

/************ macro overrides*********** */
#define isalnum(c) (_Ctype [ (int)(C) ] & (_DI | _LO | _UP | _XA) )
#define isalpha (c) (_Ctype [ (int)(C) ] & (_LO | _UP | _XA) )
#define i s c n t r l (c) (_Ctype [ (int)(C) ] & (_BB | _CN) )
#define isdigit (c) (_Ctype [ (int)(C) ] & _DI)
#define isgraph (c) (_Ctype [ (int)(C) ] & (_DI | _LO| _PU| _UP | _XA) )
#define islower (c) (_Ctype [ (int)(C) ] & _LO)
#define isprint (c) \
(_Ctype[(int) (c)1 & (_DI| _LO| _PU| _SP| _UP| _XA))
#define ispunct (c) (_Ctype [ ( int ) (c) ] & _PU)
#define isspace (c) (_Ctype [ ( int ) (c) ] & (_CN | _SP | _XS) )
#define isupper (c) (_Ctype [ ( int ) (c) ] & _UP)
#define isxdigit (c) (_Ctype [ ( int ) (c) ] & _XD)
#define tolower (c) _Tolower [ ( int ) (c) ]
#define toupper (c) _Toupper [ ( int ) (c) ]
#endif

*更不用说函数定义在单独的文件中

【问题讨论】:

  • 显示您所指内容的示例。
  • @kaylum 我已经编辑了一个例子
  • #define 0x200 是一个语法错误(我怀疑它应该是#define _XA 0x200)。不过,这对你的问题不是很重要。
  • 请决定您是否想要关于 C 或 C++ 的答案,由于语言也大相径庭,答案通常是不同的。

标签: c++ c c-libraries


【解决方案1】:

完全没有理由使用宏而不是函数。在过去,可能会提高效率,但现代编译器完全能够在适当的情况下内联函数调用。

您必须举例说明宏覆盖的含义。同名的函数声明和宏声明不能合理共存。例如,一旦定义了宏就很难调用函数,您必须先取消定义宏。

【讨论】:

  • 好吧,如果你想使用这个功能,我想你可以写#undef isalpha。我相信在 C++ 中 isalpha 是一个函数,但我可能是错的。不知道 C 标准是怎么说的。
  • 它们绝对可以共存,而且您不必取消定义宏来使用该功能。 (isalpha)(ch) 调用该函数。这是因为类函数宏的调用必须是宏的名称,后跟可选的空格,后跟(。当名称在括号中时,其后跟 ),因此它不是宏调用。
  • @PeteBecker 很有趣,幸运的是我不再使用宏了
【解决方案2】:

实际的接口是三个数组,例如_Ctype 数组返回字符的类型。宏,例如is... 宏是为了方便使用 _Ctype 数组。

我会说数组是固定的,宏可以适应您的用例。您可以编写新的宏,更改现有的宏。或者完全省略宏。

这里的方法是为了速度和兼容性。对于现代 C++ 结构,人们会以不同的方式编写这样的接口。

【讨论】:

  • _Ctype 不是函数,它是一个数组,用于查找设置为指示字符类的位的值。 _Tolower_Toupper 也只是数组。
【解决方案3】:

C 标准要求它指定的函数必须定义为函数,并且必须在适当的头文件中声明,以便您可以传递指向函数的指针。

C 标准允许函数被类似函数的宏覆盖。

该标准有一个可靠的规则块,我已将其重新格式化为项目符号列表。前两个要点与问题没有直接关系。


§7.1.4 Use of library functions

¶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.3 节保留标识符](http://port70.net/~nsz/c/c11/n1570.html#7.1.3))。它声明了 <ctype.h> 标头指定声明的函数。它提供了覆盖这些函数的宏,相信使用这些函数会比调用实现数组访问的函数更快。

通过这种方式实现,如果您需要将指向分类或转换函数之一的指针传递给其他代码,您可以这样做。如果只提供了宏,你就不得不花一些功夫来让实际的函数作为指针传递。

标准仔细规定了一些宏必须是宏——offsetof()va_start()va_arg() 是我想到的三个。但是标准中的绝大多数函数都必须作为函数来实现——但如果实现者认为合适的话,可以被宏覆盖。

宏是类函数宏的要求也很重要。它允许使用名称而不用括号来获取指向函数的指针。如果宏不是函数式的(如果标头包含类似 #define isupper _IsUpper 而不是 #define isupper(c) _IsUpper(c) 的内容),那么就不可能依赖访问标准函数名——而 ¶2 规则允许您在代码中编写(不包括<ctype.h>):

extern int isupper(int c);

并且您将保证库中有一个符合预期的函数isupper()(即使还有一个_IsUpper() 函数)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-10-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多