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?


Wyrd
Желтоватенько, конечно. Если errno определена препроцессором, значит ее реализация «встраивается» в бинарники во время компиляции, так ведь? То есть все уже собранные бинарники перестанут работать корректно если реализацию поменять.
Вопрос: какого масштаба апокалипсис должны найти в текущей реализации чтобы такое изменение протащить через все код ревью в прод? Сломавшийся при этом GO будет явно меньшей проблемой, да и ту можно починить «скопировав» реализацию ещё раз и все пересобрав.
Wyrd
Ах да, .h файлы - это вполне себе documentation-as-a-code в таких случаях. Даже с комментариями иногда.
lockywolf
Собранный бинарник в мире UNIX вообще не считается чем-либо авторитативным. Гентушники же не просто так придумали emerge world.
Если у вас "добрый" вендор, то он хотя бы бампнет so-version, и у вас бинарник пожалуется на отсутствие символа. (Большинство вендоров "добрые")
Но бывает ещё хуже -- бинарники будут запускаться, но... крашиться или выдавать неверный ответ.
В мире UNIX авторитетен только код, и, собственно, это была одна из причин, почему Столлмана так взбесил тот закрытый драйвер для принтера.
DistortNeo
Тут другой момент: в каждой UNIX-подобной системе реализация этих вызовов разная, поэтому, если мы охватить множество платформ, нам придётся делать много системо-зависимых вызовов. Одно дело — просто перекомпилировать враппер, и совсем другое — поддерживать реализацию всего этого самостоятельно, что заведомо провальная идея.
Претензия же у людей к тому, что glibc, которая является обёрткой над системными вызовами, сама предоставляет API, к которому снова нужно писать обёртку из более высокоуровневых языков, что очень неудобно.