Вопрос

Upon reimplementing some libc functions (in my school’s “personal” library project called libft), I got the idea of implementing some as macros, like so:

#define ft_isalnum(c)  (ft_isalpha(c) || ft_isdigit(c))
#define ft_isalpha(c)  (ft_isupper(c) || ft_islower(c))
#define ft_isascii(c)  (((c) >= 0) && ((c) <= 0177))
#define ft_isdigit(c)  (((c) >= '0') && ((c) <= '9'))
#define ft_islower(c)  (((c) >= 'a') && ((c) <= 'z'))
#define ft_isprint(c)  (((c) >= 0040) && ((c) <= 0176))
#define ft_isspace(c)  ((((c) >= 0x09) && ((c) <= 0x0d)) || ((c) == 0x20))
#define ft_isupper(c)  (((c) >= 'A') && ((c) <= 'Z'))

However, I soon found out that instructions like ft_isspace(s[--len]) become broken, because the len variable gets decremented three times. So I had to make actual functions instead of macros.

I know that macros are unsafe. But I see that GNU/BSD libc implementations of the is*(3) character tests are macros. How do they make their macros safe?

I am not allowed (at school) to use functions I have not implemented myself (except malloc(3), free(3), and a few system calls such as write(2). And I presume that calling a function just to test an ASCII character is rather inefficient.

Thanks.

Это было полезно?

Решение

In this particular case for the functions in ctype.h both linux and BSDs look up the value in an array that contains a bitmask for all the valid values the functions can be called with - [0,255]. Each of the functions simply index the array with their argument and check if the particular bit you're testing for is set. That's how they only evaluate the argument once.

It might look something like this:

#define C_ALPHA 0x01
#define C_NUM 0x02
...
int ctype_array[256] = { ..., /* offset for 'A' */C_ALPHA|C_PRINT|C_UPPER, ...};
#define isalnum(c) (ctype_array[(c)] & (C_ALPHA|C_NUM))

In a more general case for all macros a trick is to do:

#define foo(x) do { int _x = (x); do_something_with(_x, twice(_x)); } while (0)

Of course this construct can't return anything (the do-while construct is so that this is a single statement that doesn't break when put as a single statement in an if/for/while statement without brackets). There are compiler extensions that allow macros like this to be implemented safely and returning a value.

In general, I would recommend going for inline functions or just use normal functions. Function call overhead isn't that high on modern CPUs and in large programs you might even get substantial speedups on a macro level by wasting less cache for code. Get your code correct first, then think about speeding it up while measuring if your inlines are actually doing anything at all.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top