open()
(и использовать ABI вызова C-функций). К сожалению, не так всё просто, так как очень часто фрагменты обычного API C-библиотеки, на самом деле, реализованы в препроцессоре C. Из-за этого API C-библиотеки нельзя надёжно использовать для решения обычных задач без написания собственного связующего кода на C.
Звучит это, пожалуй, дико, поэтому позвольте мне проиллюстрировать это на примере всеми любимого значения errno
, к которому обращаются для получения кода ошибки в том случае, если системный вызов даёт сбой (им же пользуются и для получения кодов ошибок от некоторых библиотечных вызовов). В этом материале я рассказывал о том, что в современных условиях механизм errno
должен быть реализован так, чтобы у разных потоков были бы разные значения errno
, так как они могут в одно и то же время выполнять различные системные вызовы. Это требует наличия у потоков собственных локальных хранилищ, а к такому хранилищу нельзя обратиться так же, как к простой переменной. Доступ к нему должен быть организован через специальный механизм, поддерживаемый средой выполнения C. Вот объявления errno
из OpenBSD 6.6 и из текущей версии Fedora Linux с glibc:
/* OpenBSD */
int *__errno(void);
#define errno (*__errno())
/* Fedora glibc */
extern int *__errno_location (void) __THROW __attribute_const__;
# define errno (*__errno_location ())
В обоих этих случаях переменная
errno
, на самом деле, представлена определением препроцессора. Это определение ссылается на внутренние недокументированные функции C-библиотеки (на что указывают два символа подчёркивания в их именах), которые не входят в состав общедоступного API. Если скомпилировать код, написанный на C, рассчитанный на работу с этим API errno
(включив в код errno.h
), то он будет работать, но это — единственный официальный способ работы с errno
. Нет некоей обычной переменной errno
, которую можно загрузить в среде выполнения своего языка, например, после вызова функции open()
. А если вызвать __errno
или ____errno_location
в своей среде выполнения, то это будет означать использование внутреннего API, который в будущем вполне может измениться (хотя он, вероятно, не изменится). Для того чтобы создавать надёжные среды выполнения языков программирования, которые ориентированы на общедоступный API С-библиотеки, недостаточно просто вызвать экспортированную функцию вроде open()
; нужно ещё написать и скомпилировать собственную маленькую C-функцию, которая просто возвращает среде выполнения errno
.
(Тут, помимо errno
, могут быть и другие важные моменты; я предлагаю самостоятельно поискать их тем, кому интересна эта тема.)
Это, конечно, не какая-то новая проблема Unix. С первых дней stdio
в V7 некоторые из «функций» stdio
были реализованы в stdio.h в виде макросов препроцессора. Но в течение долгого времени никто не настаивал на том, чтобы единственным официально поддерживаемым способом выполнения системных вызовов было бы их выполнение из C-библиотеки, что позволяло обойти нечто вроде того, что представляет собой современный механизм errno
, в тех случаях, когда не нужна совместимость с C-кодом.
(До того, как в Unix появилась многопоточность, сущность errno
была представлена простой переменной и, в целом, выглядела как хороший, хотя и не идеальный интерфейс.)
Пользовались ли вы когда-нибудь недокументированными API?
Комментариев нет:
Отправить комментарий