Как (GNU/BSD) макросы libc могут быть безопасными?

После повторной реализации некоторых функций libc (в «личном» проекте моей школьной библиотеки под названием libft ), у меня возникла идея реализовать некоторые из них в виде макросов, например:

#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'))

Однако вскоре я обнаружил, что такие инструкции, как ft_isspace(s[--len]), становятся не работает, потому что переменная len трижды декрементируется. Поэтому мне пришлось создавать настоящие функции вместо макросов.

Я знаю, что макросы небезопасны. Но я вижу, что реализации GNU/BSD libc тестов is*(3) символов являются макросами. Как они защищают свои макросы?

Мне не разрешено (в школе) использовать функции, которые я не реализовал сам (кроме malloc(3), free(3) и нескольких системных вызовов, таких как write(2). И я предполагаю, что вызов функции только для проверки символа ASCII довольно неэффективен.

Спасибо.


person Diti    schedule 14.01.2014    source источник
comment
Мне не разрешено (в школе) использовать функции, которые я не реализовал сам. - Похоже, будет очень сложно написать хоть какой-то работающий код, следуя этому правилу.   -  person Fred Larson    schedule 14.01.2014
comment
Макросы BSD также небезопасны. Никаких макросов препроцессора нет.   -  person Some programmer dude    schedule 14.01.2014
comment
Мне не разрешено (в школе) использовать функции, которые я не реализовал сам. - Странно, что в школе на самом деле обучают синдрому «изобретено не здесь».   -  person Daniel Kamil Kozar    schedule 14.01.2014
comment
Что касается неэффективности функций, вы можете попросить компилятор сделать их встроенными.   -  person Some programmer dude    schedule 14.01.2014
comment
@JoachimPileborg Это C89. Встроенные функции C99.   -  person Diti    schedule 14.01.2014
comment
Существуют способы эффективной реализации большинства макросов без двойной оценки аргумента. Они по-прежнему небезопасны, но по другим причинам.   -  person cnicutar    schedule 14.01.2014
comment
Встроенные функции были введены в стандарт в C99. Это не означает, что компиляторы, реализующие более ранний стандарт, не имеют встроенных функций в качестве расширений поставщиков. Вы можете легко проверить это с помощью макросов препроцессора.   -  person Daniel Kamil Kozar    schedule 14.01.2014
comment
О боже, у вас действительно есть отдельный файл для каждой функции?   -  person Daniel Kamil Kozar    schedule 14.01.2014
comment
Почему бы вам не посмотреть включаемые файлы, чтобы увидеть, как версии макросов is* настроены для оценки аргумента только один раз?   -  person mpez0    schedule 14.01.2014
comment
@mpez0 Я, очевидно, сделал это, прежде чем задать вопрос здесь. Проверка заголовочного файла не сильно помогла, макросы запутаны другими макросами, определение которых я не знаю, где искать.   -  person Diti    schedule 15.01.2014


Ответы (1)


В этом конкретном случае для функций в ctype.h как Linux, так и BSD ищут значение в массиве, который содержит битовую маску для всех допустимых значений, с которыми могут быть вызваны функции - [0,255]. Каждая из функций просто индексирует массив своим аргументом и проверяет, установлен ли конкретный бит, который вы тестируете. Вот как они оценивают аргумент только один раз.

Это может выглядеть примерно так:

#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))

В более общем случае для всех макросов нужно сделать трюк:

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

Конечно, эта конструкция ничего не может вернуть (конструкция do-while такова, что это один оператор, который не прерывается, когда он помещается как один оператор в операторе if/for/while без скобок). Существуют расширения компилятора, которые позволяют безопасно реализовывать подобные макросы и возвращать значение.

В общем, я бы рекомендовал использовать встроенные функции или просто использовать обычные функции. Накладные расходы на вызов функций не так высоки на современных процессорах, и в больших программах вы можете даже получить существенное ускорение на макроуровне, тратя меньше кеша на код. Сначала сделайте свой код правильным, а затем подумайте об его ускорении, измеряя, действительно ли ваши встроенные строки что-то делают.

person Art    schedule 16.01.2014
comment
Аккуратный! Что должна делать функция twice(_x)? - person Diti; 17.01.2014
comment
@Diti Это был просто умный способ написать что-то с x дважды. - person Art; 17.01.2014