Use of macro overrides for functions
Asked Answered
C

3

1

I was reading implementation of header in C library, where I came across macro overrides for functions, along with function declarations. I want to know how is this useful, .i.e. either macro should be used or functions, what is the need for override ?

EDIT: Example:

/* 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

*Not to mention function definitions are in separate files

Clash answered 5/4, 2020 at 6:11 Comment(4)
Show an example of what you are referring to.Goalkeeper
@Goalkeeper I have edited to give an exampleClash
The #define 0x200 is a syntax error (I suspect it should be #define _XA 0x200). It isn't very material to your question, though.Corkhill
Please decide whether you want an answer regarding C or regarding C++, most often the answers are different as the languages are widely different as well.Consols
C
4

The C standard requires that the functions it specifies must be defined as functions, and they must be declared in the appropriate headers, so that you can pass pointers to functions around.

The C standard allows functions to be overridden by function-like macros.

The standard has a solid block of rules that I've reformatted as a bullet list. The first two bullet points are not directly relevant to the question.


§7.1.4 Use of library functions

¶1 Each of the following statements applies unless explicitly stated otherwise in the detailed descriptions that follow:

  • If an argument to a function has an invalid value (such as a value outside the domain of the function, or a pointer outside the address space of the program, or a null pointer, or a pointer to non-modifiable storage when the corresponding parameter is not const-qualified) or a type (after promotion) not expected by a function with variable number of arguments, the behavior is undefined.
  • If a function argument is described as being an array, the pointer actually passed to the function shall have a value such that all address computations and accesses to objects (that would be valid if the pointer did point to the first element of such an array) are in fact valid.
  • Any function declared in a header may be additionally implemented as a function-like macro defined in the header, so if a library function is declared explicitly when its header is included, one of the techniques shown below can be used to ensure the declaration is not affected by such a macro.
  • Any macro definition of a function can be suppressed locally by enclosing the name of the function in parentheses, because the name is then not followed by the left parenthesis that indicates expansion of a macro function name. For the same syntactic reason, it is permitted to take the address of a library function even if it is also defined as a macro.185)
  • The use of #undef to remove any macro definition will also ensure that an actual function is referred to.
  • Any invocation of a library function that is implemented as a macro shall expand to code that evaluates each of its arguments exactly once, fully protected by parentheses where necessary, so it is generally safe to use arbitrary expressions as arguments.186)
  • Likewise, those function-like macros described in the following subclauses may be invoked in an expression anywhere a function with a compatible return type could be called.187)
  • All object-like macros listed as expanding to integer constant expressions shall additionally be suitable for use in #if preprocessing directives.

¶2 Provided that a library function can be declared without reference to any type defined in a header, it is also permissible to declare the function and use it without including its associated header.


185) This means that an implementation shall provide an actual function for each library function, even if it also provides a macro for that function.

186) Such macros might not contain the sequence points that the corresponding function calls do.

187) Because external identifiers and some macro names beginning with an underscore are reserved, implementations may provide special semantics for such names. For example, the identifier _BUILTIN_abs could be used to indicate generation of in-line code for the abs function. Thus, the appropriate header could specify

     #define abs(x) _BUILTIN_abs(x)

for a compiler whose code generator will accept it. In this manner, a user desiring to guarantee that a given library function such as abs will be a genuine function may write

     #undef abs

whether the implementation's header provides a macro implementation of abs or a built-in implementation. The prototype for the function, which precedes and is hidden by any macro definition, is thereby revealed also.


The header in the question illustrates the use of reserved identifiers (§7.1.3 Reserved identifiers](http://port70.net/~nsz/c/c11/n1570.html#7.1.3)). It declares the functions that the <ctype.h> header is specified to declare. It provides macros which override those functions in the belief that using those will be quicker than calling a function that implements the array access.

With the implementation done that way, if you need to pass a pointer to one of the classification or conversion functions to some other code, you can do so. If only the macros were provided, you'd have to pull some stunts to get actual functions to pass as pointers.

The standard carefully stipulates that a few macros really must be macros — offsetof() and va_start() and va_arg() are three that spring to mind. But the vast majority of the functions in the standard must be implemented as functions — but may be overridden by a macro if the implementers think that is appropriate.

The requirement that the macros be function-like macros is important too. It allows the name to be used without being followed by parentheses to get a pointer to the function. If the macros were not function-like (if the header contained something like #define isupper _IsUpper instead of #define isupper(c) _IsUpper(c)) then it would be impossible to rely on accessing the standard function name — whereas the ¶2 rule allows you to write in your code (without including <ctype.h>):

extern int isupper(int c);

and you will be guaranteed that there is a function isupper() in the library that matches the expectations (even if there is also an _IsUpper() function).

Corkhill answered 5/4, 2020 at 7:22 Comment(0)
P
0

The actual interface is three arrays, e.g. the _Ctype array returns the type of a character. The macros, e.g. the is... macros are for convenience to use the _Ctype array.

I would say the arrays are fixed, the macros could be adapted to your use case. You could write new macros, change the existing ones. Or leave out the macros altogether.

The approach here is for speed and compatibility. With modern C++ constructs one would write such an interface differently.

Phebephedra answered 5/4, 2020 at 6:52 Comment(1)
_Ctype isn't a function, it's an array used to lookup a value with bits set to indicate character classes. _Tolower and _Toupper are also just arrays.Cathouse
A
-1

There's no reason at all for using a macro instead of a function. In the olden days there might have been an efficiency gain but modern compilers are perfectly capable of inlining function calls where appropriate.

You'll have to give an example for what you mean by a macro override. A function declaration and a macro declaration with the same name cannot reasonably coexist. For example, it's hard to call the function once the macro has been defined, you would have to undefine the macro first.

Amnion answered 5/4, 2020 at 6:15 Comment(3)
Well I suppose you could write #undef isalpha if you wanted to use the function. I believe that in C++ isalpha is a function, but I could be wrong. No idea what the C standard says.Amnion
They absolutely can coexist, and you don't have to undefine the macro to use the function. (isalpha)(ch) calls the function. That's because the invocation of a function-like macro has to be the name of the macro followed by optional whitespace followed by a (. When the name is in parentheses it's followed by a ), so it's not a macro invocation.Stinkweed
@PeteBecker interesting, fortunately i rarely use macros anymoreAmnion

© 2022 - 2024 — McMap. All rights reserved.