PostgreSQL のコーディングスタイル

作成日:2017.01.26

PostgreSQL で C 言語を使ったエクステンション(Extension)を作成する際に必要なコーディングスタイルのメモを残す。 ここで述べているコーディングスタイルは PostgreSQL のソースコードをサンプリングしてこういうルールだろうと類推したものである。 PostgreSQL ソースコード自体にもブレがあり、このルールに沿っていない箇所も多いので、参考までにして欲しい。

PostgreSQL の他の記事へのインデックスはここ


更新履歴
(2017.01.26) 作成。


目次

1. 公式のコーディング規約

PostgreSQL のコーディング規約は PostgreSQL 文書の 52章 に説明されている。

2. ファイル全体

2.1 .c ファイルのヘッダー

エクステンションの各 .c ファイルは(コメント部分を除くと)以下のようなヘッダーファイルのインクルードから始める。 赤字のコメントは説明のために挿入したものなので、実際にコードを書く場合には無視して欲しい。

/* 先頭で postgres.h をインクルードする。 */
/* クライアントサイドアプリケーションの場合は postgres_fe.h をインクルードする。 */
#include "postgres.h"

/* システムのヘッダーファイルをインクルードする。 */
#include <sys/stat.h>
#include <unistd.h>

/* postgres.h 以外の PostgreSQL のヘッダーファイルをインクルードする。 */
/* アルファベット順に並べること。 */
#include "access/htup_details.h"
#include "miscadmin.h"
#include "utils/rel.h"

/* このエクステンション固有のヘッダーをインクルードする。 */
#include "myextension.h"

少しコメントする。

3 番目のヘッダーファイルのカテゴリーには .c ファイル中で必要となる PostgreSQL のヘッダーファイルを列挙する。 ただし postgres.h の中で以下のヘッダーファイルはインクルード済みなので、改めて .c 側で取り込む必要はない。


このインクルードの仕方は原則である。 PostgreSQL 以外の様々なライブラリを取り込む場合、工夫が必要になることがある。

2.2 .h ファイルのヘッダー

.h ファイルは2重定義禁止用のマクロからはじめる。 これは .h ファイルのディレクトリ位置を取り除いたファイル名を大文字化して ._ に置き換えた名前になる。 foo_bar.h なら FOO_BAR_H になる。

次に .h ファイル中の中で直接参照する変数・型のヘッダーファイルを列記する。 ただし postgres.h は必ず .c ファイルで参照するので、postgres.h をインクルードする必要がない。

#ifndef FOO_BAR_H
#define FOO_BAR_H

#include "foreign/foreign.h"
#include "lib/stringinfo.h"
#include "nodes/relation.h"
#include "utils/relcache.h"

...

2.3 コメント

コメントは C89 の制約の縛りがあるので、/* コメント */ のみであり、C++形式の //コメント は使えない。

複数行のコメントは以下のように記載する。

/*
 * コメントテキストは2行目からはじめ
 * 1つ前の行で終える。
 */ 

コードは PostgreSQL には pgindent というツールがあり、コメントを整形する。

以下のよう、ダッシュを書くと pgindent を通しても改行を維持される。

/*----------
 * コメントテキストは2行目からはじめ
 * 1つ前の行で終える。
 *----------
 */ 

3. 構文

3.1 関数の定義

関数の定義は関数名以外を最初の1行に書き、関数名が次の行頭に来るように配置する。

int
funcname(int arg1, char **arg2)
{
    ...
}

関数引数が長過ぎる場合、1引数づつ改行する。 あるいは適当なところで改行する。

static int
funcname(int arg1,
         char **arg2)
{
    ...
}

3.2 ローカル変数の定義

ローカル変数の定義はブロックの先頭で行う必要がある。 ブロックの途中でのローカル変数の定義は禁止されている(C89 の制限)。

一つブロックの内に複数のローカル変数を定義する場合、変数名の位置で縦にそろえる慣習である。 この場合、ポインタ変数のポインタは(C++ではなくC言語なので)変数名の前に置くが、縦にそろえるのは変数名の開始位置なので以下のように並べることになる。 そのため一部インデントにタブが使えなくなる。

static void
somename_function(void)
{
    Index     scan_relid;
    List     *fdw_private;
    Plan    **plan_edge;
    int     (*callback)(int, void*);

    ...
}

3.3 ステートメント

if、while、for 文はブロックを開始する { を改行した位置に書く。

if (cond1)
{
}
else if (cond2)
{
}
else
{
}

do-while 文は do の後のブロックを開始する { を改行した位置に書くが、ブロックの終了の } と while は同じ行に書く。

do
{
} while (cond1);

switch 文のインデントは以下のように行う。

switch (value)
{
    case LABEL1:
        /* このインデント位置にコードを書く */
        break;

        /* case に対してブロックスコープ変数を用意するためにブロックを書く場合 */
    case LABEL2:
        {
            int c;

        }
        break; /* break はブロックから外に出す */
}

void 関数の最後の不要な return; は書かない。

3.4 if 文のぶら下がり構文

if 文のぶら下がり構文は認めるが、多段の if 文に else 節がでるような場合は明示的にブロック化する。 例えば以下のような書き方はあり。

if (code)
    umbrella1();
else
    umbrella2();

これもあり。

if (code1)
    if (code2)
        umbrella();

しかし以下のようなぶら下がり構文はなし。

if (code1)
    if (code2)
        umbrella1();
    else
        umbrella2();

ブロックで囲う。

if (code1)
{
    if (code2)
        umbrella1();
    else
        umbrella2();
}

3.5 スペーシング

if、while、for、do と括弧の間には 1 文字分のスペースを挿入する。

例えば下のコードは if( の間に 1 文字分のスペースがない。

if(cond1)
    statement();

   ↓ 

if (cond1)
    statement(); 

ただし foreach マクロは C 言語の構文ではないので、スペースを明けずに書く。

foreach(pl, parameters)
{
}

型の中のアスタリスク(*)はその前に1文字分のスペースを空ける。 キャストは後に 1文字分のスペースを空ける。

(Type*)object
   ↓
(Type *) object

3.6 可変長構造体

構造体の最後のメンバーを配列として、構造体本体に続くメモリ領域にアクセスする手法がある。 この手法は PostgreSQL でも利用されている。

C99 は最後のメンバー変数が構造体の場合、要素数を省略した記法 int16 tail[]; を認めているが、C89 では要素数が 0 個の配列は許されていない。

そこで PostgreSQL では要素が 1 個だけ持つ配列を配置する。 この最後のメンバー配列の後には /* VARIABLE LENGTH ARRAY */ というコメントを残すこと。 構造体定義の最後にも /* VARIABLE LENGTH STRUCT */ というコメントを残すこと。

typedef struct T
{
    /* other member */
    int16 tail[1]; /* VARIABLE LENGTH ARRAY */
} T;               /* VARIABLE LENGTH STRUCT */

別の方法として FLEXIBLE_ARRAY_MEMBER を使う方法がある。

typedef struct T
{
    /* other member */
    int16 tail[FLEXIBLE_ARRAY_MEMBER];
} T;

FLEXIBLE_ARRAY_MEMBER はその環境で適切な要素数が入る。 それは tail[1] かもしれないし、tail[0] かもしれないし、tail[] かもしれない。

3.7 その他

その他、以下のようなルールがある。

4. データ型

PostgreSQL は C99 標準でない真偽値型やビット幅指定整数のデータ型を定義している。 原則として PostgreSQL 定義を利用する。

PostgreSQLのデータ型 C言語やライブラリの標準 内容
bool 該当なし 真偽値を返すデータ型で実体は char 型。truefalse もマクロとして定義されている。
int8、uint8、int16、uint16、int32、uint32 int8_t、uint8_t、int16_t、uint16_t、int32_t、uint32_t 基本的な整数型に対する型である。 C99 では int8_t のように _t が付いた型が定義されているが、PostgreSQL の整数型には付かない。
int64、uint64 int64_t、uint64_t 64ビット整数型である。これはプラットフォームが 64 ビット型をサポートしている場合のみ有効。 ただし存在する前提でコードを書くことが多い。
float4 float 32 ビット浮動小数点。
float8 double 64 ビット浮動小数点。
Size size_t sizeof 演算子も戻り値や malloc 関数の引数などメモリサイズを示すデータ型。

bool 型は C99 に導入された _Bool ではないので注意が必要である。

5. 64 ビット定数の扱い

PostgreSQL は 64 ビット定数を定義するために INT64CONST(x)UINT64CONST(x) が定義されている。 これは C 言語の標準の INT64_C(x)UINT64_C(x) の代替である。

以下のように使う。

uint64 x, y;

x = UINT64CONST(0xFFFFFFFFFFFF0001);
y = UINT64CONST(9999999999999);

INT64CONST(x)UINT64CONST(x) は c.h の中で以下のように定義されている。

#ifdef HAVE_LL_CONSTANTS
#define INT64CONST(x)  ((int64) x##LL)
#define UINT64CONST(x) ((uint64) x##ULL)
#else
#define INT64CONST(x)  ((int64) x)
#define UINT64CONST(x) ((uint64) x)
#endif

printf のフォーマット文字列の中に 64 ビット整数を指定する場合のサイズ指定データ型として INT64_FORMATUINT64_FORMAT が定義されている。 これは C 言語の標準の PRIx64 などの代替である。

printf は long 型を指定する場合は l を、long long 型を ll を取るが、64 ビット整数は 64 ビットプラットフォームであっても UNIX 系では long 型、Windows 型では long long 型なため異なるサイズ指定をする必要がある。 INT64_FORMATUINT64_FORMAT を使うとポータビリティのある書き方が可能になる。

uint64 x;

printf("x = " UINT64_FORMAT "\n", x);

64 ビット型を 16 進数で表示するためのマクロが用意されていないが、INT64_MODIFIER が存在する。

uint64 x;

printf("x = " INT64_MODIFIER "x\n", x);

6. 整数の上限・下限

PostgreSQL は整数型の上限・下限が専用のマクロで定義されている。 これは limits.h で定義されているマクロの替わりである。

#define PG_INT8_MIN     (-0x7F-1)
#define PG_INT8_MAX     (0x7F)
#define PG_UINT8_MAX    (0xFF)
#define PG_INT16_MIN    (-0x7FFF-1)
#define PG_INT16_MAX    (0x7FFF)
#define PG_UINT16_MAX   (0xFFFF)
#define PG_INT32_MIN    (-0x7FFFFFFF-1)
#define PG_INT32_MAX    (0x7FFFFFFF)
#define PG_UINT32_MAX   (0xFFFFFFFF)
#define PG_INT64_MIN    (-INT64CONST(0x7FFFFFFFFFFFFFFF) - 1)
#define PG_INT64_MAX    INT64CONST(0x7FFFFFFFFFFFFFFF)
#define PG_UINT64_MAX   UINT64CONST(0xFFFFFFFFFFFFFFFF)

7. 便利なマクロ

c.h や pg_config_manual.h には便利なマクロがいくつか定義されている。 c.h も pg_config_manual.h も postgres.h から読み込まれるので、個別にインクルードしなくても利用できる。

よく使うと思うものを返す。

PostgreSQLのマクロ 内容
lengthof(array) 配列 array の要素数を返す。
Max(x, y) x と y を比較して大きい方を返す。
Min(x, y) x と y を比較して小さい方を返す。
MAXPGPATH ファイルパスとして使える文字列の上限。1024 が入っている。MAXPATHLEN、MAX_PATH、PATH_MAX などの替わりに使うことができる。これらの標準マクロのいずれかと同じかより小さい値が入っている。
BITS_PER_BYTE 1 バイトのビット数を返す。8 が入っている。PostgreSQL は char 型が 8 ビットでないシステムでは動作しない。
PG_CACHE_LINE_SIZE CPU のキャッシュのブロックサイズを返す。Intel の x86 CPU では 128 になる。

8. インライン関数

PostgreSQL にはインライン関数を定義するために STATIC_IF_INLINE が定義されている。 .h ファイルの中でインライン関数を定義するのに利用する。

STATIC_IF_INLINE int
list_length(const List *l)
{
    return l ? l->length : 0;
}

GCC の環境では #define STATIC_IF_INLINE static inline と定義されている。

9. アサート

C 言語には assert.h で診断関数 assert() が定義されているが、PostgreSQL では専用の Assert(x) マクロを使う。 使い方は assert() と一緒である。

ただしこのアサートは PostgreSQL をソースコードからビルドする際に configure に --enable-cassert を付けた場合のみ検査が行われる。

x. ポータビリティを高めるための専用関数

port.h の中にポータビリティを高めるための専用関数が定義されている。 port.h は postgres.h から読み込まれるので、port.h をインクルードしなくても利用できる。

PostgreSQLの関数 C言語やライブラリの標準 内容
pg_usleep() usleep() マイクロ秒単位でスリープする関数
pg_strcasecmp() strcasecmp() 大文字・小文字を区別しない文字列比較。他に pg_strncasecmp() もある。
pg_toupper() toupper() 与えられた文字を大文字に変換する。
pg_tolower() tolower() 与えられた文字を子文字に変換する。
pg_printf() printf() いわゆる printf()。他に pg_fprintf() など printf() のファミリーが一式存在する。
pgkill() kill() 指定のプロセスにシグナルを送信する。kill() はプリプロセッサマクロで pgkill() に入れ替わっている。
pg_erand48()、pg_lrand48()、pg_srand48() erand48()、lrand48()、srand48() 乱数。
pg_qsort() qsort() qsort 関数。実装は独自である。qsort() はプリプロセッサマクロで pg_qsort() に入れ替わっている。
qsort_arg() qsort_r() または qsort_s() 比較関数にコンテキストへのポインタを渡すことができる qsort 関数。

10. コンパイラの警告を抑止

未使用のローカル変数・静的変数はコンパイラが警告メッセージを表示するが、PG_USED_FOR_ASSERTS_ONLY を付ける事で抑止できる。 以下のように利用する。

int unused PG_USED_FOR_ASSERTS_ONLY;

コメント

コメントを書き込む
[1] [soda] 2017-01-30 13:12:09
2箇所typoがありました。
誤 STATIC_IF_INLNIE
正 STATIC_IF_INLINE

TOP    掲示板    戻る
Written by NAKAMURA Minoru, Email: nminoru atmark nminoru dot jp, Twitter:@nminoru_jp