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?