56.4. Различные соглашения по оформлению кода #
56.4.1. Стандарт C #
Код в PostgreSQL должен использовать только те возможности языка, что описаны в стандарте C99. Это означает, что код postgres должен успешно компилироваться компилятором, поддерживающим C99, возможно, за исключением нескольких платформозависимых мест.
Некоторые возможности, вошедшие в стандарт C99, в настоящее время использовать в коде ядра PostgreSQL нельзя. В данный момент это массивы переменного размера, перемежающиеся с кодом объявления, комментарии //
и универсальные символьные имена. Данный запрет объясняется соображениями переносимости и исторически сложившейся практикой.
Возможности более поздних ревизий стандарта C или специфические особенности компилятора могут использоваться, только если предусмотрен и вариант компиляции без них.
Например, в настоящее время используются конструкции _Static_assert()
и __builtin_constant_p
, хотя они относятся к более новым ревизиям стандарта C и расширению GCC, соответственно. Но если они недоступны, мы переходим к совместимой с C99 замене, которая выполняет те же проверки, но выдаёт довольно непонятные сообщения, и не используем __builtin_constant_p
.
56.4.2. Внедрённые функции и макросы, подобные функциям #
Допускается использование и макросов с аргументами, и функций static inline
. Последний вариант предпочтительнее, если возникает риск множественного вычисления выражений в макросе, как например в случае с
#define Max(x, y) ((x) > (y) ? (x) : (y))
или когда макрос может быть слишком объёмным. В других случаях использовать макросы — единственный, или как минимум более простой вариант. Например, может быть полезна возможность передавать макросу выражения различных типов.
Когда определение внедрённой функции обращается к символам (переменным, функциям), доступным только в серверном коде, такая функция не должна быть видна при включении в клиентский код.
#ifndef FRONTEND static inline MemoryContext MemoryContextSwitchTo(MemoryContext context) { MemoryContext old = CurrentMemoryContext; CurrentMemoryContext = context; return old; } #endif /* FRONTEND */
В этом примере вызывается функция CurrentMemoryContext
, существующая только на стороне сервера, и поэтому функция скрыта директивой #ifndef FRONTEND
. Это правило введено, потому что некоторые компиляторы генерируют указатели на символы, фигурирующие во внедрённых функциях, даже когда эти функции не используются.
56.4.3. Написание обработчиков сигналов #
Чтобы код мог выполняться внутри обработчика сигналов, его нужно написать очень аккуратно. Фундаментальная сложность состоит в том, что обработчик сигнала может прервать код в любой момент, если он не отключён. Если код внутри обработчика сигнала использует то же состояние, что и внешний основной код, это может привести к хаосу. В качестве примера представьте, что произойдёт, если обработчик сигнала попытается получить ту же блокировку, которой уже владеет прерванный код.
Если не предпринимать специальных мер, код в обработчиках сигналов может вызывать только безопасные с точки зрения асинхронных сигналов функции (как это определяется в POSIX) и обращаться к переменным типа volatile sig_atomic_t
. Также безопасными для обработчиков сигналов считаются несколько функций в postgres
, в том числе, что важно, SetLatch()
.
В большинстве случаев обработчики событий должны только сообщить о поступлении сигнала и пробудить код снаружи обработчика, используя защёлку. Например, обработчик может быть таким:
static void handle_sighup(SIGNAL_ARGS) { int save_errno = errno; got_SIGHUP = true; SetLatch(MyLatch); errno = save_errno; }
Переменная errno
сохраняется и восстанавливается, так как её может изменить SetLatch()
. Если этого не сделать, прерванный код, считывая errno
, мог бы получить некорректное значение.
56.4.4. Вызов функций по указателям #
Вызов функции по указателю может записываться по-разному. Ясности ради, когда указатель на функцию — простая переменная, предпочтительным вариантом считается запись с явным разыменованием указателя, например:
(*emit_log_hook) (edata);
(хотя будет работать и просто emit_log_hook(edata)
). Когда указатель на функции является частью структуры, дополнительные знаки пунктуации можно и обычно даже нужно опускать, например:
paramInfo->paramFetch(paramInfo, paramId);