Об одном трюке для возврата кода ошибки из функции
Ядро Linux — кладезь как применяемых алгоритмов, так и некоторых хакерских или полухакерских трюков, призванных убыстрить и / или уменьшить размер в памяти (memory footprint). Об одном из таких полухахерских трюков я хочу рассказать далее.В нашем убогом мире языке Си как таковых ссылок и классов не существует, но есть указатели и структуры. Часто возникает вопрос, что выбрать в качестве возращаемого значения функции, которая должна вернуть вызывающему указатель на живущий объект? Есть два как минимум варианта прототипов:
struct obj *get_obj (…); и void get_obj (…, struct obj**); Почему я выделил второй метод, хотя он кажется здесь неэффективным? А дело вот в чём. Изначально мы можем вернуть указатель на объект или NULL, если такового не нашлось. Однако, у любого программиста возникает вопрос:, а как же исключения внутри метода? Да-да, на Диком Западе в языке Си мы пользуемся кодом возврата. И вот тут возникает такая картинка (см. прототипы выше и сравните):
struct obj *get_obj (…, int *ret); int get_obj (…, struct obj **); Но теперь задача уменьшить количество аргументов функции, так как это сильно влияет и на скорость исполнения, и на объёмы расходуемой памяти на стеке (ведь мы же о ядре ОС говорим, да? :-)).
Для этого было придумано такое решение. Поскольку void * у нас всё-таки число, то давайте разделим возможные значения на три отрезка (рассмотрим 32-битный случай):
0×00…0×10 0×11…0xffffffff — MAX_ERRNO 0xffffffff — MAX_ERRNO + 1…0xffffffff Первый — NULL pointer, второй — обычное адресное пространство, третий же — невалидный указатель или код ошибки. Соответственно появилось несколько макросов для проверки: ZERO_OR_NULL_PTR (); IS_ERR (); IS_ERR_OR_NULL (); И люди начали ими пользоваться! Результирующий прототип превратился в:
struct obj *get_obj (…); И пример использования: struct obj *get_obj (…) { struct obj *obj = malloc (sizeof (*obj)); int ret;
if (! obj) return ERR_PTR (-ENOMEM);
ret = do_smth (obj, …); if (ret) { free (obj); return ERR_PTR (ret); } return obj; }
int whatever (…) { struct obj *obj = get_obj (…); if (IS_ERR (obj)) return PTR_ERR (obj); printf («Cool object %p\n», obj); return 0; } Встречаются однако и проблемы с этим подходом, например, из-за неправильного применения макроса IS_ERR_OR_NULL для тех частей кода, где NULL — валидный возврат, например, когда объёкт отвечает за некую функциональность в ядре, которая в данной сборке ядра выключена. Тогда возвращается NULL pointer, который надо обрабатывать по-другому!