选择 打开 改范围 完整检索页

pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。

受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
测试与开发版本: 19 / devel
不受支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0
历史版本PostgreSQL 9.4 已于 2020 年 2 月结束社区维护,本页译文保留供仍在使用旧版本的读者参考。新系统请看当前版本

35.9. C 语言函数 #

用户定义的函数可以用 C 编写(或者可以与 C 兼容的语言,例如 C++)。 这类函数被编译成动态载入对象(也被称为共享库)并且由服务器在 需要时载入。动态载入是把C语言函数和 内部函数区分开的特性 — 两者真正的编码习惯 实际上是一样的(因此,标准的内部函数库是用户定义的 C 函数很好 的源代码实例)。

目前 C 函数使用两种不同的调用约定。较新的版本 1调用约定通过为函数编写一个PG_FUNCTION_INFO_V1()宏来指示,如下文所示。没有这样的宏则表示这是一个旧式(版本 0)函数。无论哪种情况,CREATE FUNCTION中指定的语言名称都是C。旧式函数由于可移植性问题和功能缺乏,现在已被弃用,但出于兼容性原因仍然支持。

35.9.1. 动态载入 #

在一个会话中第一次调用一个特定可载入目标文件中的用户定义函数时, 动态载入器会把那个目标文件载入到内存以便该函数被调用。因此用户 定义的 C 函数的CREATE FUNCTION必须 为该函数指定两块信息:可载入目标文件的名称,以及要在该目标文件中 调用的特定函数的 C 名称(链接符号)。如果没有显式指定 C 名称,则 它被假定为和 SQL 函数名相同。

下面的算法被用来基于CREATE FUNCTION 命令中给定的名称来定位共享目标文件:

  1. 如果名称是一个绝对路径,则载入给定的文件。

  2. 如果该名称以字符串$libdir开始,那么这一部分会被 PostgreSQL包的库目录名(在编译时确定)替换。

  3. 如果该名称不包含目录部分,会在配置变量 dynamic_library_path指定的路径中搜索该 文件。

  4. 否则(在该路径中没找到该文件,或者它包含一个非绝对目录), 动态载入器将尝试接受给定的名称,这大部分会导致失败(依赖 当前工作目录是不可靠的)。

如果这个序列不起作用,会把平台相关的共享库文件名扩展(通常是 .so)追加到给定的名称并且再次尝试上述 的过程。如果还是失败,则载入失败。

建议通过相对于 $libdir 的路径,或者通过动态库路径来定位共享库。这样一来,如果新安装位于不同的位置,版本升级会更简单。$libdir 实际代表的目录可以通过命令 pg_config --pkglibdir 查出。

用于运行PostgreSQL服务器的 用户 ID 必须能够通过要载入文件的路径。常见的错误是把文件或 更高层的目录变得对postgres用户 不可读或者不可执行。

在任何情况下,CREATE FUNCTION命令 中给定的文件名会被原封不动地记录在系统目录中,这样如果需要再次 载入该文件则会应用同样的过程。

注意

PostgreSQL不会自动编译 C 函数。在 从CREATE FUNCTION命令中引用目标文件 之前,它必须先被编译好。更多信息请见第 35.9.6 节

为确保动态装载的目标文件不会被加载到不兼容的服务器中,PostgreSQL会检查该文件是否包含内容适当的魔数块。这样服务器就能检测出明显的不兼容情况,例如代码所针对的大版本不同于当前的 PostgreSQL。魔数块自 PostgreSQL 8.2 起就是必需的。要加入魔数块,请在模块某一个(且只能一个)源文件中写入下列内容,并事先包含头文件 fmgr.h

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

可以省略 #ifdef 检查,如果代码不需要针对 8.2 之前的 PostgreSQL 版本编译的话。

在第一次使用之后,动态载入目标文件会保留在内存中。在同一个会话中, 后续对该文件中函数的调用只需付出一次很小的符号表查找开销。如果需要 强制重新载入一个目标文件(例如在重新编译之后),就需要开启一个新的会话。

动态装载的文件可以选择包含初始化和终结函数。如果文件包含一个名为 _PG_init 的函数,该函数会在文件装载后立即被调用。该函数不接收参数,并且应返回 void。如果文件包含一个名为 _PG_fini 的函数,该函数会在文件卸载前立即被调用。同样,该函数不接收参数,并且应返回 void。注意,_PG_fini 只会在卸载文件时被调用,而不会在进程终止时被调用。(目前,卸载已被禁用,永远不会发生,但将来可能会改变。)

35.9.2. C 语言函数中的基础类型 #

要了解如何编写 C 语言函数,你需要了解 PostgreSQL如何在内部表达基础类型 以及如何与函数传递它们。在内部, PostgreSQL把基础类型视为一块内存数据块。 你为该类型定义的用户自定义函数,决定了 PostgreSQL 如何操作它。 也就是说,PostgreSQL 只负责把数据存到磁盘、再从磁盘取回, 而数据的输入、处理和输出则依赖你定义的这些函数。

基础类型可以有三种内部格式之一:

  • 传值,定长

  • 传引用,定长

  • 传引用,变长

传值类型在长度上只能是 1、2 或 4 字节(如果你的机器上 sizeof(Datum)是 8,则还有 8 字节)。你应当小心地 定义你的类型以便它们在所有的架构上都是相同的尺寸(字节)。例如, long类型很危险,因为它在某些机器上是 4 字节但在 另外一些机器上是 8 字节,而int类型在大部分 Unix 机器 上都是 4 字节。在 Unix 机器上int4类型一种合理的实现 可能是:

/* 4 字节整数,传值 */
typedef int int4;

(实际的 PostgreSQL C 代码会把这种类型称为int32,因为 C 中的习惯是intXX 表示XX 。注意 因此还有尺寸为 1 字节的 C 类型int8。SQL 类型 int8在 C 中被称为int64。另见 表 35.1)。

另一方面,任意尺寸的定长类型都可以通过引用传递。例如,这里有一种 PostgreSQL类型的实现示例:

/* 16 字节结构,传引用 */
typedef struct
{
    double  x, y;
} Point;

PostgreSQL函数中传进或传出这种 类型时,只能使用指向这种类型的指针。要返回这样一种类型的值,用 palloc分配正确的内存量,然后填充分配好的内存, 并且返回一个指向该内存的指针(还有,如果只想返回与具有相同数据类型的 一个输入参数相同的值,可以跳过额外的palloc并且返回 指向该输入值的指针)。

最后,所有变长类型必须也以引用的方式传递。所有变长类型必须用一个 正好 4 字节的不透明长度字段开始,该字段会由SET_VARSIZE 设置,绝不要直接设置该字段!所有要被存储在该类型中的数据必须在内存 中接着该长度字段的后面存储。长度字段包含该结构体的总长度,也就是包括长度字段本身的尺寸。

另一个重点是要避免在数据类型值中留下未被初始化的位。例如,要注意 把可能存在于结构体中的任何对齐填充字节置零。如果不这样做,你的数据 类型的逻辑等价常量可能会被规划器认为是不等的,进而导致低效的(不过 还是正确的)计划。

警告

绝不要修改通过引用传递的输入值的内容。如果这样做 很可能会破坏磁盘上的数据,因为给出的指针可能直接指向一个磁盘缓冲 区。这条规则唯一的例外在第 35.10 节中有解释。

例如,我们可以这样定义类型text

typedef struct {
    int32 length;
    char data[1];
} text;

显然,这里声明的 data 字段并不足以容纳所有可能的字符串。由于在 C中不可能声明变长结构体,我们依赖这样一个事实: C编译器不会对数组下标做范围检查。我们只须分配 所需数量的空间,然后就像数组按正确长度声明过一样去访问它。(这是 一个常见的技巧,在许多关于 C 的教科书中都能读到。)

在操作变长类型时,我们必须小心分配正确数量的内存,并正确设置长度字段。 例如,如果我们想在一个text结构体 中存储 40 字节,我们可以使用这样的代码片段:

#include "postgres.h"
...
char buffer[40]; /* our source data */
...
text *destination = (text *) palloc(VARHDRSZ + 40);
SET_VARSIZE(destination, VARHDRSZ + 40);
memcpy(destination->data, buffer, 40);
...

VARHDRSZsizeof(int32)一样, 但是用宏VARHDRSZ来引用变长类型的额外开销的 尺寸被认为是比较好的风格。还有,必须 使用SET_VARSIZE宏来设置长度字段,而不是用 简单的赋值来设置。

表 35.1说明了在编写使用 PostgreSQL 内置类型的 C 语言函数时,哪种 C 类型对应哪种 SQL 类型。 定义文件列给出了需要包含的头文件,以获取类型定义。 (实际的定义可能位于所列文件包含的其他文件中。建议用户坚持使用已定义的接口。) 请注意,在任何源文件中都应该始终首先包含postgres.h, 因为它声明了很多你反正都会用到的内容。

表 35.1. 内置 SQL 类型等效的 C 类型

SQL 类型 C 类型 定义文件
abstime AbsoluteTime utils/nabstime.h
boolean bool postgres.h(可能是编译器内置)
box BOX* utils/geo_decls.h
bytea bytea* postgres.h
"char" char (编译器内置)
character BpChar* postgres.h
cid CommandId postgres.h
date DateADT utils/date.h
smallint (int2) int16 postgres.h
int2vector int2vector* postgres.h
integer (int4) int32 postgres.h
real (float4) float4* postgres.h
double precision (float8) float8* postgres.h
interval Interval* datatype/timestamp.h
lseg LSEG* utils/geo_decls.h
name Name postgres.h
oid Oid postgres.h
oidvector oidvector* postgres.h
path PATH* utils/geo_decls.h
point POINT* utils/geo_decls.h
regproc regproc postgres.h
reltime RelativeTime utils/nabstime.h
text text* postgres.h
tid ItemPointer storage/itemptr.h
time TimeADT utils/date.h
time with time zone TimeTzADT utils/date.h
timestamp Timestamp* datatype/timestamp.h
tinterval TimeInterval utils/nabstime.h
varchar VarChar* postgres.h
xid TransactionId postgres.h

了解了基础类型所有可能的结构后,就可以看一些实际函数示例。

35.9.3. 版本 0 调用约定

我们先介绍旧式调用约定——尽管这种方法现在已被弃用,但入门时更容易上手。在版本 0 方法中,C 函数的参数和结果就按普通的 C 风格声明,只是要注意使用上文所示的每种 SQL 数据类型的 C 表示。

下面是一些示例:

#include "postgres.h"
#include <string.h>
#include "utils/geo_decls.h"

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

/* by value */

int
add_one(int arg)
{
    return arg + 1;
}

/* by reference, fixed length */

float8 *
add_one_float8(float8 *arg)
{
    float8    *result = (float8 *) palloc(sizeof(float8));

    *result = *arg + 1.0;

    return result;
}

Point *
makepoint(Point *pointx, Point *pointy)
{
    Point     *new_point = (Point *) palloc(sizeof(Point));

    new_point->x = pointx->x;
    new_point->y = pointy->y;

    return new_point;
}

/* by reference, variable length */

text *
copytext(text *t)
{
    /*
     * VARSIZE is the total size of the struct in bytes.
     */
    text *new_t = (text *) palloc(VARSIZE(t));
    SET_VARSIZE(new_t, VARSIZE(t));
    /*
     * VARDATA is a pointer to the data region of the struct.
     */
    memcpy((void *) VARDATA(new_t), /* destination */
           (void *) VARDATA(t),     /* source */
           VARSIZE(t) - VARHDRSZ);  /* how many bytes */
    return new_t;
}

text *
concat_text(text *arg1, text *arg2)
{
    int32 new_text_size = VARSIZE(arg1) + VARSIZE(arg2) - VARHDRSZ;
    text *new_text = (text *) palloc(new_text_size);

    SET_VARSIZE(new_text, new_text_size);
    memcpy(VARDATA(new_text), VARDATA(arg1), VARSIZE(arg1) - VARHDRSZ);
    memcpy(VARDATA(new_text) + (VARSIZE(arg1) - VARHDRSZ),
           VARDATA(arg2), VARSIZE(arg2) - VARHDRSZ);
    return new_text;
}

假定上述代码已经写在文件funcs.c中并编译为共享对象,我们可以用类似下面的命令向 PostgreSQL定义这些函数:

CREATE FUNCTION add_one(integer) RETURNS integer
     AS 'DIRECTORY/funcs', 'add_one'
     LANGUAGE C STRICT;

-- note overloading of SQL function name "add_one"
CREATE FUNCTION add_one(double precision) RETURNS double precision
     AS 'DIRECTORY/funcs', 'add_one_float8'
     LANGUAGE C STRICT;

CREATE FUNCTION makepoint(point, point) RETURNS point
     AS 'DIRECTORY/funcs', 'makepoint'
     LANGUAGE C STRICT;

CREATE FUNCTION copytext(text) RETURNS text
     AS 'DIRECTORY/funcs', 'copytext'
     LANGUAGE C STRICT;

CREATE FUNCTION concat_text(text, text) RETURNS text
     AS 'DIRECTORY/funcs', 'concat_text'
     LANGUAGE C STRICT;

这里DIRECTORY代表共享库文件所在的目录(例如 PostgreSQL的教程目录,其中包含本节示例所用的代码)。 (更好的风格是在AS子句中只写'funcs',前提是已把 DIRECTORY加入搜索路径。无论哪种情况,都可以省略共享库的系统特定扩展名,通常是 .so.sl。)

注意我们把函数声明为strict(严格),意思是如果任何输入值为空,系统会自动假定结果为空。这样做可以避免在函数代码中检查空输入。否则,我们就必须显式检查空值,即检查每个传引用参数是否为空指针。(对于传值参数,我们甚至没有办法检查!)

尽管这种调用约定使用简单,但可移植性不好;在某些体系结构上,以这种方式传递小于int的数据类型会有问题。另外,也没有返回空结果的简单方法,除了把函数声明为严格函数之外,也没有其他处理空参数的简单方法。下面介绍的版本 1 约定克服了这些缺点。

35.9.4. 版本 1 的调用约定

版本-1 的调用约定依赖于宏来降低传参数和结果的复杂度。版本-1 函数的 C 声明总是:

Datum funcname(PG_FUNCTION_ARGS)

此外,宏调用:

PG_FUNCTION_INFO_V1(funcname);

必须出现在同一个源文件中(按惯例会正好写在该函数本身之前)。 这种宏调用不是internal语言函数所需要的,因为 PostgreSQL会假定所有内部函数都使用 版本-1 调用约定。不过,对于动态载入函数是必需的。

在版本-1 函数中,每一个实参都使用对应于该参数数据类型的PG_GETARG_xxx()宏取得,结果要用对应于返回类型的PG_RETURN_xxx()宏返回。PG_GETARG_xxx()的参数是要取得的函数参数的编号,从零开始计。PG_RETURN_xxx()的参数是实际要返回的值。

这里我们展示与上面相同的函数,以版本-1 风格编码:

#include "postgres.h"
#include <string.h>
#include "fmgr.h"
#include "utils/geo_decls.h"

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

/* by value */

PG_FUNCTION_INFO_V1(add_one);

Datum
add_one(PG_FUNCTION_ARGS)
{
    int32   arg = PG_GETARG_INT32(0);

    PG_RETURN_INT32(arg + 1);
}

/* by reference, fixed length */

PG_FUNCTION_INFO_V1(add_one_float8);

Datum
add_one_float8(PG_FUNCTION_ARGS)
{
    /* The macros for FLOAT8 hide its pass-by-reference nature. */
    float8   arg = PG_GETARG_FLOAT8(0);

    PG_RETURN_FLOAT8(arg + 1.0);
}

PG_FUNCTION_INFO_V1(makepoint);

Datum
makepoint(PG_FUNCTION_ARGS)
{
    /* Here, the pass-by-reference nature of Point is not hidden. */
    Point     *pointx = PG_GETARG_POINT_P(0);
    Point     *pointy = PG_GETARG_POINT_P(1);
    Point     *new_point = (Point *) palloc(sizeof(Point));

    new_point->x = pointx->x;
    new_point->y = pointy->y;

    PG_RETURN_POINT_P(new_point);
}

/* by reference, variable length */

PG_FUNCTION_INFO_V1(copytext);

Datum
copytext(PG_FUNCTION_ARGS)
{
    text     *t = PG_GETARG_TEXT_P(0);
    /*
     * VARSIZE is the total size of the struct in bytes.
     */
    text     *new_t = (text *) palloc(VARSIZE(t));
    SET_VARSIZE(new_t, VARSIZE(t));
    /*
     * VARDATA is a pointer to the data region of the struct.
     */
    memcpy((void *) VARDATA(new_t), /* destination */
           (void *) VARDATA(t),     /* source */
           VARSIZE(t) - VARHDRSZ);  /* how many bytes */
    PG_RETURN_TEXT_P(new_t);
}

PG_FUNCTION_INFO_V1(concat_text);

Datum
concat_text(PG_FUNCTION_ARGS)
{
    text  *arg1 = PG_GETARG_TEXT_P(0);
    text  *arg2 = PG_GETARG_TEXT_P(1);
    int32 new_text_size = VARSIZE(arg1) + VARSIZE(arg2) - VARHDRSZ;
    text *new_text = (text *) palloc(new_text_size);

    SET_VARSIZE(new_text, new_text_size);
    memcpy(VARDATA(new_text), VARDATA(arg1), VARSIZE(arg1) - VARHDRSZ);
    memcpy(VARDATA(new_text) + (VARSIZE(arg1) - VARHDRSZ),
           VARDATA(arg2), VARSIZE(arg2) - VARHDRSZ);
    PG_RETURN_TEXT_P(new_text);
}

这些函数的CREATE FUNCTION命令与版本 0 的等价形式相同。

乍看之下,版本 1 的编码约定似乎只是无谓的故弄玄虚。不过,它们确实提供了许多改进,因为宏可以隐藏不必要的细节。例如,在编码add_one_float8时,我们不再需要知道float8是传引用类型。另一个例子是,变长类型的GETARG宏允许更有效地取得toasted(压缩或外置)值。

版本-1 函数的一大改进是对空输入和结果的更好处理。宏PG_ARGISNULL(n)允许一个函数测试是否每一个输入为空值(当然,只需要在没有声明为strict的函数中这样做)。和PG_GETARG_xxx()宏一样,输入参数也是从零开始计数。注意应该在验证了一个参数不是空值之后才执行PG_GETARG_xxx()。要返回空值结果,应执行PG_RETURN_NULL(),它对严格的以及非严格的函数都有用。

新式接口中提供的其他选项是PG_GETARG_xxx()宏的两个变种。其中的第一种是PG_GETARG_xxx_COPY(),它确保返回的指定参数的拷贝可以被安全地写入(通常的宏有时会返回一个指向表中物理存储值的指针,不能写入该值。使用PG_GETARG_xxx_COPY()宏可以保证得到一个可写的结果)。第二种变种PG_GETARG_xxx_SLICE()宏有三个参数。第一个是函数参数的编号(如上文)。第二个和第三个是要被返回的段的偏移量和长度。偏移量从零开始计算,而负值的长度则表示要求返回该值的剩余部分。当大型值的存储类型为external时,这些宏提供了访问这些大型值的部分内容的更有效方法(列的存储类型可以使用ALTER TABLE tablename ALTER COLUMN colname SET STORAGE storagetype来指定。storagetypeplainexternalextended或者main)。

最后,版本-1 的函数调用约定可以返回集合结果(第 35.9.9 节)、实现触发器函数(第 36 章)和过程语言调用处理器(第 52 章)。版本-1 代码也比版本-0 更可移植,因为它不违反 C 标准中关于函数调用协议的限制。更多细节 可见源代码发布中的src/backend/utils/fmgr/README

35.9.5. 编写代码

在开始更高级的话题之前,我们应该讨论一下用于 PostgreSQL C 语言函数的编码规则。 虽然有可能把不是 C 编写的函数载入到 PostgreSQL中,但即使能够做到,通常也很困难, 因为其他语言(例如 C++、FORTRAN 或者 Pascal)通常不会遵循和 C 相同的调用约定。也就是说,其他语言不会以同样的方式在函数之间传递 参数以及返回值。由于这个原因,我们会假定你的 C 语言函数确实是用 C 编写的。

编写和构建 C 语言函数的基本规则如下:

  • 使用 pg_config --includedir-server 查出 PostgreSQL 服务器头文件在你的系统上(或你的用户将要运行的系统上)安装于何处。

  • 为了让你的代码能够被 PostgreSQL 动态装入,编译和链接时总是需要特殊的选项。关于如何在特定操作系统上完成这件事,详见第 35.9.6 节

  • 记得按照第 35.9.1 节中的说明,为你的共享库定义一个魔数块

  • 分配内存时,使用 PostgreSQL 提供的 pallocpfree, 而不是相应的 C 库函数 mallocfree。用 palloc 分配的内存会在每个事务结束时自动释放,从而避免内存泄漏。

  • 总是使用 memset 将结构体的所有字节清零(或者一开始就用 palloc0 来分配它们)。即使你给结构体的每个字段都赋了值,结构体中仍可能存在包含垃圾值的对齐填充字节(也就是结构体中的空洞)。如果不这么做,就很难支持哈希索引或哈希连接,因为那时你必须只挑出数据结构中真正有意义的位来计算哈希值。规划器有时也依赖按位相等来比较常量,因此如果逻辑上等价的值在按位上不相等,就可能得到不理想的规划结果。

  • PostgreSQL 的大多数内部类型都在 postgres.h 中声明,而函数管理器接口(PG_FUNCTION_ARGS 等)位于 fmgr.h 中,因此至少需要包含这两个文件。出于可移植性考虑,最好把 postgres.h 放在 最前面,先于任何其他系统或用户头文件。包含 postgres.h 时,也会顺带为你包含 elog.hpalloc.h

  • 目标文件中定义的符号名不能彼此冲突,也不能与 PostgreSQL 服务器可执行文件中定义的符号冲突。如果你收到这类错误消息,就必须重命名相关函数或变量。

35.9.6. 编译和链接动态装载函数 #

在你能够使用以 C 编写的 PostgreSQL 扩展函数之前, 必须以特殊方式对它们进行编译和链接,以生成一个可由服务器动态装载的文件。 更准确地说,需要创建一个共享库

若想了解本节未涵盖的信息,你应阅读操作系统的文档,特别是 C 编译器 cc 和链接编辑器 ld 的手册页。 此外,PostgreSQL 源代码在 contrib 目录中包含若干可用的示例。 不过,如果你依赖这些示例,就会使你的模块依赖于 PostgreSQL 源代码是否可用。

创建共享库通常与链接可执行文件类似:先把源文件编译为目标文件, 再把目标文件链接在一起。目标文件需要以位置无关代码PIC)形式生成。 从概念上讲,这意味着当它们被可执行文件装载时,可以放在内存中的任意位置。 (面向可执行文件的目标文件通常不会这样编译。) 链接共享库的命令中也包含一些特殊标志,用来把它与链接可执行文件的命令区分开来 (至少理论上如此,某些系统上的实际做法要丑陋得多)。

在下面的示例中,我们假定你的源代码位于文件 foo.c 中, 并将创建共享库 foo.so。除非另有说明, 中间目标文件名为 foo.o。共享库可以包含多个目标文件, 但这里我们只使用一个。

FreeBSD

生成 PIC 的编译器选项是 -fPIC。 创建共享库时使用的编译器选项是 -shared

gcc -fPIC -c foo.c
gcc -shared -o foo.so foo.o

这从 FreeBSD 3.0 版起适用。

HP-UX

生成 PIC 的系统编译器选项是 +z。 使用 GCC 时则是 -fPIC。 用于共享库的链接器选项是 -b。因此:

cc +z -c foo.c

或者:

gcc -fPIC -c foo.c

然后:

ld -b -o foo.sl foo.o

HP-UX 使用 .sl 作为共享库扩展名,这与大多数其他系统不同。

Linux

生成 PIC 的编译器选项是 -fPIC。 创建共享库的编译器选项是 -shared。完整示例如下:

cc -fPIC -c foo.c
cc -shared -o foo.so foo.o
OS X

下面是一个示例。它假定开发者工具已经安装。

cc -c foo.c
cc -bundle -flat_namespace -undefined suppress -o foo.so foo.o
NetBSD

生成 PIC 的编译器选项是 -fPIC。 对于 ELF 系统,使用带有 -shared 选项的编译器来链接共享库。 对于较早的非 ELF 系统,则使用 ld -Bshareable

gcc -fPIC -c foo.c
gcc -shared -o foo.so foo.o
OpenBSD

生成 PIC 的编译器选项是 -fPIC。 使用 ld -Bshareable 来链接共享库。

gcc -fPIC -c foo.c
ld -Bshareable -o foo.so foo.o
Solaris

生成 PIC 的编译器选项,对于 Sun 编译器是 -KPIC, 而 -fPIC 则用于 GCC。 链接共享库时,两种编译器都可以使用 -G,或者也可以改用 -shared,但这只适用于 GCC

cc -KPIC -c foo.c
cc -G -o foo.so foo.o

或者

gcc -fPIC -c foo.c
gcc -G -o foo.so foo.o
Tru64 UNIX

PIC 是默认值,因此编译命令就是常规的那条。链接时需要使用带特殊选项的ld

cc -c foo.c
ld -shared -expect_unresolved '*' -o foo.so foo.o

使用 GCC 代替系统编译器时过程相同;不需要特殊选项。

UnixWare

生成 PIC 的编译器选项,对于 SCO 编译器是 -K PIC,而 -fpic 则用于 GCC。 链接共享库时,编译器选项对于 SCO 编译器是 -G, 对于 GCC 则是 -shared

cc -K PIC -c foo.c
cc -G -o foo.so foo.o

或者

gcc -fpic -c foo.c
gcc -shared -o foo.so foo.o

提示

如果这些内容对你来说过于复杂,可以考虑使用 GNU Libtool, 它通过统一接口隐藏了平台差异。

生成的共享库文件随后就可以装载到 PostgreSQL 中。 在向 CREATE FUNCTION 命令指定文件名时, 必须给出共享库文件名,而不是中间目标文件名。 请注意,系统标准的共享库扩展名(通常是 .so.sl) 可以在 CREATE FUNCTION 命令中省略,并且通常也应省略,以获得最佳可移植性。

关于服务器期望在何处找到共享库文件,请回头参见 第 35.9.1 节

35.9.7. 复合类型参数

复合类型没有像 C 结构体那样的固定布局。复合类型的实例可能包含 空值字段。此外,继承层次中的复合类型可能具有和同一继承层次中 其他成员不同的字段。因此, PostgreSQL提供了函数接口 以便从 C 访问复合类型的字段。

假设我们想编写一个函数来回答如下查询:

SELECT name, c_overpaid(emp, 1500) AS overpaid
    FROM emp
    WHERE name = 'Bill' OR name = 'Sam';

使用版本 0 调用约定,我们可以将 c_overpaid 定义为:

#include "postgres.h"
#include "executor/executor.h"  /* for GetAttributeByName() */

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

bool
c_overpaid(HeapTupleHeader t, /* the current row of emp */
           int32 limit)
{
    bool isnull;
    int32 salary;

    salary = DatumGetInt32(GetAttributeByName(t, "salary", &isnull));
    if (isnull)
        return false;
    return salary > limit;
}

按照版本-1 编码,上面的函数写成这样:

#include "postgres.h"
#include "executor/executor.h"  /* for GetAttributeByName() */

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

PG_FUNCTION_INFO_V1(c_overpaid);

Datum
c_overpaid(PG_FUNCTION_ARGS)
{
    HeapTupleHeader  t = PG_GETARG_HEAPTUPLEHEADER(0);
    int32            limit = PG_GETARG_INT32(1);
    bool isnull;
    Datum salary;

    salary = GetAttributeByName(t, "salary", &isnull);
    if (isnull)
        PG_RETURN_BOOL(false);
    /* Alternatively, we might prefer to do PG_RETURN_NULL() for null salary. */

    PG_RETURN_BOOL(DatumGetInt32(salary) > limit);
}

GetAttributeByNamePostgreSQL 的一个系统函数,用于从指定行中取出属性。它有三个参数:传入函数的 HeapTupleHeader 类型参数、所需属性的名称,以及一个用于指示该属性是否为 null 的返回参数。GetAttributeByName 返回一个 Datum 值,你可以用适当的 DatumGetXXX() 宏把它转换为正确的数据类型。注意,如果 null 标志被设置,那么返回值本身没有意义;在尝试对结果做任何处理之前,务必先检查这个 null 标志。

也有GetAttributeByNum函数,它可以用目标属性 的列号而不是属性名来选择目标属性。

下面的命令声明 SQL 中的c_overpaid

CREATE FUNCTION c_overpaid(emp, integer) RETURNS boolean
    AS 'DIRECTORY/funcs', 'c_overpaid'
    LANGUAGE C STRICT;

注意我们用了STRICT,这样我们不需要检查输入参数是否 为 NULL。

35.9.8. 返回行(复合类型)

要从 C 语言函数中返回一行或一个复合类型值,可以使用一套特殊的 API, 它通过一组宏和函数隐藏了构造复合数据类型时的大部分复杂性。要使用这套 API,源文件中必须包含:

#include "funcapi.h"

构造复合数据值(下文简称元组)有两种方式: 一种是从 Datum 值数组构造,另一种是从 C 字符串数组构造,这些字符串会传给该元组各列数据类型的输入转换函数。 无论采用哪种方式,首先都需要获取或构造描述该元组结构的 TupleDesc。 处理 Datum 时,需要把 TupleDesc 传给 BlessTupleDesc,然后为每一行调用 heap_form_tuple。 处理 C 字符串时,则要把 TupleDesc 传给 TupleDescGetAttInMetadata,然后为每一行调用 BuildTupleFromCStrings。 对于返回元组集合的函数,这些准备步骤可以在第一次调用函数时一次性完成。

有一些辅助函数可以用来设置所需的 TupleDesc。在大多数返回复合值的函数中,推荐的做法是调用:

TypeFuncClass get_call_result_type(FunctionCallInfo fcinfo,
                                   Oid *resultTypeId,
                                   TupleDesc *resultTupleDesc)

传入调用函数本身收到的同一个 fcinfo 结构体(这当然要求使用版本 1 调用约定)。 resultTypeId 可以指定为 NULL,也可以指定为一个本地变量的地址,用于接收函数结果类型的 OID。 resultTupleDesc 应当是一个本地 TupleDesc 变量的地址。 检查返回结果是否为 TYPEFUNC_COMPOSITE;如果是,resultTupleDesc 就会被填入所需的 TupleDesc。 (如果不是,可以报告一个类似function returning record called in context that cannot accept type record的错误。)

提示

get_call_result_type能够解析一个多态函数结果的实际类型, 因此不仅在返回复合类型的函数中,在返回标量多态结果的函数中它也是非常 有用的。resultTypeId输出主要用于返回多态标量的函数。

注意

get_call_result_type有一个兄弟 get_expr_result_type,它被用来解析被表示为一棵表达式 树的函数调用的输出类型。在尝试从函数自身外部确定结果类型时可以用它。 也有一个get_func_result_type,当只有函数的 OID 可用时 可以用它。不过这些函数无法处理被声明为返回record的 函数,并且get_func_result_type无法解析多态类型,因此你 应该优先使用get_call_result_type

更早、现在已被弃用的获取TupleDesc的函数有:

TupleDesc RelationNameGetTupleDesc(const char *relname)

它可以为一个指定名称的关系的行类型得到TupleDesc, 还有:

TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases)

可以基于一个类型 OID 得到TupleDesc。这可以被用来 为一种基本或者复合类型获得TupleDesc。不过,对于 返回record的函数它不起作用,并且它无法解析多态类型。

一旦有了一个TupleDesc,如果计划处理 Datum,可以调用:

TupleDesc BlessTupleDesc(TupleDesc tupdesc)

如果计划处理 C 字符串,可调用:

AttInMetadata *TupleDescGetAttInMetadata(TupleDesc tupdesc)

如果正在编写一个返回集合的函数,你可以把这些函数的结果保存在 FuncCallContext结构体中 — 分别使用 tuple_desc或者attinmeta字段。

在处理 Datum 时,使用

HeapTuple heap_form_tuple(TupleDesc tupdesc, Datum *values, bool *isnull)

来用 Datum 形式的用户数据构建一个HeapTuple

在处理 C 字符串时,使用

HeapTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)

来用 C 字符串形式的用户数据构建一个HeapTuplevalues是一个 C 字符串数组,每一个元素是返回行 的一个属性。每一个 C 字符串应该是该属性数据类型的输入函数所期望 的格式。为了对一个属性返回空值,values数组中对 应的指针应该被设置为NULL。对于你返回的每一行都需要再次调用这个函数。

一旦已经构建了一个要从函数中返回的元组,它必须被转换成一个 Datum。使用

HeapTupleGetDatum(HeapTuple tuple)

可把一个HeapTuple转换成合法的 Datum。如果你 只想返回一行,那么这个Datum可以被直接返回,在一个 集合返回函数中它也可以被当做当前的返回值。

下一节中会有一个示例。

35.9.9. 返回集合 #

还有一种特殊的 API,为从 C 语言函数返回集合(多行)提供支持。集合返回函数必须遵循版本 1 调用约定。此外,与前述一样,源文件必须包含funcapi.h

集合返回函数(SRF)为它返回的每一项被调用一次。因此,SRF 必须保存足够的状态,以记住它先前在做什么,并在每次调用时返回下一项。结构体 FuncCallContext 就是用来帮助控制这一过程的。在函数内部, fcinfo->flinfo->fn_extra 用于在调用之间保存指向 FuncCallContext 的指针。

typedef struct
{
    /*
     * 本次调用以前已经被调用过多少次
     *
     * SRF_FIRSTCALL_INIT() 会为你把 call_cntr 初始化为 0,
     * 并且在每次调用 SRF_RETURN_NEXT() 时增加。
     */
    uint32 call_cntr;

    /*
     * 可选:最大调用次数
     *
     * 这里的 max_calls 只是为了方便,设置它是可选的。
     * 如果没有设置,你必须提供替代的方法来了解函数什么时候做完。
     */
    uint32 max_calls;

    /*
     * 可选:指向结果槽的指针
     *
     * 此字段已过时,仅为向后兼容而保留,即兼容使用已弃用的
     * TupleDescGetSlot() 的用户定义 SRF。
     */
    TupleTableSlot *slot;

    /*
     * 可选:指向用户提供的上下文信息的指针
     *
     * user_fctx 是一个指向你自己的数据的指针,它可用来在函数的多次
     * 调用之间保存任意的上下文信息。
     */
    void *user_fctx;

    /*
     * 可选:指向包含属性类型输入元数据的结构的指针
     *
     * attinmeta 被用在返回元组(即复合数据类型)时,在返回基础类型
     * 时不会使用。只有想用BuildTupleFromCStrings()创建返回元组时才需要它。
     */
    AttInMetadata *attinmeta;

    /*
     * 用于保存必须在多次调用间都存在的结构的内存上下文
     *
     * SRF_FIRSTCALL_INIT() 会为你设置 multi_call_memory_ctx,并且由
     * SRF_RETURN_DONE() 来清理。对于任何需要在 SRF 的多次调用间都
     * 存在的内存来说,它是最合适的内存上下文。
     */
    MemoryContext multi_call_memory_ctx;

    /*
     * 可选:指向包含元组描述的结构的指针
     *
     * tuple_desc 被用在返回元组(即复合数据类型)时,并且只有在用
     * heap_form_tuple() 而不是 BuildTupleFromCStrings() 构建元组时才需要它。
     * 注意这里存储的 TupleDesc 指针通常已经被先运行过 BlessTupleDesc()。
     */
    TupleDesc tuple_desc;

} FuncCallContext;

SRF 会使用若干自动操纵 FuncCallContext 结构体(并期望通过 fn_extra 找到它)的函数和宏。使用:

SRF_IS_FIRSTCALL()

来判断你的函数是第一次被调用还是后续调用。在第一次调用时(只在第一次调用时)使用:

SRF_FIRSTCALL_INIT()

来初始化 FuncCallContext。在每次函数调用时,包括第一次,使用:

SRF_PERCALL_SETUP()

来正确设置对 FuncCallContext 的使用,并清除上一次处理遗留的任何已返回数据。

如果你的函数有数据要返回,使用:

SRF_RETURN_NEXT(funcctx, result)

把它返回给调用者(result必须是类型Datum, 可以是一个单一值或者按上文所述准备好的元组)。最后,当函数完成了 数据返回后,可使用:

SRF_RETURN_DONE(funcctx)

来清理并且结束SRF

调用 SRF 时当前所处的内存上下文是一个瞬时上下文, 它会在两次调用之间被清空。这意味着,你不必对用 palloc 分配的所有东西都调用 pfree,因为它们反正会自动释放。 不过,如果你需要分配在多次调用之间持续存在的数据结构,就必须把它们放到别处。 对于任何需要一直存活到 SRF 运行结束的数据,multi_call_memory_ctx 所指向的内存上下文就是合适的位置。 在大多数情况下,这意味着你应当在做首次调用初始化时切换到 multi_call_memory_ctx

一个完整的伪代码示例:

Datum
my_set_returning_function(PG_FUNCTION_ARGS)
{
    FuncCallContext  *funcctx;
    Datum             result;
    further declarations as needed

    if (SRF_IS_FIRSTCALL())
    {
        MemoryContext oldcontext;

        funcctx = SRF_FIRSTCALL_INIT();
        oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
        /* 这里是一次性设置代码: */
        user code
        if returning composite
            build TupleDesc, and perhaps AttInMetadata
        endif returning composite
        user code
        MemoryContextSwitchTo(oldcontext);
    }

    /* 这里是每一次都要做的设置代码: */
    user code
    funcctx = SRF_PERCALL_SETUP();
    user code

    /* 这里只是一种测试是否执行完的方法: */
    if (funcctx->call_cntr < funcctx->max_calls)
    {
        /* 这里返回另一个项: */
        user code
        obtain result Datum
        SRF_RETURN_NEXT(funcctx, result);
    }
    else
    {
        /* 这里已经完成了项的返回,只需要做清理: */
        user code
        SRF_RETURN_DONE(funcctx);
    }
}

一个返回复合类型的简单SRF的完整示例如下:

PG_FUNCTION_INFO_V1(retcomposite);

Datum
retcomposite(PG_FUNCTION_ARGS)
{
    FuncCallContext     *funcctx;
    int                  call_cntr;
    int                  max_calls;
    TupleDesc            tupdesc;
    AttInMetadata       *attinmeta;

    /* 仅在函数的第一次调用时执行的操作 */
    if (SRF_IS_FIRSTCALL())
    {
        MemoryContext   oldcontext;

        /* 为跨调用持久性创建函数上下文 */
        funcctx = SRF_FIRSTCALL_INIT();

        /* 切换到适合多次函数调用的内存上下文 */
        oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

        /* 要返回的元组总数 */
        funcctx->max_calls = PG_GETARG_UINT32(0);

        /* 为我们的结果类型构建一个元组描述符 */
        if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
            ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                     errmsg("function returning record called in context "
                            "that cannot accept type record")));

        /*
         * 生成后续从原始C字符串生成元组所需的属性元数据
         */
        attinmeta = TupleDescGetAttInMetadata(tupdesc);
        funcctx->attinmeta = attinmeta;

        MemoryContextSwitchTo(oldcontext);
    }

    /* 每次函数调用时执行的操作 */
    funcctx = SRF_PERCALL_SETUP();

    call_cntr = funcctx->call_cntr;
    max_calls = funcctx->max_calls;
    attinmeta = funcctx->attinmeta;

    if (call_cntr < max_calls)    /* 当还有更多要发送时执行 */
    {
        char       **values;
        HeapTuple    tuple;
        Datum        result;

        /*
         * 为构建返回的元组准备一个值数组。
         * 这应该是一个由后续类型输入函数处理的C字符串数组。
         */
        values = (char **) palloc(3 * sizeof(char *));
        values[0] = (char *) palloc(16 * sizeof(char));
        values[1] = (char *) palloc(16 * sizeof(char));
        values[2] = (char *) palloc(16 * sizeof(char));

        snprintf(values[0], 16, "%d", 1 * PG_GETARG_INT32(1));
        snprintf(values[1], 16, "%d", 2 * PG_GETARG_INT32(1));
        snprintf(values[2], 16, "%d", 3 * PG_GETARG_INT32(1));

        /* 构建一个元组 */
        tuple = BuildTupleFromCStrings(attinmeta, values);

        /* 将元组转换为 datum */
        result = HeapTupleGetDatum(tuple);

        /* 清理(这实际上并不是必要的) */
        pfree(values[0]);
        pfree(values[1]);
        pfree(values[2]);
        pfree(values);

        SRF_RETURN_NEXT(funcctx, result);
    }
    else    /* 当没有更多要发送时执行 */
    {
        SRF_RETURN_DONE(funcctx);
    }
}

在SQL中声明此函数的一种方法是:

CREATE TYPE __retcomposite AS (f1 integer, f2 integer, f3 integer);

CREATE OR REPLACE FUNCTION retcomposite(integer, integer)
    RETURNS SETOF __retcomposite
    AS 'filename', 'retcomposite'
    LANGUAGE C IMMUTABLE STRICT;

另一种方法是使用OUT参数:

CREATE OR REPLACE FUNCTION retcomposite(IN integer, IN integer,
    OUT f1 integer, OUT f2 integer, OUT f3 integer)
    RETURNS SETOF record
    AS 'filename', 'retcomposite'
    LANGUAGE C IMMUTABLE STRICT;

请注意,采用这种方法时,函数的输出类型在形式上是一个匿名的 record 类型。

源码发行版中的contrib/tablefunc模块包含更多集合返回函数的示例。

35.9.10. 多态参数和返回类型

可以声明 C 语言函数来接受和返回多态类型 anyelementanyarrayanynonarrayanyenumanyrange。关于多态函数的更详细解释,见 第 35.2.5 节。当函数参数或者返回 类型被定义为多态类型时,函数的编写者无法提前知道会用什么数据类型 调用该函数或者该函数需要返回什么数据类型。在fmgr.h 中提供了两种例程来允许版本-1 的 C 函数发现其参数的实际数据类型以及 它要返回的类型。这些例程被称为 get_fn_expr_rettype(FmgrInfo *flinfo)get_fn_expr_argtype(FmgrInfo *flinfo, int argnum)。它们 返回结果或者参数的类型的 OID,或者当该信息不可用时返回 InvalidOid。结构体flinfo通常被当做 fcinfo->flinfo访问。参数argnum则是从零 开始计。get_call_result_type也可被用作 get_fn_expr_rettype的一种替代品。还有 get_fn_expr_variadic,它可以被用来找出可变参数 是否已经被合并到了一个数组中。这主要用于 VARIADIC "any"函数,因为对于接收普通数组类型的 可变参数函数来说总是会发生这类合并。

例如,假设我们想要写一个接收一个任意类型元素并且返回一个该类型的一维 数组的函数:

PG_FUNCTION_INFO_V1(make_array);
Datum
make_array(PG_FUNCTION_ARGS)
{
    ArrayType  *result;
    Oid         element_type = get_fn_expr_argtype(fcinfo->flinfo, 0);
    Datum       element;
    bool        isnull;
    int16       typlen;
    bool        typbyval;
    char        typalign;
    int         ndims;
    int         dims[MAXDIM];
    int         lbs[MAXDIM];

    if (!OidIsValid(element_type))
        elog(ERROR, "could not determine data type of input");

    /* 得到提供的元素,小心它为 NULL 的情况 */
    isnull = PG_ARGISNULL(0);
    if (isnull)
        element = (Datum) 0;
    else
        element = PG_GETARG_DATUM(0);

    /* 只有一个维度 */
    ndims = 1;
    /* 和一个元素 */
    dims[0] = 1;
    /* 且下界是 1 */
    lbs[0] = 1;

    /* 得到该元素类型所需的信息 */
    get_typlenbyvalalign(element_type, &typlen, &typbyval, &typalign);

    /* 现在构建数组 */
    result = construct_md_array(&element, &isnull, ndims, dims, lbs,
                                element_type, typlen, typbyval, typalign);

    PG_RETURN_ARRAYTYPE_P(result);
}

下面的命令在 SQL 中声明了 make_array 函数:

CREATE FUNCTION make_array(anyelement) RETURNS anyarray
    AS 'DIRECTORY/funcs', 'make_array'
    LANGUAGE C IMMUTABLE;

还有一种只对 C 语言函数可用的多态变体:它们可以被声明为接受 "any" 类型的参数。(注意,这个类型名必须用双引号括起来,因为它同时也是 SQL 保留字。) 它与 anyelement 类似,但不会要求不同的 "any" 参数必须是同一种类型,也不会帮助确定函数的结果类型。 C 语言函数还可以把最后一个参数声明为 VARIADIC "any"。 这可以匹配一个或多个任意类型的实参(不必是同一种类型)。 这些参数不会像普通可变参数函数那样被收集成一个数组,而是会单独传给函数。 使用这种特性时,必须结合 PG_NARGS() 宏以及前面介绍的方法来确定实参的个数和类型。 此外,这种函数的用户也可能希望在函数调用中使用 VARIADIC 关键字,以便让函数把数组元素当作独立参数处理。 如果希望支持这种行为,函数本身就必须在使用 get_fn_expr_variadic 检测到实参被标记为 VARIADIC 后自行实现它。

35.9.11. 转换函数 #

某些函数调用可以在规划期间根据函数特有的属性进行简化。例如,int4mul(n, 1) 可以直接简化为 n。要定义这类针对特定函数的优化,请编写一个转换函数,并将其 OID 放入主函数的 protransform 字段中,该字段位于其 pg_proc 项中。转换函数必须具有 SQL 签名 protransform(internal) RETURNS internal。参数实际上是 FuncExpr *,它是一个表示主函数调用的占位节点。如果转换函数对表达式树的分析证明,可以用简化的表达式树替代它所表示的所有可能的具体调用,就构建并返回该简化表达式。否则,返回一个 NULL 指针(不是 SQL 空值)。

我们不保证 PostgreSQL 在转换函数可以简化的情况下绝不会调用主函数。请确保简化后的表达式与实际调用主函数严格等价。

目前,出于安全考虑,这项功能没有在 SQL 层面开放给用户,因此实际只能用于优化内置函数。

35.9.12. 共享内存与 LWLocks

附加模块可以在服务器启动时预留 LWLock 和一块共享内存。附加模块的共享库必须通过在以下参数中指定它来预装载:shared_preload_libraries。共享内存可以通过下面的调用来预留:

void RequestAddinShmemSpace(int size)

该调用应放在你的 _PG_init 函数中。

LWLock 可以通过下面的调用来预留:

void RequestAddinLWLocks(int n)

该调用应放在 _PG_init 中。

为避免可能的竞争条件,每个后端都应使用 LWLock AddinShmemInitLock 来连接和初始化分配给它的共享内存,如下所示:

static mystruct *ptr = NULL;

if (!ptr)
{
        bool    found;

        LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
        ptr = ShmemInitStruct("my struct name", size, &found);
        if (!found)
        {
                initialize contents of shmem area;
                acquire any requested LWLocks using:
                ptr->mylockid = LWLockAssign();
        }
        LWLockRelease(AddinShmemInitLock);
}

35.9.13. 把 C++ 用于可扩展性 #

尽管PostgreSQL后端是用 C 编写的, 只要遵循下面的指导方针也可以用 C++ 编写扩展:

  • 所有被后端访问的函数必须对后端呈现一种 C 接口,然后这些 C 函数 调用 C++ 函数。例如,对后端访问的函数要求extern C 链接。对需要在后端和 C++ 代码之间作为指针传递的任何函数也要 这样做。

  • 使用合适的释放方法释放内存。例如,大部分后端内存是通过 palloc()分配的,所以应使用pfree() 来释放。在这种情况中使用 C++ 的delete会失败。

  • 防止异常传播到 C 代码中(在所有extern C函数的顶层 使用一个捕捉全部异常的块)。即使 C++ 代码不会显式地抛出任何 异常也需要这样做,因为类似内存不足等事件仍会抛出异常。任何异常 都必须被捕捉并且用适当的错误传回给 C 接口。如果可能,用 -fno-exceptions 来编译 C++ 以完全消灭异常。在这种 情况下,你必须在 C++ 代码中检查失败,例如检查new() 返回的 NULL。

  • 如果从 C++ 代码调用后端函数,确定 C++ 调用栈只包含传统 C 风格 的数据结构(POD)。这是必要的,因为后端错误会 产生远距离的longjmp(),它无法正确地展开具有非 POD 对象的 C++ 调用栈。

总之,最好把 C++ 代码放在与后端交互的extern C函数之后, 并且避免异常、内存和调用栈泄露。

提交更正

译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。