53.4. Различные соглашения по оформлению кода
53.4.1. Стандарт C
Код в PostgreSQL должен использовать только те возможности языка, что описаны в стандарте C99. Это означает, что код postgres должен успешно компилироваться компилятором, поддерживающим C99, возможно, за исключением нескольких платформозависимых мест.
Некоторые возможности, вошедшие в стандарт C99, в настоящее время использовать в коде ядра PostgreSQL нельзя. В данный момент это массивы переменного размера, перемежающиеся с кодом объявления, комментарии //
и универсальные символьные имена. Данный запрет объясняется соображениями переносимости и исторически сложившейся практикой.
Возможности более поздних ревизий стандарта C или специфические особенности компилятора могут использоваться, только если предусмотрен и вариант компиляции без них.
Например, в настоящее время используются конструкции _Static_assert()
и __builtin_constant_p
, хотя они относятся к более новым ревизиям стандарта C и расширению GCC, соответственно. Но если они недоступны, мы переходим к совместимой с C99 замене, которая выполняет те же проверки, но выдаёт довольно непонятные сообщения, и не используем __builtin_constant_p
.
53.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
. Это правило введено, потому что некоторые компиляторы генерируют указатели на символы, фигурирующие во внедрённых функциях, даже когда эти функции не используются.
53.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
, мог бы получить некорректное значение.
53.4.4. Вызов функций по указателям
Вызов функции по указателю может записываться по-разному. Ясности ради, когда указатель на функцию — простая переменная, предпочтительным вариантом считается запись с явным разыменованием указателя, например:
(*emit_log_hook) (edata);
(хотя будет работать и просто emit_log_hook(edata)
). Когда указатель на функции является частью структуры, дополнительные знаки пунктуации можно и обычно даже нужно опускать, например:
paramInfo->paramFetch(paramInfo, paramId);