52.4. Различные соглашения по оформлению кода
52.4.1. Стандарт C
Код в PostgreSQL должен использовать только те возможности языка, что описаны в стандарте C89. Это означает, что код postgres должен успешно компилироваться компилятором, поддерживающим C89, возможно, за исключением нескольких платформозависимых мест. Возможности более поздних ревизий стандарта C или специфические особенности компилятора могут использоваться, только если предусмотрен и вариант компиляции без них.
Например, в настоящее время используются конструкции static inline и _Static_assert(), хотя они относятся к более новым ревизиям стандарта C. Но если они недоступны, мы соответственно переходим к определению функций без inline и к использованию альтернативы, которая совместима с C89 и выполняет те же проверки, но выдаёт довольно непонятные сообщения.
52.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. Это правило введено, потому что некоторые компиляторы генерируют указатели на символы, фигурирующие во внедрённых функциях, даже когда эти функции не используются.
52.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, мог бы получить некорректное значение.
52.4. Miscellaneous Coding Conventions
C Standard
Code in PostgreSQL should only rely on language features available in the C89 standard. That means a conforming C89 compiler has to be able to compile postgres, at least aside from a few platform dependent pieces. Features from later revision of the C standard or compiler specific features can be used, if a fallback is provided.
For example static inline and _Static_assert() are currently used, even though they are from newer revisions of the C standard. If not available we respectively fall back to defining the functions without inline, and to using a C89 compatible replacement that performs the same checks, but emits rather cryptic messages.
Function-Like Macros and Inline Functions
Both, macros with arguments and static inline functions, may be used. The latter are preferable if there are multiple-evaluation hazards when written as a macro, as e.g., the case with
#define Max(x, y) ((x) > (y) ? (x) : (y))
or when the macro would be very long. In other cases it's only possible to use macros, or at least easier. For example because expressions of various types need to be passed to the macro.
When the definition of an inline function references symbols (i.e., variables, functions) that are only available as part of the backend, the function may not be visible when included from frontend code.
#ifndef FRONTEND
static inline MemoryContext
MemoryContextSwitchTo(MemoryContext context)
{
MemoryContext old = CurrentMemoryContext;
CurrentMemoryContext = context;
return old;
}
#endif /* FRONTEND */
In this example CurrentMemoryContext, which is only available in the backend, is referenced and the function thus hidden with a #ifndef FRONTEND. This rule exists because some compilers emit references to symbols contained in inline functions even if the function is not used.
Writing Signal Handlers
To be suitable to run inside a signal handler code has to be written very carefully. The fundamental problem is that, unless blocked, a signal handler can interrupt code at any time. If code inside the signal handler uses the same state as code outside chaos may ensue. As an example consider what happens if a signal handler tries to acquire a lock that's already held in the interrupted code.
Barring special arrangements code in signal handlers may only call async-signal safe functions (as defined in POSIX) and access variables of type volatile sig_atomic_t. A few functions in postgres are also deemed signal safe, importantly SetLatch().
In most cases signal handlers should do nothing more than note that a signal has arrived, and wake up code running outside of the handler using a latch. An example of such a handler is the following:
static void
handle_sighup(SIGNAL_ARGS)
{
int save_errno = errno;
got_SIGHUP = true;
SetLatch(MyLatch);
errno = save_errno;
}
errno is saved and restored because SetLatch() might change it. If that were not done interrupted code that's currently inspecting errno might see the wrong value.