perlguts - Perl API 简介
本文档试图描述如何使用 Perl API,以及提供一些关于 Perl 核心基本工作原理的信息。它远未完善,可能包含许多错误。请将任何问题或意见反馈给下面的作者。
Perl 有三个类型定义,用于处理 Perl 的三种主要数据类型
SV Scalar Value
AV Array Value
HV Hash Value
每个类型定义都有特定的例程来操作各种数据类型。
Perl 使用一个特殊的类型定义 IV,它是一个简单的带符号整数类型,保证足够大以容纳指针(以及整数)。此外,还有 UV,它只是一个无符号 IV。
Perl 还使用几个特殊的类型定义来声明变量以保存(至少)给定大小的整数。使用 I8、I16、I32 和 I64 来声明一个带符号整数变量,其位数至少与名称中的数字一样多。它们都评估为最接近给定位数的本机 C 类型,但不会小于该数字。例如,在许多平台上,short
是 16 位长,如果是这样,I16 将评估为 short
。但在 short
不完全是 16 位的平台上,Perl 将使用包含 16 位或更多位的最小类型。
U8、U16、U32 和 U64 用于声明相应的无符号整数类型。
如果平台不支持 64 位整数,则 I64 和 U64 将未定义。使用 IV 和 UV 声明最大可行值,并使用 "perlapi 中的 WIDEST_UTYPE"
声明绝对最大无符号值,但该值可能并非在所有情况下都可用。
可以使用 "perlapi 中的 INT16_C"、"perlapi 中的 UINTMAX_C" 等指定数值常量。
可以使用一条命令创建并加载 SV。可以加载五种类型的值:整数值 (IV)、无符号整数值 (UV)、双精度浮点数 (NV)、字符串 (PV) 和另一个标量 (SV)。("PV" 代表 "指针值"。您可能认为它命名错误,因为它被描述为仅指向字符串。但是,它可以指向其他内容。例如,它可以指向 UV 数组。但是,将其用于非字符串需要谨慎,因为内部机制的大部分基本假设是 PV 仅用于字符串。例如,通常会自动附加尾随 NUL。非字符串用法仅在本段中记录。)
七个例程是
SV* newSViv(IV);
SV* newSVuv(UV);
SV* newSVnv(double);
SV* newSVpv(const char*, STRLEN);
SV* newSVpvn(const char*, STRLEN);
SV* newSVpvf(const char*, ...);
SV* newSVsv(SV*);
STRLEN
是一种整数类型 (Size_t
,通常在 config.h 中定义为 size_t
),保证足够大以表示 Perl 可以处理的任何字符串的大小。
在 SV 需要更复杂初始化的极少数情况下,您可以使用 newSV(len) 创建一个空 SV。如果 len
为 0,则返回类型为 NULL 的空 SV,否则返回类型为 PV 的 SV,并分配 len + 1 (用于 NUL) 字节的存储空间,可以通过 SvPVX 访问。在这两种情况下,SV 都具有 undef 值。
SV *sv = newSV(0); /* no storage allocated */
SV *sv = newSV(10); /* 10 (+1) bytes of uninitialised storage
* allocated */
要更改已存在 SV 的值,有八个例程
void sv_setiv(SV*, IV);
void sv_setuv(SV*, UV);
void sv_setnv(SV*, double);
void sv_setpv(SV*, const char*);
void sv_setpvn(SV*, const char*, STRLEN)
void sv_setpvf(SV*, const char*, ...);
void sv_vsetpvfn(SV*, const char*, STRLEN, va_list *,
SV **, Size_t, bool *);
void sv_setsv(SV*, SV*);
请注意,您可以选择使用 sv_setpvn
、newSVpvn
或 newSVpv
指定要分配的字符串的长度,或者您可以让 Perl 使用 sv_setpv
计算长度,或者将 0 指定为 newSVpv
的第二个参数。但是,请注意,Perl 将使用 strlen
确定字符串的长度,这取决于字符串以 NUL 字符结尾,并且不包含其他 NUL。
sv_setpvf
的参数像 sprintf
一样处理,格式化的输出成为值。
sv_vsetpvfn
是 vsprintf
的类似物,但它允许您指定指向可变参数列表的指针或 SV 数组的地址和长度。最后一个参数指向一个布尔值;在返回时,如果该布尔值为真,则已使用特定于区域设置的信息来格式化字符串,因此字符串的内容不可信(请参阅 perlsec)。如果该信息不重要,则该指针可以为 NULL。请注意,此函数要求您指定格式的长度。
sv_set*()
函数不够通用,无法操作具有“魔法”的值。有关详细信息,请参阅本文档后面的 “魔法虚拟表” 部分。
所有包含字符串的 SV 应该以 NUL
字符结尾。如果字符串没有以 NUL
字符结尾,则可能会导致核心转储和损坏,因为代码会将字符串传递给期望 NUL
字符结尾字符串的 C 函数或系统调用。出于这个原因,Perl 的自身函数通常会添加一个尾部的 NUL
字符。但是,在将存储在 SV 中的字符串传递给 C 函数或系统调用时,您应该非常小心。
为了访问 SV 指向的实际值,Perl 的 API 公开了几个宏,这些宏可以将实际的标量类型强制转换为 IV、UV、double 或字符串。
SvIV(SV*)
(IV
) 和 SvUV(SV*)
(UV
)
SvNV(SV*)
(double
)
字符串有点复杂
字节字符串:SvPVbyte(SV*, STRLEN len)
或 SvPVbyte_nolen(SV*)
如果 Perl 字符串是 "\xff\xff"
,则此宏返回一个 2 字节的 char*
。
这适用于表示字节的 Perl 字符串。
UTF-8 字符串:SvPVutf8(SV*, STRLEN len)
或 SvPVutf8_nolen(SV*)
如果 Perl 字符串是 "\xff\xff"
,则此宏返回一个 4 字节的 char*
。
这适用于表示字符的 Perl 字符串。
警告:该 char*
将使用 Perl 的内部 UTF-8 变体进行编码,这意味着如果 SV 包含非 Unicode 代码点(例如,0x110000),则结果可能包含超出有效 UTF-8 的扩展。有关 Perl 提供的一些用于检查这些宏返回值的 UTF-8 有效性的方法,请参阅 perlapi 中的“is_strict_utf8_string”。
您还可以使用 SvPV(SV*, STRLEN len)
或 SvPV_nolen(SV*)
来获取 SV 的原始内部缓冲区。但这很棘手;如果您的 Perl 字符串是 "\xff\xff"
,则根据 SV 的内部编码,您可能会得到一个 2 字节 或 4 字节的 char*
。此外,如果它是 4 字节字符串,则它可能来自以 UTF-8 编码存储的 Perl "\xff\xff"
,也可能来自以原始字节存储的 Perl "\xc3\xbf\xc3\xbf"
。为了区分它们,您必须查找 SV 的 UTF8 位(参见 SvUTF8
)以了解源 Perl 字符串是 2 个字符(SvUTF8
将打开)还是 4 个字符(SvUTF8
将关闭)。
重要提示:如果允许非 ASCII 输入,则在不查找 SV 的 UTF8 位的情况下使用 SvPV
、SvPV_nolen
或类似命名的宏几乎肯定是一个错误。
当 UTF8 位打开时,与 SvPVutf8
相同的警告关于 UTF-8 有效性也适用于此。
(有关更多详细信息,请参阅 “如何将 Perl 字符串传递给 C 库?”。)
在 SvPVbyte
、SvPVutf8
和 SvPV
中,返回的 char*
的长度将被放置到变量 len
中(这些是宏,因此您不使用 &len
)。如果您不关心数据的长度,请改用 SvPVbyte_nolen
、SvPVutf8_nolen
或 SvPV_nolen
。在这种情况下,全局变量 PL_na
也可以传递给 SvPVbyte
/SvPVutf8
/SvPV
。但这可能非常低效,因为 PL_na
必须在多线程 Perl 中的线程局部存储中访问。无论如何,请记住 Perl 允许任意数据字符串,这些字符串可能同时包含 NUL 并可能不会以 NUL
结尾。
还要记住,C 不允许您安全地说 foo(SvPVbyte(s, len), len);
。它可能在您的编译器上工作,但不会对所有人都有效。将这种语句分解成单独的赋值。
SV *s;
STRLEN len;
char *ptr;
ptr = SvPVbyte(s, len);
foo(ptr, len);
如果您想知道标量值是否为 TRUE,您可以使用
SvTRUE(SV*)
虽然 Perl 会自动为您扩展字符串,但如果您需要强制 Perl 为您的 SV 分配更多内存,您可以使用宏
SvGROW(SV*, STRLEN newlen)
它将确定是否需要分配更多内存。如果是,它将调用函数 sv_grow
。请注意,SvGROW
只能增加,不能减少 SV 的已分配内存,并且它不会自动为尾随的 NUL
字节添加空间(perl 自己的字符串函数通常会执行 SvGROW(sv, len + 1)
)。
如果您想写入现有 SV 的缓冲区并将它的值设置为字符串,请使用 SvPVbyte_force() 或它的变体之一来强制 SV 成为 PV。这将从 SV 中删除各种类型的非字符串性,同时保留 SV 在 PV 中的内容。例如,这可用于将来自 API 函数的数据追加到缓冲区,而无需额外的复制。
(void)SvPVbyte_force(sv, len);
s = SvGROW(sv, len + needlen + 1);
/* something that modifies up to needlen bytes at s+len, but
modifies newlen bytes
eg. newlen = read(fd, s + len, needlen);
ignoring errors for these examples
*/
s[len + newlen] = '\0';
SvCUR_set(sv, len + newlen);
SvUTF8_off(sv);
SvSETMAGIC(sv);
如果您已经将数据存储在内存中,或者您希望代码保持简单,您可以使用 sv_cat*() 变体之一,例如 sv_catpvn()。如果您想在字符串中的任何位置插入数据,您可以使用 sv_insert() 或 sv_insert_flags()。
如果您不需要 SV 的现有内容,您可以使用以下方法避免一些复制操作:
SvPVCLEAR(sv);
s = SvGROW(sv, needlen + 1);
/* something that modifies up to needlen bytes at s, but modifies
newlen bytes
eg. newlen = read(fd, s, needlen);
*/
s[newlen] = '\0';
SvCUR_set(sv, newlen);
SvPOK_only(sv); /* also clears SVf_UTF8 */
SvSETMAGIC(sv);
同样,如果您已经将数据存储在内存中,或者您希望避免上述复杂性,您可以使用 sv_setpvn()。
如果您有一个使用 Newx() 分配的缓冲区,并且希望将其设置为 SV 的值,您可以使用 sv_usepvn_flags()。如果您想避免 perl 重新分配缓冲区以适应尾随的 NUL,则有一些要求。
Newx(buf, somesize+1, char);
/* ... fill in buf ... */
buf[somesize] = '\0';
sv_usepvn_flags(sv, buf, somesize, SV_SMAGIC | SV_HAS_TRAILING_NUL);
/* buf now belongs to perl, don't release it */
如果您有一个 SV 并且想知道 Perl 认为它存储了哪种类型的数据,您可以使用以下宏来检查您拥有的 SV 的类型。
SvIOK(SV*)
SvNOK(SV*)
SvPOK(SV*)
请注意,检索 SV 的数值可能会在该 SV 上设置 IOK 或 NOK,即使 SV 最初是字符串。在 Perl 5.36.0 之前,检索整数的字符串值可能会设置 POK,但现在不再发生。从 5.36.0 开始,这可以用来区分 SV 的原始表示,旨在简化序列化器的使用。
/* references handled elsewhere */
if (SvIsBOOL(sv)) {
/* originally boolean */
...
}
else if (SvPOK(sv)) {
/* originally a string */
...
}
else if (SvNIOK(sv)) {
/* originally numeric */
...
}
else {
/* something special or undef */
}
您可以使用以下宏获取和设置存储在 SV 中的字符串的当前长度。
SvCUR(SV*)
SvCUR_set(SV*, I32 val)
您还可以使用宏获取指向存储在 SV 中的字符串末尾的指针。
SvEND(SV*)
但请注意,这最后三个宏只有在 SvPOK()
为真时才有效。
如果您想将某些内容追加到存储在 SV*
中的字符串末尾,您可以使用以下函数。
void sv_catpv(SV*, const char*);
void sv_catpvn(SV*, const char*, STRLEN);
void sv_catpvf(SV*, const char*, ...);
void sv_vcatpvfn(SV*, const char*, STRLEN, va_list *, SV **,
I32, bool);
void sv_catsv(SV*, SV*);
第一个函数使用 strlen
计算要追加的字符串的长度。在第二个函数中,您自己指定字符串的长度。第三个函数像 sprintf
一样处理其参数,并将格式化的输出追加到末尾。第四个函数的工作方式类似于 vsprintf
。您可以指定 SV 数组的地址和长度,而不是 va_list 参数。第五个函数将存储在第一个 SV 中的字符串扩展为存储在第二个 SV 中的字符串。它还强制第二个 SV 被解释为字符串。
sv_cat*()
函数不够通用,无法对具有“魔法”的值进行操作。请参阅本文档后面的 "魔法虚拟表"。
如果您知道标量变量的名称,您可以使用以下方法获取指向其 SV 的指针。
SV* get_sv("package::varname", 0);
如果变量不存在,则返回 NULL。
如果您想知道此变量(或任何其他 SV)是否实际上是 defined
,您可以调用。
SvOK(SV*)
标量 undef
值存储在名为 PL_sv_undef
的 SV 实例中。
无论何时需要 SV*
,都可以使用它的地址。请确保您不要尝试将随机 sv 与 &PL_sv_undef
进行比较。例如,在与 Perl 代码交互时,它将对以下情况正常工作。
foo(undef);
但以下情况将无法正常工作。
$x = undef;
foo($x);
因此,请始终使用 SvOK() 来检查 sv 是否已定义。
此外,在 AV 或 HV 中使用 &PL_sv_undef
作为值时,您需要格外小心(请参阅 "AVs、HVs 和未定义的值")。
还有两个值 PL_sv_yes
和 PL_sv_no
,它们分别包含布尔值 TRUE 和 FALSE。与 PL_sv_undef
一样,它们的地址可以在需要 SV*
时使用。
不要误以为 (SV *) 0
与 &PL_sv_undef
相同。请看以下代码
SV* sv = (SV*) 0;
if (I-am-to-return-a-real-value) {
sv = sv_2mortal(newSViv(42));
}
sv_setsv(ST(0), sv);
这段代码试图在应该返回真实值时返回一个新的 SV(包含值 42),否则返回 undef。但实际上它返回了一个 NULL 指针,这将在以后导致段错误、总线错误或其他奇怪的结果。将第一行中的零更改为 &PL_sv_undef
,一切都会好起来。
要释放您创建的 SV,请调用 SvREFCNT_dec(SV*)
。通常情况下,此调用是不必要的(请参阅 "引用计数和死亡")。
Perl 提供了 sv_chop
函数来高效地从字符串开头删除字符;您向它提供一个 SV 和指向 PV 内部的某个位置的指针,它会丢弃指针之前的所有内容。效率来自于一个小技巧:sv_chop
并没有真正删除字符,而是设置了 OOK
(偏移量 OK)标志,以向其他函数发出信号,表明偏移量技巧正在生效,它将 PV 指针(称为 SvPVX
)向前移动被删除的字节数,并相应地调整 SvCUR
和 SvLEN
。(旧 PV 指针和新 PV 指针之间的部分空间用于存储被删除的字节数。)
因此,此时,我们分配的缓冲区的开头位于内存中的 SvPVX(sv) - SvIV(sv)
处,而 PV 指针指向此分配存储的中间位置。
这最好用例子来演示。通常情况下,写时复制会阻止替换运算符使用此技巧,但如果您能创建一个无法进行写时复制的字符串,您就可以看到它在起作用。在当前实现中,字符串缓冲区的最后一个字节用作写时复制引用计数。如果缓冲区不够大,则会跳过写时复制。首先看看一个空字符串
% ./perl -Ilib -MDevel::Peek -le '$a=""; $a .= ""; Dump $a'
SV = PV(0x7ffb7c008a70) at 0x7ffb7c030390
REFCNT = 1
FLAGS = (POK,pPOK)
PV = 0x7ffb7bc05b50 ""\0
CUR = 0
LEN = 10
请注意这里的 LEN 为 10。(它可能在您的平台上有所不同。)将字符串的长度扩展到 10 减 1,并进行替换。
% ./perl -Ilib -MDevel::Peek -le '$a=""; $a.="123456789"; $a=~s/.//; \
Dump($a)'
SV = PV(0x7ffa04008a70) at 0x7ffa04030390
REFCNT = 1
FLAGS = (POK,OOK,pPOK)
OFFSET = 1
PV = 0x7ffa03c05b61 ( "\1" . ) "23456789"\0
CUR = 8
LEN = 9
这里,被截断的字节数 (1) 作为 OFFSET 显示在旁边。字符串中“真实”开头和“假”开头之间的部分用括号括起来,SvCUR
和 SvLEN
的值反映的是假开头,而不是真实开头。(字符串缓冲区的第一个字符恰好变为“\1”,而不是“1”,因为当前实现将偏移计数存储在字符串缓冲区中。这可能会发生变化。)
类似于偏移技巧,在 AV 上执行类似的操作,以实现高效地从数组开头进行移位和剪切;虽然 AvARRAY
指向从 Perl 可见的数组中的第一个元素,但 AvALLOC
指向 C 数组的真实开头。它们通常相同,但可以通过将 AvARRAY
增加 1 并减少 AvFILL
和 AvMAX
来执行 shift
操作。同样,C 数组真实开头的定位仅在释放数组时才会起作用。请参阅 av.c 中的 av_shift
。
回想一下,确定您拥有的标量类型的常用方法是使用 Sv*OK
宏。因为标量可以同时是数字和字符串,所以通常这些宏总是返回 TRUE,并且调用 Sv*V
宏将对字符串到整数/双精度数或整数/双精度数到字符串进行适当的转换。
如果您真的需要知道您的 SV 中是否包含整数、双精度数或字符串指针,您可以使用以下三个宏。
SvIOKp(SV*)
SvNOKp(SV*)
SvPOKp(SV*)
这些宏将告诉您您的 SV 中是否真正存储了整数、双精度数或字符串指针。“p”代表私有。
私有和公共标志可能存在多种差异。例如,在 Perl 5.16 及更早版本中,绑定 SV 可以在 IV 槽中具有有效的底层值(因此 SvIOKp 为真),但数据应通过 FETCH 例程访问,而不是直接访问,因此 SvIOK 为假。(在 Perl 5.18 及更高版本中,绑定标量使用与未绑定标量相同的方式使用标志。)另一种情况是发生数值转换并且精度丢失:只有私有标志在“有损”值上设置。因此,当 NV 转换为 IV 并且有损时,将设置 SvIOKp、SvNOKp 和 SvNOK,而 SvIOK 不会设置。
但是,一般来说,最好使用 Sv*V
宏。
创建和加载 AV 的两种主要方法已经存在很长时间。第一种方法创建空 AV
AV* newAV();
第二种方法既创建 AV 又用 SV 初始化它
AV* av_make(SSize_t num, SV **ptr);
第二个参数指向包含 num
个 SV*
的数组。创建 AV 后,可以根据需要销毁 SV。
Perl v5.36 添加了两种新的方法来创建 AV 并分配 SV** 数组,而无需填充它。这些方法比 newAV() 后跟 av_extend() 更有效。
/* Creates but does not initialize (Zero) the SV** array */
AV *av = newAV_alloc_x(1);
/* Creates and does initialize (Zero) the SV** array */
AV *av = newAV_alloc_xz(1);
数值参数指的是要分配的数组元素数量,而不是数组索引,并且必须大于 0。第一种形式只能在所有元素在任何读取发生之前初始化时使用。读取未初始化的 SV* - 即将随机内存地址视为 SV* - 是一个严重的错误。
创建 AV 后,可以在其上执行以下操作
void av_push(AV*, SV*);
SV* av_pop(AV*);
SV* av_shift(AV*);
void av_unshift(AV*, SSize_t num);
这些应该是熟悉的操作,除了 av_unshift
。此例程在数组的前面添加 num
个元素,其值为 undef
。然后,您必须使用 av_store
(如下所述)将值分配给这些新元素。
以下是一些其他函数
SSize_t av_top_index(AV*);
SV** av_fetch(AV*, SSize_t key, I32 lval);
SV** av_store(AV*, SSize_t key, SV* val);
av_top_index
函数返回数组中最高的索引值(就像 Perl 中的 $#array
一样)。如果数组为空,则返回 -1。av_fetch
函数返回索引 key
处的值,但如果 lval
非零,则 av_fetch
将在该索引处存储一个 undef 值。av_store
函数将值 val
存储在索引 key
处,并且不会增加 val
的引用计数。因此,调用者负责处理它,如果 av_store
返回 NULL,则调用者必须减少引用计数以避免内存泄漏。请注意,av_fetch
和 av_store
都返回 SV**
,而不是 SV*
作为它们的返回值。
更多
void av_clear(AV*);
void av_undef(AV*);
void av_extend(AV*, SSize_t key);
av_clear
函数会删除 AV* 数组中的所有元素,但不会实际删除数组本身。av_undef
函数会删除数组中的所有元素以及数组本身。av_extend
函数会扩展数组,使其至少包含 key+1
个元素。如果 key+1
小于当前分配的数组长度,则不会执行任何操作。
如果您知道数组变量的名称,可以使用以下方法获取指向其 AV 的指针
AV* get_av("package::varname", 0);
如果变量不存在,则返回 NULL。
有关如何在绑定数组上使用数组访问函数的更多信息,请参见 "理解绑定哈希和数组的魔力"。
Perl v5.36 和 v5.38 引入了某些函数的简化内联版本
av_store_simple
av_fetch_simple
av_push_simple
这些是直接替换,但只能用于满足以下条件的简单 AV
不是魔法的
不是只读的
是“真实”的(引用计数)AV
具有 av_top_index 值 > -2
使用 newAV()
、av_make
、newAV_alloc_x
和 newAV_alloc_xz
创建的 AV 在创建时都是兼容的。只有在它们被声明为只读或非真实,附加了魔法,或以其他不寻常的方式配置时,它们才会停止兼容。
请注意,某些解释器函数可能会将魔法附加到 AV,作为正常操作的一部分。因此,除非您确定 AV 的生命周期,否则最安全的方法是在靠近 AV 创建点的地方使用这些新函数。
要创建 HV,请使用以下例程
HV* newHV();
创建 HV 后,可以在其上执行以下操作
SV** hv_store(HV*, const char* key, U32 klen, SV* val, U32 hash);
SV** hv_fetch(HV*, const char* key, U32 klen, I32 lval);
klen
参数是传入键的长度(请注意,您不能将 0 作为 klen
的值传递给 Perl 以测量键的长度)。val
参数包含指向要存储的标量的 SV 指针,hash
是预先计算的哈希值(如果您希望 hv_store
为您计算,则为零)。lval
参数指示此获取是否实际上是存储操作的一部分,在这种情况下,将使用提供的键向 HV 添加一个新的未定义值,并且 hv_fetch
将返回,就好像该值已经存在一样。
请记住,hv_store
和 hv_fetch
返回的是 SV**
,而不仅仅是 SV*
。要访问标量值,您必须先解引用返回值。但是,在解引用之前,您应该检查返回值是否为 NULL。
这两个函数中的第一个检查哈希表条目是否存在,第二个删除它。
bool hv_exists(HV*, const char* key, U32 klen);
SV* hv_delete(HV*, const char* key, U32 klen, I32 flags);
如果 flags
不包含 G_DISCARD
标志,则 hv_delete
将创建并返回已删除值的死亡副本。
以及更多杂项函数
void hv_clear(HV*);
void hv_undef(HV*);
与它们的 AV 对应物一样,hv_clear
删除哈希表中的所有条目,但实际上不删除哈希表本身。hv_undef
删除条目和哈希表本身。
Perl 将实际数据保存在一个 typedef 为 HE 的结构的链表中。这些结构包含实际的键和值指针(以及额外的管理开销)。键是字符串指针;值为 SV*
。但是,一旦您拥有 HE*
,要获取实际的键和值,请使用下面指定的例程。
I32 hv_iterinit(HV*);
/* Prepares starting point to traverse hash table */
HE* hv_iternext(HV*);
/* Get the next entry, and return a pointer to a
structure that has both the key and value */
char* hv_iterkey(HE* entry, I32* retlen);
/* Get the key from an HE structure and also return
the length of the key string */
SV* hv_iterval(HV*, HE* entry);
/* Return an SV pointer to the value of the HE
structure */
SV* hv_iternextsv(HV*, char** key, I32* retlen);
/* This convenience routine combines hv_iternext,
hv_iterkey, and hv_iterval. The key and retlen
arguments are return values for the key and its
length. The value is returned in the SV* argument */
如果您知道哈希变量的名称,则可以使用以下方法获取指向其 HV 的指针
HV* get_hv("package::varname", 0);
如果变量不存在,则返回 NULL。
哈希算法在 PERL_HASH
宏中定义
PERL_HASH(hash, key, klen)
此宏的具体实现因体系结构和 perl 版本而异,返回值可能因调用而异,因此该值仅在单个 perl 进程的持续时间内有效。
有关如何在绑定哈希上使用哈希访问函数的更多信息,请参见 "理解绑定哈希和数组的魔力"。
从版本 5.004 开始,还支持以下函数
HE* hv_fetch_ent (HV* tb, SV* key, I32 lval, U32 hash);
HE* hv_store_ent (HV* tb, SV* key, SV* val, U32 hash);
bool hv_exists_ent (HV* tb, SV* key, U32 hash);
SV* hv_delete_ent (HV* tb, SV* key, I32 flags, U32 hash);
SV* hv_iterkeysv (HE* entry);
请注意,这些函数采用 SV*
键,这简化了处理哈希结构的扩展代码的编写。这些函数还允许将 SV*
键传递给 tie
函数,而无需强制您将键转换为字符串(与之前的函数集不同)。
它们还返回和接受整个哈希条目 (HE*
),使其使用效率更高(因为特定字符串的哈希编号不必每次都重新计算)。有关详细说明,请参见 perlapi。
以下宏必须始终用于访问哈希条目的内容。请注意,这些宏的参数必须是简单变量,因为它们可能会被评估多次。有关这些宏的详细说明,请参见 perlapi。
HePV(HE* he, STRLEN len)
HeVAL(HE* he)
HeHASH(HE* he)
HeSVKEY(HE* he)
HeSVKEY_force(HE* he)
HeSVKEY_set(HE* he, SV* sv)
定义了这两个较低级别的宏,但仅在处理不是 SV*
的键时才使用
HeKEY(HE* he)
HeKLEN(HE* he)
请注意,hv_store
和 hv_store_ent
不会增加存储的 val
的引用计数,这是调用者的责任。如果这些函数返回 NULL 值,调用者通常必须减少 val
的引用计数以避免内存泄漏。
有时您必须在 AV 或 HV 中存储未定义的值。虽然这可能是一个罕见的情况,但它可能很棘手。这是因为您习惯于使用 &PL_sv_undef
如果您需要一个未定义的 SV。
例如,直觉告诉你这段 XS 代码
AV *av = newAV();
av_store( av, 0, &PL_sv_undef );
等同于这段 Perl 代码
my @av;
$av[0] = undef;
不幸的是,事实并非如此。在 Perl 5.18 及更早版本中,AV 使用 &PL_sv_undef
作为标记来指示数组元素尚未初始化。因此,对于上面的 Perl 代码,exists $av[0]
将为真,但对于 XS 代码生成的数组,则为假。在 Perl 5.20 中,存储 &PL_sv_undef
将创建一个只读元素,因为存储的是标量 &PL_sv_undef
本身,而不是副本。
在 HV 中存储 &PL_sv_undef
时也会出现类似的问题
hv_store( hv, "key", 3, &PL_sv_undef, 0 );
这确实会使值变为 undef
,但如果你尝试修改 key
的值,你会收到以下错误
Modification of non-creatable hash value attempted
在 Perl 5.8.0 中,&PL_sv_undef
也用于标记受限哈希中的占位符。这会导致在遍历哈希或使用 hv_exists
函数检查键时,这些哈希条目不会出现。
当你将 &PL_sv_yes
或 &PL_sv_no
存储到 AV 或 HV 中时,也会遇到类似的问题。尝试修改这些元素会给你以下错误
Modification of a read-only value attempted
长话短说,你可以将特殊变量 &PL_sv_undef
、&PL_sv_yes
和 &PL_sv_no
与 AV 和 HV 一起使用,但你必须确保知道自己在做什么。
通常,如果你想在 AV 或 HV 中存储一个未定义的值,你不应该使用 &PL_sv_undef
,而应该使用 newSV
函数创建一个新的未定义值,例如
av_store( av, 42, newSV(0) );
hv_store( hv, "foo", 3, newSV(0), 0 );
引用是一种特殊的标量类型,它指向其他数据类型(包括其他引用)。
要创建引用,请使用以下任一函数
SV* newRV_inc((SV*) thing);
SV* newRV_noinc((SV*) thing);
thing
参数可以是 SV*
、AV*
或 HV*
中的任何一个。这两个函数除了 newRV_inc
会增加 thing
的引用计数,而 newRV_noinc
不会之外,其他方面都相同。出于历史原因,newRV
是 newRV_inc
的同义词。
一旦你有了引用,你可以使用以下宏来解除引用
SvRV(SV*)
然后调用相应的例程,将返回的 SV*
转换为 AV*
或 HV*
,如果需要。
要确定一个 SV 是否是引用,可以使用以下宏
SvROK(SV*)
要发现引用所指值的类型,可以使用以下宏,然后检查返回值。
SvTYPE(SvRV(SV*))
将返回的最有用的类型是
SVt_PVAV Array
SVt_PVHV Hash
SVt_PVCV Code
SVt_PVGV Glob (possibly a file handle)
任何小于 SVt_PVAV 的数值返回值都将是某种形式的标量。
有关更多详细信息,请参阅 "perlapi 中的 svtype"。
引用也用于支持面向对象编程。在 Perl 的 OO 词汇表中,对象只是一个被祝福到包(或类)中的引用。一旦被祝福,程序员现在可以使用引用来访问类中的各种方法。
可以使用以下函数将引用祝福到包中
SV* sv_bless(SV* sv, HV* stash);
sv
参数必须是引用值。stash
参数指定引用将属于哪个类。有关将类名转换为存储区的更多信息,请参阅 "存储区和全局变量"。
/* 仍在建设中 */
以下函数将 rv 升级为引用(如果尚未升级)。为 rv 指向创建一个新的 SV。如果 classname
非空,则 SV 将被祝福到指定的类中。返回 SV。
SV* newSVrv(SV* rv, const char* classname);
以下三个函数将整数、无符号整数或双精度数复制到其引用为 rv
的 SV 中。如果 classname
非空,则 SV 将被祝福。
SV* sv_setref_iv(SV* rv, const char* classname, IV iv);
SV* sv_setref_uv(SV* rv, const char* classname, UV uv);
SV* sv_setref_nv(SV* rv, const char* classname, NV iv);
以下函数将指针值(*地址,而不是字符串!*)复制到其引用为 rv 的 SV 中。如果 classname
非空,则 SV 将被祝福。
SV* sv_setref_pv(SV* rv, const char* classname, void* pv);
以下函数将字符串复制到其引用为 rv
的 SV 中。将长度设置为 0 以让 Perl 计算字符串长度。如果 classname
非空,则 SV 将被祝福。
SV* sv_setref_pvn(SV* rv, const char* classname, char* pv,
STRLEN length);
以下函数测试 SV 是否被祝福到指定的类中。它不检查继承关系。
int sv_isa(SV* sv, const char* name);
以下函数测试 SV 是否是到祝福对象的引用。
int sv_isobject(SV* sv);
以下函数测试 SV 是否派生自指定的类。SV 可以是到祝福对象的引用,也可以是包含类名的字符串。这是实现 UNIVERSAL::isa
功能的函数。
bool sv_derived_from(SV* sv, const char* name);
要检查您是否拥有从特定类派生的对象,您需要编写
if (sv_isobject(sv) && sv_derived_from(sv, class)) { ... }
要使用 undef 值创建一个新的 Perl 变量,该变量可以从您的 Perl 脚本访问,请根据变量类型使用以下例程。
SV* get_sv("package::varname", GV_ADD);
AV* get_av("package::varname", GV_ADD);
HV* get_hv("package::varname", GV_ADD);
注意使用 GV_ADD 作为第二个参数。现在可以使用适合数据类型的例程设置新变量。
还有其他宏,其值可以与 GV_ADD
参数按位或运算以启用某些额外功能。这些位是
将变量标记为多次定义,从而防止
Name <varname> used only once: possible typo
警告。
发出警告
Had to create <varname> unexpectedly
如果变量在调用函数之前不存在。
如果您没有指定包名,则变量将在当前包中创建。
Perl 使用引用计数驱动的垃圾回收机制。SV、AV 或 HV(以下简称 xV)的生命周期从引用计数为 1 开始。如果 xV 的引用计数降至 0,则它将被销毁,其内存将可用于重用。在最基本的内部级别,引用计数可以使用以下宏进行操作
int SvREFCNT(SV* sv);
SV* SvREFCNT_inc(SV* sv);
void SvREFCNT_dec(SV* sv);
(对于可以将这些基本宏的全部通用性换取一些性能的情况,还有增量和减量宏的后缀版本。)
但是,程序员应该考虑引用的方式不是以裸引用计数为基础,而是以引用所有权为基础。对 xV 的引用可以由各种实体拥有:另一个 xV、Perl 解释器、XS 数据结构、正在运行的代码或动态范围。xV 通常不知道哪些实体拥有对它的引用;它只知道有多少个引用,即引用计数。
为了正确维护引用计数,必须跟踪 XS 代码正在操作的哪些引用。程序员应该始终知道引用来自哪里以及谁拥有它,并注意任何引用创建或销毁以及任何所有权转移。由于所有权在 xV 数据结构中没有明确表示,因此只有引用计数需要由代码实际维护,这意味着对所有权的这种理解实际上在代码中并不明显。例如,将引用所有权从一个所有者转移到另一个所有者不会改变引用计数,因此可以不使用任何实际代码来实现。(转移代码不会触碰被引用对象,但需要确保前一个所有者知道它不再拥有该引用,并且新所有者知道它现在拥有该引用。)
在 Perl 层级可见的 xV 不应该失去引用,从而被销毁。通常,一个对象只有在不再可见时才会失去引用,通常通过使它不可见的方式。例如,Perl 引用值 (RV) 拥有对其被引用者的引用,因此如果 RV 被覆盖,该引用就会被销毁,并且不再可达的被引用者可能会因此被销毁。
许多函数在其目的中包含某种引用操作。有时这在引用所有权方面有文档记录,有时则在引用计数的更改方面有文档记录(不太有用)。例如,newRV_inc() 函数的文档记录表明它创建了一个新的 RV(引用计数为 1)并递增了调用者提供的被引用者的引用计数。这最好理解为对被引用者创建了一个新的引用,该引用由创建的 RV 拥有,并将对 RV 的唯一引用的所有权返回给调用者。而 newRV_noinc() 函数则不会递增被引用者的引用计数,但 RV 仍然最终拥有对被引用者的引用。因此,隐含的是 newRV_noinc()
的调用者正在放弃对被引用者的引用,这在概念上使其成为一个更复杂的操作,即使它对数据结构的操作更少。
例如,假设您想从 XSUB 函数返回一个引用。在 XSUB 例程内部,您创建一个 SV,它最初只有一个引用,由 XSUB 例程拥有。在例程完成之前,需要处理掉这个引用,否则它会泄漏,阻止 SV 被销毁。因此,为了创建一个引用 SV 的 RV,最方便的方法是将 SV 传递给 newRV_noinc()
,它会消耗该引用。现在 XSUB 例程不再拥有对 SV 的引用,但确实拥有对 RV 的引用,而 RV 又拥有对 SV 的引用。然后,通过从 XSUB 返回 RV 的过程,将对 RV 的引用的所有权转移。
有一些可用的便利函数可以帮助销毁 xV。这些函数引入了“死亡”的概念。许多文档都提到 xV 本身是死亡的,但这是一种误导。实际上,是对 xV 的引用是死亡的,并且可能存在多个对单个 xV 的死亡引用。对一个引用的死亡意味着它由 temps 栈拥有,temps 栈是 Perl 的许多内部栈之一,它将在“稍后”销毁该引用。通常,“稍后”是指当前 Perl 语句的结束。但是,在动态作用域周围会变得更加复杂:可能存在多个死亡引用集同时存在,并且具有不同的死亡日期。在内部,确定何时销毁死亡 xV 引用的实际决定因素取决于两个宏:SAVETMPS 和 FREETMPS。有关这些宏的更多详细信息,请参见 perlcall 和 perlxs 以及下面的 "临时栈"。
致命引用主要用于放置在 perl 主栈上的 xV。栈对于引用跟踪来说是有问题的,因为它包含许多 xV 引用,但并不拥有这些引用:它们没有被计数。目前,由于 xV 在被栈引用时被销毁,导致了许多错误,因为栈的未计数引用不足以使 xV 保持存活。因此,当将(未计数)引用放在栈上时,确保存在一个指向相同 xV 的已计数引用,并且该引用至少与未计数引用持续时间一样长,这一点至关重要。但同样重要的是,该已计数引用在适当的时间被清理,并且不会过分延长 xV 的生命周期。对于存在致命引用来说,通常是满足此要求的最佳方式,尤其是在 xV 是专门为了放在栈上而创建的,否则将没有引用。
要创建致命引用,请使用以下函数
SV* sv_newmortal()
SV* sv_mortalcopy(SV*)
SV* sv_2mortal(SV*)
sv_newmortal()
创建一个 SV(具有未定义的值),其唯一引用是致命的。sv_mortalcopy()
创建一个 xV,其值为提供的 xV 的副本,其唯一引用是致命的。sv_2mortal()
将现有 xV 引用致命化:它将引用的所有权从调用者转移到临时栈。由于 sv_newmortal
没有为新的 SV 提供任何值,因此通常需要通过 sv_setpv
、sv_setiv
等方式为其提供值。
SV *tmp = sv_newmortal();
sv_setiv(tmp, an_integer);
由于这涉及多个 C 语句,因此通常会看到以下习惯用法
SV *tmp = sv_2mortal(newSViv(an_integer));
致命例程不仅仅用于 SV;可以通过将它们的地址(类型转换为 SV*
)传递给 sv_2mortal
或 sv_mortalcopy
例程来使 AV 和 HV 成为致命的。
Stash 是一个哈希,它包含在包中定义的所有变量。Stash 的每个键都是一个符号名称(由所有具有相同名称的不同类型的对象共享),哈希表中的每个值都是一个 GV(全局值)。这个 GV 反过来包含对该名称的各种对象的引用,包括(但不限于)以下内容
Scalar Value
Array Value
Hash Value
I/O Handle
Format
Subroutine
有一个名为 PL_defstash
的单一存储区,它包含存在于 main
包中的项目。要访问其他包中的项目,请将字符串 "::" 附加到包名。Foo
包中的项目位于 PL_defstash
中的 Foo::
存储区。Bar::Baz
包中的项目位于 Bar::
存储区中的 Baz::
存储区。
要获取特定包的存储区指针,请使用以下函数
HV* gv_stashpv(const char* name, I32 flags)
HV* gv_stashsv(SV*, I32 flags)
第一个函数接受一个字面字符串,第二个函数使用存储在 SV 中的字符串。请记住,存储区只是一个哈希表,因此您将获得一个 HV*
。如果 flags
标志设置为 GV_ADD,则会创建一个新包。
gv_stash*v
需要的名称是您要获取符号表的包的名称。默认包称为 main
。如果您有多个嵌套包,请将它们的名称传递给 gv_stash*v
,用 ::
分隔,就像 Perl 语言本身一样。
或者,如果您有一个被祝福的引用的 SV,您可以使用以下方法找出存储区指针
HV* SvSTASH(SvRV(SV*));
然后使用以下方法获取包名本身
char* HvNAME(HV* stash);
如果您需要祝福或重新祝福一个对象,您可以使用以下函数
SV* sv_bless(SV*, HV* stash)
其中第一个参数(一个 SV*
)必须是一个引用,第二个参数是一个存储区。返回的 SV*
现在可以像任何其他 SV 一样使用。
有关引用和祝福的更多信息,请参阅 perlref。
与 AV 和 HV 一样,IO 对象是另一种非标量 SV 类型,它可能包含输入和输出 PerlIO 对象或来自 opendir() 的 DIR *
。
您可以创建一个新的 IO 对象
IO* newIO();
与其他 SV 不同,新的 IO 对象会自动被祝福到 IO::File 类中。
IO 对象包含一个输入和输出 PerlIO 处理程序
PerlIO *IoIFP(IO *io);
PerlIO *IoOFP(IO *io);
通常,如果 IO 对象已在文件上打开,则始终存在输入处理程序,但只有在文件以输出方式打开时才存在输出处理程序。对于文件,如果两者都存在,它们将是相同的 PerlIO 对象。
为套接字和字符设备创建不同的输入和输出 PerlIO 对象。
IO 对象还包含与 Perl I/O 处理程序关联的其他数据
IV IoLINES(io); /* $. */
IV IoPAGE(io); /* $% */
IV IoPAGE_LEN(io); /* $= */
IV IoLINES_LEFT(io); /* $- */
char *IoTOP_NAME(io); /* $^ */
GV *IoTOP_GV(io); /* $^ */
char *IoFMT_NAME(io); /* $~ */
GV *IoFMT_GV(io); /* $~ */
char *IoBOTTOM_NAME(io);
GV *IoBOTTOM_GV(io);
char IoTYPE(io);
U8 IoFLAGS(io);
=for apidoc_sections $io_scn, $formats_section
=for apidoc_section $reports
=for apidoc Amh|IV|IoLINES|IO *io
=for apidoc Amh|IV|IoPAGE|IO *io
=for apidoc Amh|IV|IoPAGE_LEN|IO *io
=for apidoc Amh|IV|IoLINES_LEFT|IO *io
=for apidoc Amh|char *|IoTOP_NAME|IO *io
=for apidoc Amh|GV *|IoTOP_GV|IO *io
=for apidoc Amh|char *|IoFMT_NAME|IO *io
=for apidoc Amh|GV *|IoFMT_GV|IO *io
=for apidoc Amh|char *|IoBOTTOM_NAME|IO *io
=for apidoc Amh|GV *|IoBOTTOM_GV|IO *io
=for apidoc_section $io
=for apidoc Amh|char|IoTYPE|IO *io
=for apidoc Amh|U8|IoFLAGS|IO *io
其中大多数与 格式 相关。
IoFLAGs() 可能包含标志的组合,其中最有趣的是 IOf_FLUSH
($|
) 用于自动刷新和 IOf_UNTAINT
,可以使用 IO::Handle 的 untaint() 方法 设置。
IO 对象还可能包含一个目录处理程序
DIR *IoDIRP(io);
适用于 PerlDir_read() 等函数。
所有这些访问器宏都是左值,没有单独的 _set()
宏来修改 IO 对象的成员。
标量变量通常只包含一种类型的值,整数、双精度浮点数、指针或引用。Perl 会自动将存储类型中的实际标量数据转换为请求的类型。
一些标量变量包含多种类型的标量数据。例如,变量 $!
包含 errno
的数值或来自 strerror
或 sys_errlist[]
的字符串等效项。
要将多个数据值强制放入 SV 中,您必须执行两件事:使用 sv_set*v
例程添加额外的标量类型,然后设置一个标志,以便 Perl 相信它包含多种类型的数据。设置标志的四个宏是
SvIOK_on
SvNOK_on
SvPOK_on
SvROK_on
您必须使用哪个特定的宏取决于您首先调用的 sv_set*v
例程。这是因为每个 sv_set*v
例程只打开正在设置的特定数据类型的位,并关闭所有其他位。
例如,要创建一个名为“dberror”的新 Perl 变量,它包含数值和描述性字符串错误值,您可以使用以下代码
extern int dberror;
extern char *dberror_list;
SV* sv = get_sv("dberror", GV_ADD);
sv_setiv(sv, (IV) dberror);
sv_setpv(sv, dberror_list[dberror]);
SvIOK_on(sv);
如果 sv_setiv
和 sv_setpv
的顺序颠倒,则需要调用宏 SvPOK_on
而不是 SvIOK_on
。
在 Perl 5.16 及更早版本中,写时复制(参见下一节)与只读标量共享一个标志位。因此,在这些版本中测试 sv_setsv
等是否会引发“修改只读值”错误的唯一方法是
SvREADONLY(sv) && !SvIsCOW(sv)
在 Perl 5.18 及更高版本中,SvREADONLY 仅适用于只读变量,并且在 5.20 中,写时复制标量也可以是只读的,因此上述检查不正确。您只需要
SvREADONLY(sv)
如果您需要经常进行此检查,请定义您自己的宏,如下所示
#if PERL_VERSION >= 18
# define SvTRULYREADONLY(sv) SvREADONLY(sv)
#else
# define SvTRULYREADONLY(sv) (SvREADONLY(sv) && !SvIsCOW(sv))
#endif
Perl 对标量实现了写时复制 (COW) 机制,其中字符串副本不会在请求时立即创建,而是在其中一个或另一个标量发生变化时才延迟创建。这在很大程度上是透明的,但必须注意不要修改由多个 SV 共享的字符串缓冲区。
可以使用 SvIsCOW(sv)
测试 SV 是否正在使用写时复制。
可以通过调用 sv_force_normal(sv)
或 SvPV_force_nolen(sv)
强制 SV 创建其字符串缓冲区的副本。
如果要使 SV 放弃其字符串缓冲区,请使用 sv_force_normal_flags(sv, SV_COW_DROP_PV)
或简单地使用 sv_setsv(sv, NULL)
。
所有这些函数都会在只读标量上 croak(有关这些标量的更多信息,请参阅上一节)。
为了测试您的代码是否行为正确并且没有修改 COW 缓冲区,在支持 mmap(2)(即 Unix)的系统上,您可以使用 -Accflags=-DPERL_DEBUG_READONLY_COW
配置 perl,它会将缓冲区违规转换为崩溃。您会发现它非常慢,因此您可能希望跳过 perl 自己的测试。
[本节仍在建设中。请忽略此处的所有内容。不要寄信。所有未经允许的行为均被禁止。]
任何 SV 都可能是魔法的,也就是说,它具有普通 SV 不具备的特殊功能。这些功能存储在 SV 结构中,在一个 struct magic
的链表中,typedef 为 MAGIC
。
struct magic {
MAGIC* mg_moremagic;
MGVTBL* mg_virtual;
U16 mg_private;
char mg_type;
U8 mg_flags;
I32 mg_len;
SV* mg_obj;
char* mg_ptr;
};
请注意,这是截至补丁级别 0 的最新信息,可能会随时更改。
Perl 使用 sv_magic 函数将魔法添加到 SV 中
void sv_magic(SV* sv, SV* obj, int how, const char* name, I32 namlen);
sv
参数是指向将获得新魔法功能的 SV 的指针。
如果 sv
还没有魔法,Perl 会使用 SvUPGRADE
宏将 sv
转换为类型 SVt_PVMG
。然后,Perl 会通过在魔法功能链表的开头添加新的魔法来继续。任何相同类型的魔法的先前条目都会被删除。请注意,这可以被覆盖,并且同一个类型的多个魔法实例可以与一个 SV 关联。
name
和 namlen
参数用于将字符串与魔法关联,通常是变量的名称。namlen
存储在 mg_len
字段中,如果 name
非空,则根据 namlen
是否大于零或等于零,name
的 savepvn
副本或 name
本身将存储在 mg_ptr
字段中。作为特例,如果 (name && namlen == HEf_SVKEY)
,则假定 name
包含一个 SV*
并按原样存储,其 REFCNT 会增加。
sv_magic 函数使用 how
来确定应将哪个(如果有)预定义的“魔法虚拟表”分配给 mg_virtual
字段。请参阅下面的 “魔法虚拟表” 部分。how
参数也存储在 mg_type
字段中。how
的值应从 perl.h 中找到的 PERL_MAGIC_foo
宏集中选择。请注意,在添加这些宏之前,Perl 内部曾经直接使用字符字面量,因此您可能会偶尔遇到引用 'U' 魔法而不是 PERL_MAGIC_uvar
的旧代码或文档,例如。
obj
参数存储在 MAGIC
结构体的 mg_obj
字段中。如果它与 sv
参数不同,则 obj
对象的引用计数会增加。如果它们相同,或者 how
参数为 PERL_MAGIC_arylen
、PERL_MAGIC_regdatum
、PERL_MAGIC_regdata
,或者它是一个空指针,那么 obj
只是被存储,而不会增加引用计数。
另请参阅 perlapi 中的 sv_magicext
,了解为 SV 添加魔力的更灵活方法。
还有一个函数可以为 HV
添加魔力。
void hv_magic(HV *hv, GV *gv, int how);
它只是调用 sv_magic
并将 gv
参数强制转换为 SV
。
要从 SV 中删除魔力,请调用函数 sv_unmagic
。
int sv_unmagic(SV *sv, int type);
type
参数应该等于 SV
最初被赋予魔力时的 how
值。
但是,请注意 sv_unmagic
会从 SV
中删除所有特定 type
的魔力。如果你想根据魔力虚拟表只删除特定 type
的某些魔力,请使用 sv_unmagicext
代替。
int sv_unmagicext(SV *sv, int type, MGVTBL *vtbl);
MAGIC
结构体中的 mg_virtual
字段是指向 MGVTBL
的指针,MGVTBL
是一个函数指针结构体,代表“魔力虚拟表”,用于处理可能应用于该变量的各种操作。
MGVTBL
有五个(有时是八个)指向以下例程类型的指针
int (*svt_get) (pTHX_ SV* sv, MAGIC* mg);
int (*svt_set) (pTHX_ SV* sv, MAGIC* mg);
U32 (*svt_len) (pTHX_ SV* sv, MAGIC* mg);
int (*svt_clear)(pTHX_ SV* sv, MAGIC* mg);
int (*svt_free) (pTHX_ SV* sv, MAGIC* mg);
int (*svt_copy) (pTHX_ SV *sv, MAGIC* mg, SV *nsv,
const char *name, I32 namlen);
int (*svt_dup) (pTHX_ MAGIC *mg, CLONE_PARAMS *param);
int (*svt_local)(pTHX_ SV *nsv, MAGIC *mg);
此 MGVTBL
结构体在编译时在 perl.h 中设置,目前有 32 种类型。这些不同的结构体包含指向各种例程的指针,这些例程根据调用的函数执行额外的操作。
Function pointer Action taken
---------------- ------------
svt_get Do something before the value of the SV is
retrieved.
svt_set Do something after the SV is assigned a value.
svt_len Report on the SV's length.
svt_clear Clear something the SV represents.
svt_free Free any extra storage associated with the SV.
svt_copy copy tied variable magic to a tied element
svt_dup duplicate a magic structure during thread cloning
svt_local copy magic to local value during 'local'
例如,名为 vtbl_sv
的 MGVTBL
结构体(对应于 mg_type
为 PERL_MAGIC_sv
)包含
{ magic_get, magic_set, magic_len, 0, 0 }
因此,当确定 SV 是魔力的,并且类型为 PERL_MAGIC_sv
时,如果正在执行获取操作,则会调用例程 magic_get
。各种魔力类型的各种例程都以 magic_
开头。注意:魔力例程不被视为 Perl API 的一部分,并且可能不会被 Perl 库导出。
最后三个槽位是最近添加的,为了保持源代码兼容性,只有在 mg_flags 中设置了三个标志之一 MGf_COPY
、MGf_DUP
或 MGf_LOCAL
时才会检查它们。这意味着大多数代码可以继续将 vtable 声明为一个 5 元素的值。这三个目前仅由线程代码使用,并且可能会发生很大变化。
当前的 Magic Virtual Tables 类型有
mg_type
(old-style char and macro) MGVTBL Type of magic
-------------------------- ------ -------------
\0 PERL_MAGIC_sv vtbl_sv Special scalar variable
# PERL_MAGIC_arylen vtbl_arylen Array length ($#ary)
% PERL_MAGIC_rhash (none) Extra data for restricted
hashes
* PERL_MAGIC_debugvar vtbl_debugvar $DB::single, signal, trace
vars
. PERL_MAGIC_pos vtbl_pos pos() lvalue
: PERL_MAGIC_symtab (none) Extra data for symbol
tables
< PERL_MAGIC_backref vtbl_backref For weak ref data
@ PERL_MAGIC_arylen_p (none) To move arylen out of XPVAV
B PERL_MAGIC_bm vtbl_regexp Boyer-Moore
(fast string search)
c PERL_MAGIC_overload_table vtbl_ovrld Holds overload table
(AMT) on stash
D PERL_MAGIC_regdata vtbl_regdata Regex match position data
(@+ and @- vars)
d PERL_MAGIC_regdatum vtbl_regdatum Regex match position data
element
E PERL_MAGIC_env vtbl_env %ENV hash
e PERL_MAGIC_envelem vtbl_envelem %ENV hash element
f PERL_MAGIC_fm vtbl_regexp Formline
('compiled' format)
g PERL_MAGIC_regex_global vtbl_mglob m//g target
H PERL_MAGIC_hints vtbl_hints %^H hash
h PERL_MAGIC_hintselem vtbl_hintselem %^H hash element
I PERL_MAGIC_isa vtbl_isa @ISA array
i PERL_MAGIC_isaelem vtbl_isaelem @ISA array element
k PERL_MAGIC_nkeys vtbl_nkeys scalar(keys()) lvalue
L PERL_MAGIC_dbfile (none) Debugger %_<filename
l PERL_MAGIC_dbline vtbl_dbline Debugger %_<filename
element
N PERL_MAGIC_shared (none) Shared between threads
n PERL_MAGIC_shared_scalar (none) Shared between threads
o PERL_MAGIC_collxfrm vtbl_collxfrm Locale transformation
P PERL_MAGIC_tied vtbl_pack Tied array or hash
p PERL_MAGIC_tiedelem vtbl_packelem Tied array or hash element
q PERL_MAGIC_tiedscalar vtbl_packelem Tied scalar or handle
r PERL_MAGIC_qr vtbl_regexp Precompiled qr// regex
S PERL_MAGIC_sig vtbl_sig %SIG hash
s PERL_MAGIC_sigelem vtbl_sigelem %SIG hash element
t PERL_MAGIC_taint vtbl_taint Taintedness
U PERL_MAGIC_uvar vtbl_uvar Available for use by
extensions
u PERL_MAGIC_uvar_elem (none) Reserved for use by
extensions
V PERL_MAGIC_vstring (none) SV was vstring literal
v PERL_MAGIC_vec vtbl_vec vec() lvalue
w PERL_MAGIC_utf8 vtbl_utf8 Cached UTF-8 information
X PERL_MAGIC_destruct vtbl_destruct destruct callback
x PERL_MAGIC_substr vtbl_substr substr() lvalue
Y PERL_MAGIC_nonelem vtbl_nonelem Array element that does not
exist
y PERL_MAGIC_defelem vtbl_defelem Shadow "foreach" iterator
variable / smart parameter
vivification
Z PERL_MAGIC_hook vtbl_hook %{^HOOK} hash
z PERL_MAGIC_hookelem vtbl_hookelem %{^HOOK} hash element
\ PERL_MAGIC_lvref vtbl_lvref Lvalue reference
constructor
] PERL_MAGIC_checkcall vtbl_checkcall Inlining/mutation of call
to this CV
^ PERL_MAGIC_extvalue (none) Value magic available for
use by extensions
~ PERL_MAGIC_ext (none) Variable magic available
for use by extensions
当表中同时存在大写和小写字母时,大写字母通常用于表示某种复合类型(列表或哈希),而小写字母则用于表示该复合类型的元素。一些内部代码利用了这种大小写关系。但是,'v' 和 'V'(vec 和 v-string)之间没有任何关系。
PERL_MAGIC_ext
、PERL_MAGIC_extvalue
和 PERL_MAGIC_uvar
魔术类型专门为扩展定义,perl 本身不会使用它们。扩展可以使用 PERL_MAGIC_ext
或 PERL_MAGIC_extvalue
魔术将私有信息“附加”到变量(通常是对象)。这特别有用,因为没有办法让正常的 perl 代码破坏这些私有信息(不像使用哈希对象的额外元素)。PERL_MAGIC_extvalue
是值魔术(不像 PERL_MAGIC_ext
和 PERL_MAGIC_uvar
),这意味着在本地化时,新值不会是魔术的。
类似地,PERL_MAGIC_uvar
魔术可以像 tie() 一样使用,在每次使用或更改标量值时调用 C 函数。MAGIC
的 mg_ptr
字段指向一个 ufuncs
结构
struct ufuncs {
I32 (*uf_val)(pTHX_ IV, SV*);
I32 (*uf_set)(pTHX_ IV, SV*);
IV uf_index;
};
当从 SV 中读取或写入 SV 时,将使用 uf_index
作为第一个参数,并将指向 SV 的指针作为第二个参数调用 uf_val
或 uf_set
函数。下面是一个添加 PERL_MAGIC_uvar
魔术的简单示例。请注意,ufuncs
结构由 sv_magic 复制,因此您可以安全地在堆栈上分配它。
void
Umagic(sv)
SV *sv;
PREINIT:
struct ufuncs uf;
CODE:
uf.uf_val = &my_get_fn;
uf.uf_set = &my_set_fn;
uf.uf_index = 0;
sv_magic(sv, 0, PERL_MAGIC_uvar, (char*)&uf, sizeof(uf));
将 PERL_MAGIC_uvar
附加到数组是允许的,但没有效果。
对于哈希,有一个专门的钩子,它可以控制哈希键(但不能控制值)。如果 ufuncs
结构中的“set”函数为 NULL,则此钩子将调用 PERL_MAGIC_uvar
的“get”魔术。每当通过 hv_store_ent
、hv_fetch_ent
、hv_delete_ent
和 hv_exists_ent
函数使用指定为 SV
的键访问哈希时,都会激活此钩子。通过不带 ..._ent
后缀的函数以字符串形式访问键会绕过此钩子。有关详细说明,请参阅 "Hash::Util::FieldHash 中的“GUTS”"。
请注意,由于多个扩展可能使用PERL_MAGIC_ext
或PERL_MAGIC_uvar
魔法,因此扩展必须格外小心以避免冲突。通常,仅对祝福到与扩展相同类的对象使用魔法就足够了。对于PERL_MAGIC_ext
魔法,通常最好定义一个MGVTBL
,即使它的所有字段都将为0
,以便可以使用它们的魔法虚拟表将各个MAGIC
指针识别为特定类型的魔法。mg_findext
提供了一种简单的方法来做到这一点。
STATIC MGVTBL my_vtbl = { 0, 0, 0, 0, 0, 0, 0, 0 };
MAGIC *mg;
if ((mg = mg_findext(sv, PERL_MAGIC_ext, &my_vtbl))) {
/* this is really ours, not another module's PERL_MAGIC_ext */
my_priv_data_t *priv = (my_priv_data_t *)mg->mg_ptr;
...
}
还要注意,前面描述的sv_set*()
和sv_cat*()
函数不会在其目标上调用“set”魔法。这必须由用户通过在调用这些函数后调用SvSETMAGIC()
宏,或使用sv_set*_mg()
或sv_cat*_mg()
函数之一来完成。类似地,通用 C 代码必须调用SvGETMAGIC()
宏来调用任何“get”魔法,如果它们在不处理魔法的函数中使用从外部来源获得的 SV。有关这些函数的描述,请参阅perlapi。例如,对sv_cat*()
函数的调用通常需要在后面跟着SvSETMAGIC()
,但它们不需要先前的SvGETMAGIC()
,因为它们的实现处理“get”魔法。
MAGIC *mg_find(SV *sv, int type); /* Finds the magic pointer of that
* type */
此例程返回指向存储在 SV 中的MAGIC
结构的指针。如果 SV 没有该魔法功能,则返回NULL
。如果 SV 具有该魔法功能的多个实例,则将返回第一个实例。mg_findext
可用于根据其魔法类型及其魔法虚拟表查找 SV 的MAGIC
结构。
MAGIC *mg_findext(SV *sv, int type, MGVTBL *vtbl);
此外,如果传递给mg_find
或mg_findext
的 SV 不是 SVt_PVMG 类型,则 Perl 可能会发生核心转储。
int mg_copy(SV* sv, SV* nsv, const char* key, STRLEN klen);
此例程检查sv
具有哪些类型的魔法。如果 mg_type 字段是大写字母,则将 mg_obj 复制到nsv
,但 mg_type 字段将更改为小写字母。
绑定哈希和数组是PERL_MAGIC_tied
魔法类型的魔法生物。
警告:从 5.004 版本开始,正确使用数组和哈希访问函数需要了解一些注意事项。其中一些注意事项实际上被认为是 API 中的错误,将在以后的版本中修复,并在下面用 [MAYCHANGE] 方括号括起来。如果您发现自己实际上在这一节中应用了这些信息,请注意,行为可能会在将来发生变化,嗯,恕不另行通知。
perl tie 函数将变量与实现各种 GET、SET 等方法的对象关联起来。要从 XSUB 执行等效于 perl tie 函数的操作,您必须模仿此行为。下面的代码执行了必要的步骤 - 首先它创建一个新的哈希,然后创建一个第二个哈希,并将其祝福到将实现 tie 方法的类中。最后,它将两个哈希绑定在一起,并返回对新绑定哈希的引用。请注意,下面的代码不会调用 MyTie 类中的 TIEHASH 方法 - 有关如何执行此操作的详细信息,请参阅"从 C 程序中调用 Perl 例程"。
SV*
mytie()
PREINIT:
HV *hash;
HV *stash;
SV *tie;
CODE:
hash = newHV();
tie = newRV_noinc((SV*)newHV());
stash = gv_stashpv("MyTie", GV_ADD);
sv_bless(tie, stash);
hv_magic(hash, (GV*)tie, PERL_MAGIC_tied);
RETVAL = newRV_noinc(hash);
OUTPUT:
RETVAL
当给定一个绑定数组参数时,av_store
函数只是将数组的魔法复制到要“存储”的值上,使用 mg_copy
。它也可能返回 NULL,表示该值实际上不需要存储在数组中。[MAYCHANGE] 在对绑定数组调用 av_store
后,调用者通常需要调用 mg_set(val)
来实际调用 TIEARRAY 对象上的 Perl 级“STORE”方法。如果 av_store
返回了 NULL,通常还需要调用 SvREFCNT_dec(val)
来避免内存泄漏。[/MAYCHANGE]
上一段文字也适用于使用 hv_store
和 hv_store_ent
函数访问绑定哈希。
av_fetch
和相应的哈希函数 hv_fetch
和 hv_fetch_ent
实际上返回一个未定义的临终值,其魔法已使用 mg_copy
初始化。请注意,返回的该值不需要释放,因为它已经是临终的。[MAYCHANGE] 但是,您需要在返回的值上调用 mg_get()
才能实际调用底层 TIE 对象上的 Perl 级“FETCH”方法。类似地,您也可以在返回值上调用 mg_set()
,在可能使用 sv_setsv
为其分配合适的值后,这将调用 TIE 对象上的“STORE”方法。[/MAYCHANGE]
[MAYCHANGE] 换句话说,数组或哈希的获取/存储函数在绑定数组和哈希的情况下并没有真正获取和存储实际的值。它们只是调用 mg_copy
将魔法附加到要“存储”或“获取”的值上。之后对 mg_get
和 mg_set
的调用实际上完成了调用底层对象上的 TIE 方法的工作。因此,魔法机制目前实现了一种对数组和哈希的延迟访问方式。
目前(截至 Perl 版本 5.004),使用哈希和数组访问函数需要用户了解他们是在操作“普通”哈希和数组,还是在操作它们的绑定变体。API 可能会在将来的版本中更改,以提供对绑定和普通数据类型的更透明访问。[/MAYCHANGE]
您应该了解,TIEARRAY 和 TIEHASH 接口只是为了在使用统一的哈希和数组语法时调用一些 Perl 方法调用而提供的语法糖。使用这种语法糖会带来一些开销(通常每个 FETCH/STORE 操作大约需要两个到四个额外的操作码,此外还需要创建调用方法所需的所有临终变量)。如果 TIE 方法本身很复杂,这种开销相对较小,但如果它们只有几条语句长,那么这种开销就不容忽视。
Perl 有一个非常方便的构造
{
local $var = 2;
...
}
这个构造大致等同于
{
my $oldvar = $var;
$var = 2;
...
$var = $oldvar;
}
最大的区别是,第一个构造会恢复 $var 的初始值,无论控制流如何退出块:goto
、return
、die
/eval
等。它也稍微更有效率。
可以通过 Perl API 从 C 实现类似的任务:创建一个伪块,并安排在块结束时自动撤销一些更改,无论是显式撤销,还是通过非局部退出(通过 die())。块状构造由一对 ENTER
/LEAVE
宏创建(参见 "perlcall 中的 Returning a Scalar")。这种构造可以专门为一些重要的本地化任务创建,或者使用现有的构造(如封闭 Perl 子例程/块的边界,或用于释放 TMP 的现有对)。(在第二种情况下,额外的本地化的开销几乎可以忽略不计。)请注意,任何 XSUB 都自动包含在一个 ENTER
/LEAVE
对中。
在这样的伪块中,以下服务可用
SAVEINT(int i)
SAVEIV(IV i)
SAVEI32(I32 i)
SAVELONG(long i)
SAVEI8(I8 i)
SAVEI16(I16 i)
SAVEBOOL(int i)
SAVESTRLEN(STRLEN i)
这些宏安排在封闭伪块结束时恢复整数变量i
的值。
SAVESPTR(s)
SAVEPPTR(p)
这些宏安排恢复指针s
和p
的值。s
必须是指向一种可以转换为SV*
并返回的类型的指针,p
应该能够转换为char*
并返回。
SAVERCPV(char **ppv)
这个宏安排在当前伪块完成时将使用rcpv_new()
分配的char *
变量的值恢复到其先前状态。在调用时存储在*ppv
中的指针将被引用计数递增并存储在保存堆栈上。稍后,当当前伪块完成时,存储在*ppv
中的值将被引用计数递减,并且先前值将从保存堆栈中恢复,该值也将被引用计数递减。
这是SAVEGENERICSV()
的RCPV
等效项。
SAVEGENERICSV(SV **psv)
此宏用于在当前伪块完成时将SV *
变量的值恢复到其先前状态。在调用时存储在*psv
中的指针将被引用计数递增并存储在保存堆栈中。稍后,当当前伪块完成时,存储在*ppv
中的值将被引用计数递减,并且先前值将从保存堆栈中恢复,该保存堆栈也将被引用计数递减。这是local $sv
的 C 等效项。
SAVEFREESV(SV *sv)
sv
的引用计数将在伪块结束时递减。这类似于sv_2mortal
,因为它也是一种延迟执行SvREFCNT_dec
的机制。但是,虽然sv_2mortal
将sv
的生命周期延长到下一条语句的开始,但SAVEFREESV
将它延长到封闭范围的结束。这些生命周期可能大不相同。
另请比较SAVEMORTALIZESV
。
SAVEMORTALIZESV(SV *sv)
与SAVEFREESV
类似,但在当前范围结束时将sv
设为 mortal,而不是递减其引用计数。这通常会使sv
保持活动状态,直到调用当前活动范围的语句完成执行。
SAVEFREEOP(OP *op)
OP *
将在伪块结束时被op_free()
。
SAVEFREEPV(p)
p
指向的内存块将在当前伪块结束时被Safefree()
。
SAVEFREERCPV(char *pv)
确保由rcpv_new()
调用创建的char *
将在当前伪块结束时被rcpv_free()
。
这是SAVEFREESV()
的 RCPV 等效项。
SAVECLEARSV(SV *sv)
在伪块结束时清除当前 scratchpad 中对应于sv
的槽。
SAVEDELETE(HV *hv, char *key, I32 length)
在伪块结束时,删除了hv
的键key
。指向key
的字符串将被Safefree()。如果在短生命周期存储中有一个键,则可以像这样重新分配相应的字符串
SAVEDELETE(PL_defstash, savepv(tmpbuf), strlen(tmpbuf));
SAVEDESTRUCTOR(DESTRUCTORFUNC_NOCONTEXT_t f, void *p)
在伪块结束时,将使用唯一参数p
调用函数f
,该参数可能为NULL。
SAVEDESTRUCTOR_X(DESTRUCTORFUNC_t f, void *p)
在伪块结束时,将使用隐式上下文参数(如果有)和p
调用函数f
,该参数可能为NULL。
请注意,当前伪块的结束可能比当前语句的结束晚得多。您可能希望查看MORTALDESTRUCTOR_X()
宏。
MORTALSVFUNC_X(SVFUNC_t f, SV *sv)
在当前语句结束时,将使用隐式上下文参数(如果有)和sv
调用函数f
,该参数可能为NULL。
请注意,析构函数的参数与相关的SAVEDESTRUCTOR_X()
不同,因为它必须是NULL或SV*
。
请注意,当前语句的结束可能比当前伪块的结束早得多。您可能希望查看SAVEDESTRUCTOR_X()
宏。
MORTALDESTRUCTOR_SV(SV *coderef, SV *args)
在当前语句结束时,将使用args
中提供的参数(如果有)调用coderef
中包含的Perl函数。有关args
参数的处理方式,请参阅mortal_destructor_sv()
的文档。
请注意,当前语句的结束可能比当前伪块的结束早得多。如果您希望在当前伪块结束时调用Perl函数,则应使用SAVEDESTRUCTOR_X()
API,这将要求您创建一个C包装器来调用Perl函数。
SAVESTACK_POS()
在伪块结束时,将恢复Perl内部堆栈(参见SP
)上的当前偏移量。
以下 API 列表包含函数,因此需要显式提供可修改数据的指针(C 指针或 Perlish 的 GV *
)。在上述宏使用 int
的地方,类似的函数使用 int *
。
上面的其他宏有实现它们的函数,但最好只使用宏,而不是这些函数或下面的函数。
SV* save_scalar(GV *gv)
等效于 Perl 代码 local $gv
。
AV* save_ary(GV *gv)
HV* save_hash(GV *gv)
类似于 save_scalar
,但将 @gv
和 %gv
局部化。
void save_item(SV *item)
复制 SV
的当前值。在当前 ENTER
/LEAVE
伪块退出时,将使用存储的值恢复 SV
的值。它不处理魔法。如果魔法受到影响,请使用 save_scalar
。
SV* save_svref(SV **sptr)
类似于 save_scalar
,但将恢复 SV *
。
void save_aptr(AV **aptr)
void save_hptr(HV **hptr)
类似于 save_svref
,但将 AV *
和 HV *
局部化。
Alias
模块在调用者的范围内实现了基本类型的局部化。对如何在包含范围内局部化事物感兴趣的人也应该看看那里。
XSUB 机制是 Perl 程序访问 C 子例程的一种简单方法。XSUB 例程将有一个包含来自 Perl 程序的参数的栈,以及一种将 Perl 数据结构映射到 C 等效项的方法。
栈参数可以通过 ST(n)
宏访问,该宏返回第 n 个栈参数。参数 0 是 Perl 子例程调用中传递的第一个参数。这些参数是 SV*
,可以在任何使用 SV*
的地方使用。
大多数情况下,C 函数的输出可以通过 RETVAL 和 OUTPUT 指令来处理。但是,在某些情况下,参数栈的长度不足以处理所有返回值。例如,POSIX tzname() 调用不接受任何参数,但返回两个值,即本地时区的标准和夏令时缩写。
为了处理这种情况,使用 PPCODE 指令,并使用宏扩展栈
EXTEND(SP, num);
其中 SP
是表示栈指针的本地副本的宏,num
是要扩展的栈元素数量。
现在栈上有空间了,可以使用 PUSHs
宏将值压入栈。压入的值通常需要是“临时的”(参见 "引用计数和临时性")。
PUSHs(sv_2mortal(newSViv(an_integer)))
PUSHs(sv_2mortal(newSVuv(an_unsigned_integer)))
PUSHs(sv_2mortal(newSVnv(a_double)))
PUSHs(sv_2mortal(newSVpv("Some String",0)))
/* Although the last example is better written as the more
* efficient: */
PUSHs(newSVpvs_flags("Some String", SVs_TEMP))
现在,调用 tzname
的 Perl 程序将把这两个值分配为
($standard_abbrev, $summer_abbrev) = POSIX::tzname;
另一种(可能更简单)将值压入栈的方法是使用宏
XPUSHs(SV*)
此宏会在需要时自动调整栈。因此,您不需要调用 EXTEND
来扩展栈。
尽管在该文档的早期版本中建议使用 (X)PUSH[iunp]
宏,但它们不适合返回多个结果的 XSUB。为此,要么坚持使用上面显示的 (X)PUSHs
宏,要么使用新的 m(X)PUSH[iunp]
宏;参见 "将 C 值压入 Perl 栈"。
有关更多信息,请参阅 perlxs 和 perlxstut。
如果 AUTOLOAD 函数是 XSUB,与 Perl 子例程一样,Perl 会将自动加载子例程的完全限定名称放入 XSUB 包的 $AUTOLOAD 变量中。
但它也会将相同的信息放入 XSUB 本身的某些字段中
HV *stash = CvSTASH(cv);
const char *subname = SvPVX(cv);
STRLEN name_length = SvCUR(cv); /* in bytes */
U32 is_utf8 = SvUTF8(cv);
SvPVX(cv)
只包含子例程名称本身,不包括包。对于 UNIVERSAL 或其超类中的 AUTOLOAD 函数,在对不存在的包进行方法调用时,CvSTASH(cv)
返回 NULL。
注意:在 5.6.1 中,设置 $AUTOLOAD 停止工作,该版本根本不支持 XS AUTOLOAD 子例程。Perl 5.8.0 引入了使用 XSUB 本身中的字段。Perl 5.16.0 恢复了 $AUTOLOAD 的设置。如果您需要支持 5.8-5.14,请使用 XSUB 的字段。
有四个函数可以用来从 C 程序中调用 Perl 子例程。这四个函数是
I32 call_sv(SV*, I32);
I32 call_pv(const char*, I32);
I32 call_method(const char*, I32);
I32 call_argv(const char*, I32, char**);
最常用的例程是call_sv
。SV*
参数包含要调用的 Perl 子例程的名称,或对该子例程的引用。第二个参数包含控制子例程调用上下文的标志,包括是否将参数传递给子例程,如何捕获错误以及如何处理返回值。
所有四个例程都返回子例程在 Perl 堆栈上返回的参数数量。
在 Perl v5.6.0 之前,这些例程被称为perl_call_sv
等,但这些名称现在已弃用;为了兼容性,提供了相同名称的宏。
使用任何这些例程(call_argv
除外)时,程序员必须操作 Perl 堆栈。这些包括以下宏和函数
dSP
SP
PUSHMARK()
PUTBACK
SPAGAIN
ENTER
SAVETMPS
FREETMPS
LEAVE
XPUSH*()
POP*()
有关从 C 到 Perl 的调用约定的详细说明,请参阅perlcall。
许多操作码(这是内部 perl 堆栈机器中的基本操作)将 SV* 放到堆栈上。但是,为了优化,相应的 SV 通常不会每次都重新创建。操作码会重复使用专门分配的 SV(目标),这些 SV 作为推论不会不断地释放/创建。
每个目标只创建一次(但请参阅下面的"Scratchpads and recursion"),当操作码需要将整数、双精度数或字符串放到堆栈上时,它只需设置其目标的相应部分并将目标放到堆栈上。
将此目标放到堆栈上的宏是PUSHTARG
,它直接用于某些操作码,以及间接用于数百万个其他操作码,这些操作码通过(X)PUSH[iunp]
使用它。
由于目标被重复使用,因此在将多个值推送到堆栈时必须小心。以下代码将不会按预期执行
XPUSHi(10);
XPUSHi(20);
这翻译为“将TARG
设置为 10,将指向TARG
的指针推送到堆栈上;将TARG
设置为 20,将指向TARG
的指针推送到堆栈上”。在操作结束时,堆栈不包含值 10 和 20,而是实际上包含两个指向TARG
的指针,我们已将其设置为 20。
如果需要推送多个不同的值,则应使用(X)PUSHs
宏,或者使用新的m(X)PUSH[iunp]
宏,这些宏都不使用TARG
。(X)PUSHs
宏只是将 SV* 推送到堆栈上,正如在"XSUBs and the Argument Stack"下所述,这通常需要是“临时的”。新的m(X)PUSH[iunp]
宏通过为您创建一个新的临时对象(通过(X)PUSHmortal
),将其推送到堆栈上(在mXPUSH[iunp]
宏的情况下,如果需要,则扩展堆栈),然后设置其值,从而使这更容易实现。因此,无需编写以下代码来“修复”上面的示例
XPUSHs(sv_2mortal(newSViv(10)))
XPUSHs(sv_2mortal(newSViv(20)))
你可以简单地写
mXPUSHi(10)
mXPUSHi(20)
相关地,如果你确实使用了 (X)PUSH[iunp]
,那么你需要在你的变量声明中添加一个 dTARG
,以便 *PUSH*
宏可以使用局部变量 TARG
。另请参阅 dTARGET
和 dXSTARG
。
对于作为操作码目标的 SVs 何时创建,这个问题仍然存在。答案是它们在当前单元(子程序或文件,对于子程序之外语句的操作码)编译时创建。在此期间,会创建一个特殊的匿名 Perl 数组,称为当前单元的临时存储区。
临时存储区保存当前单元的词法变量,这些词法变量是操作码的目标。本文档的先前版本指出,可以通过查看其标志来推断 SV 是否位于临时存储区:词法变量设置了 SVs_PADMY
,而目标设置了 SVs_PADTMP
。但这并非完全正确。SVs_PADMY
可能会在不再位于任何填充区的变量上设置。虽然目标确实设置了 SVs_PADTMP
,但它也可能在从未位于填充区但仍然像目标一样工作的变量上设置。从 Perl 5.21.5 开始,SVs_PADMY
标志不再使用,并定义为 0。SvPADMY()
现在对任何没有 SVs_PADTMP
的内容返回 true。
OP 和目标之间的对应关系并非一对一。单元编译树中的不同 OP 可以使用相同的目标,如果这不会与临时变量的预期生命周期冲突。
事实上,编译单元包含指向临时存储区 AV 的指针并不完全正确。实际上,它包含指向一个(最初)包含一个元素的 AV 的指针,而这个元素就是临时存储区 AV。为什么我们需要额外的间接级别?
答案是 **递归**,也许还有 **线程**。这两者都可能创建多个指向同一个子程序的执行指针。为了防止子程序-子级覆盖子程序-父级的临时变量(其生命周期涵盖对子级的调用),父级和子级应该有不同的临时存储区。(并且词法变量也应该分开!)
因此,每个子例程都附带一个初始长度为 1 的临时存储区数组。在每次进入子例程时,都会检查当前递归深度是否超过该数组的长度,如果超过,则会创建一个新的临时存储区并将其推入数组。
此临时存储区上的目标是undef
,但它们已经标记了正确的标志。
所有打算与 Perl API 函数一起使用的内存都应使用本节中描述的宏进行操作。这些宏提供了 Perl 内部使用的实际 malloc 实现之间差异的必要透明性。
以下三个宏用于初始分配内存
Newx(pointer, number, type);
Newxc(pointer, number, type, cast);
Newxz(pointer, number, type);
第一个参数pointer
应该是指向新分配内存的变量的名称。
第二个和第三个参数number
和type
指定要分配多少个指定类型的數據結構。参数type
传递给sizeof
。Newxc
的最后一个参数cast
,应该在pointer
参数与type
参数不同时使用。
与Newx
和Newxc
宏不同,Newxz
宏调用memzero
将所有新分配的内存清零。
Renew(pointer, number, type);
Renewc(pointer, number, type, cast);
Safefree(pointer)
这三个宏用于更改内存缓冲区大小或释放不再需要的内存块。Renew
和Renewc
的参数与New
和Newc
的参数匹配,除了不需要“魔术 cookie”参数。
Move(source, dest, number, type);
Copy(source, dest, number, type);
Zero(dest, number, type);
这三个宏用于移动、复制或清零先前分配的内存。source
和dest
参数分别指向源和目标起始点。Perl 将移动、复制或清零number
个type
数据结构大小的实例(使用sizeof
函数)。
Perl 最近的开发版本一直在尝试消除 Perl 对“正常”标准 I/O 套件的依赖,并允许使用其他 stdio 实现。这涉及创建一个新的抽象层,然后调用 Perl 编译时使用的任何 stdio 实现。现在所有 XSUB 都应该使用 PerlIO 抽象层中的函数,并且不应对正在使用的 stdio 类型做出任何假设。
有关 PerlIO 抽象的完整描述,请参阅 perlapio。
这里我们将描述您的代码被 Perl 转换为的内部形式。从一个简单的例子开始
$a = $b + $c;
这将被转换为类似于此的树
assign-to
/ \
+ $a
/ \
$b $c
(但稍微复杂一些)。这棵树反映了 Perl 解析代码的方式,但与执行顺序无关。树的节点上有一个额外的“线程”,它显示了节点的执行顺序。在我们上面的简化示例中,它看起来像
$b ---> $c ---> + ---> $a ---> assign-to
但对于 $a = $b + $c
的实际编译树来说,情况有所不同:一些节点被优化掉了。作为推论,虽然实际树包含比我们简化示例更多的节点,但执行顺序与我们示例中的相同。
如果你用调试功能编译了 Perl(通常在 Configure
命令行上使用 -DDEBUGGING
完成),你可以在 Perl 命令行上指定 -Dx
来检查编译后的树。输出每个节点占用多行,对于 $b+$c
,它看起来像这样
5 TYPE = add ===> 6
TARG = 1
FLAGS = (SCALAR,KIDS)
{
TYPE = null ===> (4)
(was rv2sv)
FLAGS = (SCALAR,KIDS)
{
3 TYPE = gvsv ===> 4
FLAGS = (SCALAR)
GV = main::b
}
}
{
TYPE = null ===> (5)
(was rv2sv)
FLAGS = (SCALAR,KIDS)
{
4 TYPE = gvsv ===> 5
FLAGS = (SCALAR)
GV = main::c
}
}
这棵树有 5 个节点(每个 TYPE
说明符一个),其中只有 3 个没有被优化掉(左列中的每个数字一个)。给定节点的直接子节点对应于同一缩进级别上的 {}
对,因此此列表对应于树
add
/ \
null null
| |
gvsv gvsv
执行顺序由 ===>
标记指示,因此它是 3 4 5 6
(节点 6
未包含在上面的列表中),即 gvsv gvsv add whatever
。
这些节点中的每一个都代表一个 op,即 Perl 内核中的一个基本操作。实现每个操作的代码可以在 pp*.c 文件中找到;实现类型为 gvsv
的 op 的函数是 pp_gvsv
,依此类推。如上面的树所示,不同的 op 具有不同的子节点数量:add
是一个二元运算符,正如预期的那样,因此有两个子节点。为了适应各种不同的子节点数量,存在各种类型的 op 数据结构,它们以不同的方式链接在一起。
最简单的 op 结构类型是 OP
:它没有子节点。一元运算符 UNOP
只有一个子节点,它由 op_first
字段指向。二元运算符 (BINOP
) 不仅有一个 op_first
字段,还有一个 op_last
字段。最复杂的 op 类型是 LISTOP
,它可以有任意数量的子节点。在这种情况下,第一个子节点由 op_first
指向,最后一个子节点由 op_last
指向。中间的子节点可以通过从第一个子节点到最后一个子节点迭代地跟随 OpSIBLING
指针来找到(但见下文)。
还有一些其他操作类型:一个PMOP
保存一个正则表达式,没有子节点,而一个LOOP
可能会有也可能不会有子节点。如果op_children
字段非零,它就像一个LISTOP
。为了使事情复杂化,如果一个UNOP
在优化后实际上是一个null
操作(参见"编译阶段 2:上下文传播"),它仍然会根据其以前类型拥有子节点。
最后,还有一个LOGOP
,或逻辑操作符。像LISTOP
一样,它有一个或多个子节点,但它没有op_last
字段:所以你必须遵循op_first
,然后是OpSIBLING
链本身来找到最后一个子节点。相反,它有一个op_other
字段,它类似于下面描述的op_next
字段,并表示一个备用执行路径。像and
、or
和?
这样的操作符是LOGOP
。请注意,通常情况下,op_other
可能不会指向LOGOP
的任何直接子节点。
从 5.21.2 版本开始,使用实验性定义-DPERL_OP_PARENT
构建的 Perl 为每个操作符添加了一个额外的布尔标志op_moresib
。当未设置时,这表示这是OpSIBLING
链中的最后一个操作符。这释放了最后一个兄弟节点上的op_sibling
字段,使其指向父操作符。在这种构建下,该字段也被重命名为op_sibparent
以反映其联合作用。宏OpSIBLING(o)
封装了这种特殊行为,并且在最后一个兄弟节点上始终返回 NULL。使用这种构建,op_parent(o)
函数可用于查找任何操作符的父节点。因此,为了向前兼容,您应该始终使用OpSIBLING(o)
宏,而不是直接访问op_sibling
。
检查树的另一种方法是使用编译器后端模块,例如B::Concise。
树是在编译器创建时,由yacc代码为其提供它识别的构造。由于yacc自下而上工作,因此 Perl 编译的第一阶段也是如此。
对于 Perl 开发人员来说,使这一阶段有趣的是,可以在此阶段执行一些优化。这是通过所谓的“检查例程”进行的优化。节点名称与相应检查例程之间的对应关系在opcode.pl中描述(如果修改此文件,请不要忘记运行make regen_headers
)。
当节点完全构建(除了执行顺序线程)时,会调用检查例程。由于此时没有指向当前构建节点的反向链接,因此可以对顶层节点执行大多数任何操作,包括释放它和/或在它之上/之下创建新节点。
检查例程返回应该插入树中的节点(如果顶层节点未被修改,检查例程将返回其参数)。
按照惯例,检查例程的名称为ck_*
。它们通常从new*OP
子例程(或convert
)调用(这些子例程又从perly.y调用)。
在调用检查例程后,立即检查返回的节点是否可以执行编译时。如果是(该值被判断为常量),它将立即执行,并且用包含相应子树“返回值”的常量节点代替。子树将被删除。
如果未执行常量折叠,则创建执行顺序线程。
当编译树的一部分的上下文已知时,它将向下传播到树中。此时,上下文可以有 5 个值(而不是运行时上下文的 2 个值):void、boolean、scalar、list 和 lvalue。与阶段 1 相比,此阶段从上到下处理:节点的上下文决定其子节点的上下文。
此时执行其他与上下文相关的优化。由于此时编译树包含反向引用(通过“线程”指针),因此现在无法释放节点。为了允许在此阶段优化掉节点,此类节点将被 null() 化而不是 free()(即它们的类型更改为 OP_NULL)。
在为子例程(或 eval
或文件)创建编译树后,将对代码执行额外的遍历。此遍历既不是自上而下也不是自下而上,而是在执行顺序中(对于条件语句有额外的复杂性)。在此阶段执行的优化受与阶段 2 相同的限制。
窥孔优化通过调用全局变量 PL_peepp
指向的函数来完成。默认情况下,PL_peepp
只调用全局变量 PL_rpeepp
指向的函数。默认情况下,这会执行一些基本的 op 修复和优化,沿着执行顺序的 op 链,并递归地为 ops 的每个侧链(由条件语句产生)调用 PL_rpeepp
。扩展可以提供额外的优化或修复,挂钩到每个子例程或递归阶段,例如
static peep_t prev_peepp;
static void my_peep(pTHX_ OP *o)
{
/* custom per-subroutine optimisation goes here */
prev_peepp(aTHX_ o);
/* custom per-subroutine optimisation may also go here */
}
BOOT:
prev_peepp = PL_peepp;
PL_peepp = my_peep;
static peep_t prev_rpeepp;
static void my_rpeep(pTHX_ OP *first)
{
OP *o = first, *t = first;
for(; o = o->op_next, t = t->op_next) {
/* custom per-op optimisation goes here */
o = o->op_next;
if (!o || o == t) break;
/* custom per-op optimisation goes AND here */
}
prev_rpeepp(aTHX_ orig_o);
}
BOOT:
prev_rpeepp = PL_rpeepp;
PL_rpeepp = my_rpeep;
编译树在 runops 函数中执行。有两个 runops 函数,分别位于 run.c 和 dump.c 中。Perl_runops_debug
在 DEBUGGING 时使用,Perl_runops_standard
在其他情况下使用。为了对编译树的执行进行精细控制,可以提供自己的 runops 函数。
最好复制现有的 runops 函数之一,并将其更改以满足您的需求。然后,在 XS 文件的 BOOT 部分,添加以下行
PL_runops = my_runops;
此函数应尽可能高效,以确保您的程序运行速度尽可能快。
从 perl 5.14 开始,可以使用 Perl_blockhook_register
挂钩到编译时词法作用域机制。使用方法如下
STATIC void my_start_hook(pTHX_ int full);
STATIC BHK my_hooks;
BOOT:
BhkENTRY_set(&my_hooks, bhk_start, my_start_hook);
Perl_blockhook_register(aTHX_ &my_hooks);
这将安排在编译每个词法作用域的开始时调用 my_start_hook
。可用的钩子是
void bhk_start(pTHX_ int full)
此函数在开始新的词法作用域后立即调用。请注意,类似于以下的 Perl 代码:
if ($x) { ... }
会创建两个作用域:第一个从 (
开始,full == 1
,第二个从 {
开始,full == 0
。两者都在 }
处结束,因此对 start
和 pre
/post_end
的调用将匹配。在此钩子中推送到保存堆栈上的任何内容都将在作用域结束之前弹出(实际上是在 pre_
和 post_end
钩子之间)。
void bhk_pre_end(pTHX_ OP **o)
此函数在词法作用域结束时调用,就在解开堆栈之前。o 是表示作用域的 optree 的根节点;它是一个双指针,因此如果需要,可以替换 OP。
void bhk_post_end(pTHX_ OP **o)
此函数在词法作用域结束时调用,就在解开堆栈之后。o 如上所述。请注意,如果保存堆栈上有调用字符串 eval 的内容,则 pre_
和 post_end
的调用可能会嵌套。
void bhk_eval(pTHX_ OP *const o)
此函数在开始编译 eval STRING
、do FILE
、require
或 use
之前调用,在设置 eval 之后。o 是请求 eval 的 OP,通常是 OP_ENTEREVAL
、OP_DOFILE
或 OP_REQUIRE
。
获得钩子函数后,需要一个 BHK
结构来存放它们。最好静态分配它,因为一旦注册,就无法释放它。函数指针应该使用 BhkENTRY_set
宏插入到此结构中,该宏还会设置标志,指示哪些条目有效。如果出于某种原因需要动态分配 BHK
,请确保在开始之前将其清零。
注册后,没有机制可以关闭这些钩子,因此如果需要,您需要自己执行此操作。%^H
中的条目可能是最好的方法,因此效果是词法作用域的;但是也可以使用 BhkDISABLE
和 BhkENABLE
宏来暂时打开和关闭条目。您还应该注意,通常情况下,至少有一个作用域将在您的扩展加载之前打开,因此您会看到一些没有匹配 start
的 pre
/post_end
对。
dump
函数检查内部数据结构为了帮助调试,源文件 dump.c 包含一些函数,这些函数会生成内部数据结构的格式化输出。
这些函数中最常用的一个是 Perl_sv_dump
;它用于转储 SV、AV、HV 和 CV。Devel::Peek
模块调用 sv_dump
从 Perl 空间生成调试输出,因此该模块的用户应该已经熟悉它的格式。
Perl_op_dump
可用于转储 OP
结构或其任何派生结构,并生成类似于 perl -Dx
的输出;实际上,Perl_dump_eval
将转储正在评估的代码的主根,就像 -Dx
一样。
其他有用的函数包括 Perl_dump_sub
,它将 GV
转换为操作树,Perl_dump_packsubs
在包中的所有子例程上调用 Perl_dump_sub
,如下所示:(值得庆幸的是,这些都是 xsubs,因此没有操作树)
(gdb) print Perl_dump_packsubs(PL_defstash)
SUB attributes::bootstrap = (xsub 0x811fedc 0)
SUB UNIVERSAL::can = (xsub 0x811f50c 0)
SUB UNIVERSAL::isa = (xsub 0x811f304 0)
SUB UNIVERSAL::VERSION = (xsub 0x811f7ac 0)
SUB DynaLoader::boot_DynaLoader = (xsub 0x805b188 0)
以及 Perl_dump_all
,它转储存储区中的所有子例程以及主根的操作树。
Perl 解释器可以看作是一个封闭的盒子:它有一个 API 用于向它提供代码或以其他方式让它执行操作,但它也有用于自身使用的函数。这很像一个对象,你可以构建 Perl,以便拥有多个解释器,其中一个解释器表示为一个 C 结构,或者在特定于线程的结构中。这些结构包含所有上下文,即该解释器的状态。
控制主要 Perl 构建风格的宏是 MULTIPLICITY。MULTIPLICITY 构建有一个 C 结构,它打包了所有解释器状态,该状态作为“隐藏”的第一个参数传递给各种 perl 函数。MULTIPLICITY 使多线程 perl 成为可能(使用 ithreads 线程模型,与宏 USE_ITHREADS 相关)。
PERL_IMPLICIT_CONTEXT 是 MULTIPLICITY 的遗留同义词。
要查看是否有非 const 数据,可以使用与 BSD(或 GNU)兼容的 nm
nm libperl.a | grep -v ' [TURtr] '
如果这显示了任何 D
或 d
符号(或可能是 C
或 c
),则表示你有非 const 数据。grep
删除的符号如下:Tt
是 text 或代码,Rr
是 read-only(const)数据,U
是 <undefined>,外部符号被引用。
测试 t/porting/libperl.t 对 libperl.a
进行这种符号健全性检查。
所有这些显然需要一种方法,使 Perl 内部函数要么是将某种结构作为第一个参数的子例程,要么是将空值作为第一个参数的子例程。为了实现这两种构建解释器的截然不同的方法,Perl 源代码(就像它在许多其他情况下所做的那样)大量使用了宏和子例程命名约定。
第一个问题:决定哪些函数是公共 API 函数,哪些是私有函数。所有以 `S_` 开头的函数都是私有的(可以理解为 "S" 代表 "secret" 或 "static")。所有其他函数都以 "Perl_" 开头,但仅仅因为一个函数以 "Perl_" 开头并不意味着它是 API 的一部分。(参见 "内部函数"。)确定一个函数是否属于 API 的最简单方法是在 perlapi 中查找它的条目。如果它存在于 perlapi 中,它就是 API 的一部分。如果它不存在,并且你认为它应该存在(例如,你需要它来扩展你的扩展),请在 https://github.com/Perl/perl5/issues 上提交一个问题,解释为什么你认为它应该存在。
第二个问题:必须有一种语法,使得相同的子程序声明和调用可以将结构体作为第一个参数传递,或者不传递任何参数。为了解决这个问题,子程序以特定的方式命名和声明。以下是一个在 Perl 内部使用的静态函数的典型开头
STATIC void
S_incline(pTHX_ char *s)
STATIC 在 C 中变为 "static",并且在未来的一些配置中可能被 #define 为空。
一个公共函数(即内部 API 的一部分,但不一定被授权在扩展中使用)以如下方式开头
void
Perl_sv_setiv(pTHX_ SV* dsv, IV num)
pTHX_
是多个宏(在 perl.h 中)之一,它们隐藏了解释器上下文的细节。THX 代表 "thread"、"this" 或 "thingy",具体情况视情况而定。(而且,乔治·卢卡斯没有参与。:-) 第一个字符可以是 'p' 代表 prototype、'a' 代表 argument 或 'd' 代表 declaration,因此我们有 pTHX
、aTHX
和 dTHX
以及它们的变体。
当 Perl 在没有设置 MULTIPLICITY 的选项的情况下构建时,没有包含解释器上下文的第一个参数。pTHX_ 宏中的尾部下划线表示宏展开需要在上下文参数之后添加一个逗号,因为后面还有其他参数。如果未定义 MULTIPLICITY,pTHX_ 将被忽略,并且子程序不会被原型化为接受额外的参数。当没有其他显式参数时,使用没有尾部下划线的宏形式。
当一个核心函数调用另一个函数时,它必须传递上下文。这通常通过宏隐藏。考虑 sv_setiv
。它展开为类似于以下内容
#ifdef MULTIPLICITY
#define sv_setiv(a,b) Perl_sv_setiv(aTHX_ a, b)
/* can't do this for vararg functions, see below */
#else
#define sv_setiv Perl_sv_setiv
#endif
这很好用,这意味着 XS 作者可以愉快地编写
sv_setiv(foo, bar);
并且它仍然可以在 Perl 可以编译的所有模式下工作。
然而,对于可变参数函数,这并不那么干净,因为宏意味着参数数量是预先知道的。相反,我们需要要么完全拼写出来,将 aTHX_
作为第一个参数传递(Perl 核心倾向于对像 Perl_warner 这样的函数这样做),要么使用无上下文版本。
Perl_warner 的无上下文版本称为 Perl_warner_nocontext,它不接受额外的参数。相反,它执行 dTHX;
以从线程本地存储中获取上下文。我们 #define warner Perl_warner_nocontext
,以便扩展在性能的代价下获得源代码兼容性。(传递一个参数比从线程本地存储中获取它更便宜。)
在浏览 Perl 头文件/源代码时,您可以忽略 [pad]THXx。这些严格用于核心内部。扩展和嵌入器只需要知道 [pad]THX。
dTHR
在 perl 5.005 中引入,以支持旧的线程模型。旧的线程模型现在使用 THX
机制来传递上下文指针,因此 dTHR
现在不再有用。Perl 5.6.0 及更高版本仍然为了向后源代码兼容性而保留它,但它被定义为一个空操作。
当 Perl 使用 MULTIPLICITY 构建时,调用 Perl API 中任何函数的扩展将需要以某种方式传递初始上下文参数。关键是您需要以一种方式编写它,以便在 Perl 未启用 MULTIPLICITY 构建时扩展仍然可以编译。
有三种方法可以做到这一点。首先,简单但效率低下的方法,这也是默认方法,为了保持与扩展的源代码兼容性:每当包含 XSUB.h 时,它都会重新定义 aTHX 和 aTHX_ 宏以调用一个函数,该函数将返回上下文。因此,类似于
sv_setiv(sv, num);
在您的扩展中,当 MULTIPLICITY 生效时,它将转换为
Perl_sv_setiv(Perl_get_context(), sv, num);
或者转换为
Perl_sv_setiv(sv, num);
您不必在扩展中做任何新的事情来实现这一点;由于 Perl 库提供了 Perl_get_context(),因此它将全部正常工作。
第二种更有效的方法是使用以下模板为您的 Foo.xs
#define PERL_NO_GET_CONTEXT /* we want efficiency */
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
STATIC void my_private_function(int arg1, int arg2);
STATIC void
my_private_function(int arg1, int arg2)
{
dTHX; /* fetch context */
... call many Perl API functions ...
}
[... etc ...]
MODULE = Foo PACKAGE = Foo
/* typical XSUB */
void
my_xsub(arg)
int arg
CODE:
my_private_function(arg, 10);
请注意,与编写扩展的正常方式相比,只有两个更改:在包含 Perl 头文件之前添加 #define PERL_NO_GET_CONTEXT
,然后在每个将调用 Perl API 的函数的开头添加 dTHX;
声明。(您将知道哪些函数需要这样做,因为 C 编译器会抱怨这些函数中存在未声明的标识符。)XSUB 本身不需要进行任何更改,因为 XS() 宏被正确定义为在需要时传递隐式上下文。
第三种更有效的方法是模仿 Perl 内部是如何完成的
#define PERL_NO_GET_CONTEXT /* we want efficiency */
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
/* pTHX_ only needed for functions that call Perl API */
STATIC void my_private_function(pTHX_ int arg1, int arg2);
STATIC void
my_private_function(pTHX_ int arg1, int arg2)
{
/* dTHX; not needed here, because THX is an argument */
... call Perl API functions ...
}
[... etc ...]
MODULE = Foo PACKAGE = Foo
/* typical XSUB */
void
my_xsub(arg)
int arg
CODE:
my_private_function(aTHX_ arg, 10);
此实现永远不需要使用函数调用来获取上下文,因为它始终作为额外的参数传递。根据您对简单性或效率的需求,您可以自由地混合前两种方法。
不要在 pTHX
后面添加逗号 - 始终使用带下划线的宏形式,用于接受显式参数的函数,或者使用不带参数的形式,用于不接受显式参数的函数。
如果您在一个线程中创建解释器,然后在另一个线程中调用它们,您需要确保 perl 自己的线程本地存储 (TLS) 槽在每个线程中都正确初始化。
perl_alloc
和 perl_clone
API 函数将自动将 TLS 槽设置为它们创建的解释器,因此如果解释器始终在创建它的同一个线程中访问,并且该线程之后没有创建或调用任何其他解释器,则无需执行任何特殊操作。如果不是这种情况,您必须在调用 Perl API 中的任何函数之前,在该线程上设置 TLS 槽。这可以通过在该线程中调用 PERL_SET_CONTEXT
宏作为您执行的第一件事来完成
/* do this before doing anything else with some_perl */
PERL_SET_CONTEXT(some_perl);
... other Perl API calls on some_perl go here ...
(您可以随时通过PERL_GET_CONTEXT
获取当前上下文。)
就像 MULTIPLICITY 提供了一种将解释器了解的所有内容捆绑在一起并传递的方式一样,也计划允许解释器将它了解的关于其运行环境的所有内容捆绑在一起。这通过 PERL_IMPLICIT_SYS 宏实现。目前它仅在 Windows 上与 USE_ITHREADS 一起使用。
这允许为所有系统调用提供一个额外的指针(称为“主机”环境)。这使得所有系统内容能够维护自己的状态,分解为七个 C 结构。这些是围绕默认 perl 可执行文件的常用系统调用的薄包装器(参见win32/perllib.c),但对于更雄心勃勃的主机(例如执行 fork() 模拟的主机),所有需要假装不同的解释器实际上是不同的“进程”的额外工作都将在此处完成。
Perl 引擎/解释器和主机是正交实体。一个进程中可以有一个或多个解释器,一个或多个“主机”,它们之间可以自由关联。
所有将公开给外部世界的 Perl 内部函数都以Perl_
为前缀,以避免与 XS 函数或 Perl 嵌入的程序中使用的函数冲突。类似地,所有全局变量都以PL_
开头。(按照惯例,静态函数以S_
开头。)
在 Perl 核心内部(PERL_CORE
已定义),您可以使用或不使用Perl_
前缀访问这些函数,这要归功于embed.h中的一组定义。请注意,扩展代码不应设置PERL_CORE
;这会暴露完整的 perl 内部结构,并且可能会导致每个新 perl 版本的 XS 出现故障。
文件embed.h是从embed.pl和embed.fnc自动生成的。embed.pl还为内部函数创建原型头文件,生成文档和许多其他部分。当您向核心添加新函数或更改现有函数时,更改embed.fnc中表格中的数据非常重要。以下是该表格中的一个示例条目
Apd |SV** |av_fetch |AV* ar|I32 key|I32 lval
第一列是一组标志,第二列是返回值类型,第三列是名称。后面的列是参数。标志在embed.fnc的顶部有说明。
如果你编辑了embed.pl或embed.fnc,你需要运行make regen_headers
来强制重新构建embed.h和其他自动生成的 文件。
如果你要打印 IV、UV 或 NVS 而不是 stdio(3) 风格的格式化代码,比如%d
、%ld
、%f
,你应该使用以下宏来保证可移植性
IVdf IV in decimal
UVuf UV in decimal
UVof UV in octal
UVxf UV in hexadecimal
NVef NV %e-like
NVff NV %f-like
NVgf NV %g-like
这些宏将处理 64 位整数和长双精度浮点数。例如
printf("IV is %" IVdf "\n", iv);
IVdf
将扩展为 IV 的正确格式。注意,格式周围的空格是必需的,以防代码使用 C++ 编译,以保持与 C++ 标准的兼容性。
注意,存在不同的“长双精度浮点数”:Perl 将使用编译器提供的任何类型。
如果你要打印指针的地址,请使用 %p 或 UVxf 与 PTR2UV() 结合使用。
SV 的内容可以使用 SVf
格式打印,如下所示
Perl_croak(aTHX_ "This croaked because: %" SVf "\n", SVfARG(err_msg))
其中 err_msg
是一个 SV。
并非所有标量类型都是可打印的。简单值当然可以打印:IV、UV、NV 或 PV 中的一种。此外,如果 SV 是对某个值的引用,那么要么会取消引用该值并打印该值,要么会显示有关该值类型及其地址的信息。打印任何其他类型 SV 的结果是未定义的,并且可能导致解释器崩溃。NV 使用 %g
风格的格式打印。
注意,SVf
周围的空格是必需的,以防代码使用 C++ 编译,以保持与 C++ 标准的兼容性。
注意,任何以 UTF-8 格式打印的文件句柄都必须期望 UTF-8,才能获得良好的结果并避免宽字符警告。对于典型文件句柄,一种方法是使用 -C
参数调用 perl。(参见 perlrun 中的 "-C [number/list]"。)
你可以使用它来连接两个标量
SV *var1 = get_sv("var1", GV_ADD);
SV *var2 = get_sv("var2", GV_ADD);
SV *var3 = newSVpvf("var1=%" SVf " and var2=%" SVf,
SVfARG(var1), SVfARG(var2));
SVf_QUOTEDPREFIX
与 SVf
类似,但它限制了打印的字符数量,最多显示参数的前 PERL_QUOTEDPREFIX_LEN
个字符,并使用双引号和双引号字符串转义规则对其进行渲染。如果字符串长度超过此限制,则会在尾随引号后追加省略号 "...”。这适用于错误消息,其中字符串被假定为类名。
HvNAMEf
和 HvNAMEf_QUOTEDPREFIX
与 SVf
类似,但它们使用 HvNAME()
、HvNAMELEN()
、HvNAMEUTF8()
宏从参数中提取字符串、长度和 utf8 标志。这旨在直接从 stash HV 中字符串化类名。
如果您只想以 7 位 NUL 结尾的字符串形式打印字节,您可以直接使用 %s
(假设它们实际上都是 7 位)。但是,如果值有可能被编码为 UTF-8 或包含大于 0x7F
的字节(因此为 8 位),则应改用 UTF8f
格式。并将其参数设置为 UTF8fARG()
宏
chr * msg;
/* U+2018: \xE2\x80\x98 LEFT SINGLE QUOTATION MARK
U+2019: \xE2\x80\x99 RIGHT SINGLE QUOTATION MARK */
if (can_utf8)
msg = "\xE2\x80\x98Uses fancy quotes\xE2\x80\x99";
else
msg = "'Uses simple quotes'";
Perl_croak(aTHX_ "The message is: %" UTF8f "\n",
UTF8fARG(can_utf8, strlen(msg), msg));
UTF8fARG
的第一个参数是布尔值:如果字符串为 UTF-8,则为 1;如果字符串为本机字节编码(Latin1),则为 0。第二个参数是要打印的字符串中的字节数。第三个也是最后一个参数是指向字符串中第一个字节的指针。
注意,任何以 UTF-8 格式打印的文件句柄都必须期望 UTF-8,才能获得良好的结果并避免宽字符警告。对于典型文件句柄,一种方法是使用 -C
参数调用 perl。(参见 perlrun 中的 "-C [number/list]"。)
Size_t
和 SSize_t
的格式化打印最通用的方法是将它们强制转换为 UV 或 IV,然后像 上一节 中那样打印。
但是,如果您使用的是 PerlIO_printf()
,则使用 %z
长度修饰符(表示 siZe)会更少输入和视觉混乱
PerlIO_printf("STRLEN is %zu\n", len);
此修饰符不可移植,因此其使用应限制在 PerlIO_printf()
中。
Ptrdiff_t
、intmax_t
、short
和其他特殊大小的格式化打印如果您使用的是 PerlIO_printf()
,则对于这些特殊情况有修饰符。请参阅 "size" in perlfunc。
由于指针大小不一定等于整数大小,因此请使用以下宏来正确执行此操作。
PTR2UV(pointer)
PTR2IV(pointer)
PTR2NV(pointer)
INT2PTR(pointertotype, integer)
例如
IV iv = ...;
SV *sv = INT2PTR(SV*, iv);
和
AV *av = ...;
UV uv = PTR2UV(av);
还有
PTR2nat(pointer) /* pointer to integer of PTRSIZE */
PTR2ul(pointer) /* pointer to unsigned long */
以及 PTRV
,它提供与指针大小相同的整数的本机类型,例如 unsigned
或 unsigned long
。
有一些宏可以在 XS 模块中进行非常基本的异常处理。您必须在包含 XSUB.h 之前定义 NO_XSLOCKS
才能使用这些宏
#define NO_XSLOCKS
#include "XSUB.h"
如果您调用可能 croak 的代码,但需要在将控制权交回 Perl 之前进行一些清理,则可以使用这些宏。例如
dXCPT; /* set up necessary variables */
XCPT_TRY_START {
code_that_may_croak();
} XCPT_TRY_END
XCPT_CATCH
{
/* do cleanup here */
XCPT_RETHROW;
}
请注意,您始终必须重新抛出已捕获的异常。使用这些宏,无法仅捕获异常并忽略它。如果您必须忽略异常,则必须使用call_*
函数。
使用上述宏的优点是,您不必为call_*
设置额外的函数,并且使用这些宏比使用call_*
更快。
目前正在努力记录内部函数并自动生成参考手册 - perlapi 就是这样一个手册,它详细介绍了 XS 编写者可用的所有函数。 perlintern 是针对不属于 API 且据称仅供内部使用的函数的自动生成手册。
源代码文档是通过在 C 源代码中添加 POD 注释来创建的,例如
/*
=for apidoc sv_setiv
Copies an integer into the given SV. Does not handle 'set' magic. See
L<perlapi/sv_setiv_mg>.
=cut
*/
如果您向 Perl 核心添加函数,请尝试提供一些文档。
Perl API 会随着时间的推移而发生变化。会添加新函数或更改现有函数的接口。Devel::PPPort
模块尝试为其中一些更改提供兼容性代码,因此 XS 编写者在支持多个版本的 Perl 时不必自己编写代码。
Devel::PPPort
生成一个 C 头文件 ppport.h,它也可以作为 Perl 脚本运行。要生成 ppport.h,请运行
perl -MDevel::PPPort -eDevel::PPPort::WriteFile
除了检查现有的 XS 代码外,该脚本还可以使用--api-info
命令行开关来检索各种 API 调用的兼容性信息。例如
% perl ppport.h --api-info=sv_magicext
有关详细信息,请参阅 perldoc ppport.h
。
Perl 5.6.0 引入了 Unicode 支持。对于移植者和 XS 编写者来说,了解这种支持并确保他们编写的代码不会破坏 Unicode 数据非常重要。
在过去的,不太开明的时代,我们都使用 ASCII。我们大多数人都是这样做的。ASCII 的主要问题是它是美国的。好吧,这实际上不是问题;问题是它对不使用罗马字母的人来说不是特别有用。过去发生的事情是,特定语言会将自己的字母表放在序列的上限范围内,介于 128 和 255 之间。当然,我们最终得到了很多与 ASCII 不完全相同的变体,并且它作为标准的全部意义就消失了。
更糟糕的是,如果您使用的是像中文或日语这样的语言,它们有数百或数千个字符,那么您真的无法将它们塞进区区 256 个字符中,因此他们不得不完全忘记 ASCII,并使用一对数字来构建自己的系统来引用一个字符。
为了解决这个问题,一些人组成了 Unicode,Inc. 并创建了一个新的字符集,其中包含所有你能想到的字符,甚至更多。有几种表示这些字符的方法,Perl 使用的方法称为 UTF-8。UTF-8 使用可变数量的字节来表示一个字符。您可以在 perlunicode 中了解更多关于 Unicode 和 Perl 的 Unicode 模型的信息。
(在 EBCDIC 平台上,Perl 使用 UTF-EBCDIC 代替,它是一种针对 EBCDIC 平台的 UTF-8 形式。下面,我们只讨论 UTF-8。UTF-EBCDIC 类似于 UTF-8,但细节不同。宏隐藏了这些差异,只需记住下面给出的特定数字和位模式在 UTF-EBCDIC 中会有所不同。)
你无法识别。这是因为 UTF-8 数据像非 UTF-8 数据一样存储在字节中。Unicode 字符 200(对于你来说是十六进制的 0xC8
),带重音的字母 E,由两个字节 v196.172
表示。不幸的是,非 Unicode 字符串 chr(196).chr(172)
也具有该字节序列。因此,你无法通过查看来判断——这就是 Unicode 输入成为一个有趣问题的所在。
通常,你必须知道你在处理什么,或者你必须猜测。API 函数 is_utf8_string
可以提供帮助;它会告诉你一个字符串是否只包含有效的 UTF-8 字符,并且随着字符串长度的增加,非 UTF-8 字符串看起来像有效的 UTF-8 的可能性会非常小。在逐字符的基础上,isUTF8_CHAR
会告诉你字符串中的当前字符是否为有效的 UTF-8。
如上所述,UTF-8 使用可变数量的字节来存储一个字符。值为 0...127 的字符存储在一个字节中,就像传统的 ASCII 一样。字符 128 存储为 v194.128
;这将一直持续到字符 191,即 v194.191
。现在我们已经用完了位(191 的二进制为 10111111
),所以我们继续;字符 192 为 v195.128
。就这样继续下去,在字符 2048 时移动到三个字节。"perlunicode 中的 Unicode 编码" 有关于此工作原理的图片。
假设你知道你在处理一个 UTF-8 字符串,你可以使用 UTF8SKIP
宏来找出其中第一个字符的长度。
char *utf = "\305\233\340\240\201";
I32 len;
len = UTF8SKIP(utf); /* len is 2 here */
utf += len;
len = UTF8SKIP(utf); /* len is 3 here */
在 UTF-8 字符串中跳过字符的另一种方法是使用 utf8_hop
,它接受一个字符串和要跳过的字符数量。不过,您需要自己进行边界检查,因此请谨慎使用。
多字节 UTF-8 字符中的所有字节都将设置高位,因此您可以像这样测试是否需要对该字符进行特殊处理(UTF8_IS_INVARIANT()
是一个宏,用于测试字节是否在 UTF-8 中编码为单个字节)
U8 *utf; /* Initialize this to point to the beginning of the
sequence to convert */
U8 *utf_end; /* Initialize this to 1 beyond the end of the sequence
pointed to by 'utf' */
UV uv; /* Returned code point; note: a UV, not a U8, not a
char */
STRLEN len; /* Returned length of character in bytes */
if (!UTF8_IS_INVARIANT(*utf))
/* Must treat this as UTF-8 */
uv = utf8_to_uvchr_buf(utf, utf_end, &len);
else
/* OK to treat this character as a byte */
uv = *utf;
您还可以从该示例中看到,我们使用 utf8_to_uvchr_buf
来获取字符的值;反函数 uvchr_to_utf8
可用于将 UV 转换为 UTF-8
if (!UVCHR_IS_INVARIANT(uv))
/* Must treat this as UTF8 */
utf8 = uvchr_to_utf8(utf8, uv);
else
/* OK to treat this character as a byte */
*utf8++ = uv;
如果您遇到需要匹配 UTF-8 和非 UTF-8 字符的情况,则**必须**使用上述函数将字符转换为 UV。在这种情况下,您可能无法跳过 UTF-8 字符。如果您这样做,您将失去匹配高位非 UTF-8 字符的能力;例如,如果您的 UTF-8 字符串包含 v196.172
,并且您跳过该字符,则您将永远无法匹配非 UTF-8 字符串中的 chr(200)
。所以不要这样做!
(请注意,我们不需要在上面的示例中测试不变字符。这些函数适用于任何格式良好的 UTF-8 输入。只是当不需要时,避免函数开销会更快。)
目前,Perl 对 UTF-8 字符串和非 UTF-8 字符串的处理略有不同。SV 中的一个标志 SVf_UTF8
指示字符串在内部编码为 UTF-8。如果没有它,字节值就是代码点编号,反之亦然。此标志仅在 SV 为 SvPOK
或通过 SvPV
或类似宏进行字符串化后立即有效。您可以使用以下宏检查和操作此标志
SvUTF8(sv)
SvUTF8_on(sv)
SvUTF8_off(sv)
此标志对 Perl 对字符串的处理有重要影响:如果 UTF-8 数据没有被正确区分,正则表达式、length
、substr
和其他字符串处理操作将产生不良(错误)结果。
问题在于,例如,当您有一个未标记为 UTF-8 的字符串,并且包含可能为 UTF-8 的字节序列时,尤其是在组合非 UTF-8 和 UTF-8 字符串时。
永远不要忘记 SVf_UTF8
标志与 PV 值是分开的;你需要确保在操作 SV 时不会意外地将其删除。更具体地说,你不能指望这样做
SV *sv;
SV *nsv;
STRLEN len;
char *p;
p = SvPV(sv, len);
frobnicate(p);
nsv = newSVpvn(p, len);
char*
字符串并不能告诉你全部信息,你不能仅仅通过复制字符串值来复制或重建 SV。检查旧的 SV 是否设置了 UTF8 标志(在 SvPV
调用之后),并相应地采取行动
p = SvPV(sv, len);
is_utf8 = SvUTF8(sv);
frobnicate(p, is_utf8);
nsv = newSVpvn(p, len);
if (is_utf8)
SvUTF8_on(nsv);
在上面,你的 frobnicate
函数已被修改为能够识别它是否正在处理 UTF-8 数据,以便它能够适当地处理字符串。
由于仅仅将 SV 传递给 XS 函数并复制 SV 的数据不足以复制 UTF8 标志,因此将 char *
传递给 XS 函数就更不对了。
为了完全通用性,使用 DO_UTF8
宏来查看 SV 中的字符串是否要被视为 UTF-8。这考虑了对 XS 函数的调用是否是在 use bytes
的作用域内进行的。如果是,则将公开构成 UTF-8 字符串的底层字节,而不是它们所代表的字符。但此 pragma 应该只用于调试和可能在字节级别进行的低级测试。因此,大多数 XS 代码不需要关心这一点,但 Perl 内核的各个区域确实需要支持它。
这还不是全部。从 Perl v5.12 开始,未以 UTF-8 编码的字符串也可能在各种情况下被视为 Unicode(参见 "perlunicode 中的 ASCII 规则与 Unicode 规则")。这实际上只对序数在 128 到 255 之间的字符有影响,它们在 ASCII 规则和 Unicode 规则下的行为方式不同,而你的代码关心这些差异(参见 "perlunicode 中的“Unicode 错误”")。没有发布的 API 用于处理这种情况,因为它可能会发生变化,但你可以查看 pp.c 中 pp_lc
的代码,以了解当前是如何实现的。
从概念上讲,Perl 字符串是一个不透明的代码点序列。许多 C 库期望其输入为“经典”C 字符串,即以 NUL 字节结尾的八位字节数组。在编写 Perl 和 C 库之间的接口时,你的工作是定义 Perl 和该库之间的映射。
一般来说,SvPVbyte
和相关的宏非常适合此任务。这些假设你的 Perl 字符串是“字节字符串”,即它是原始的、未解码的输入到 Perl 中,或者预先编码为例如 UTF-8。
或者,如果您的 C 库期望 UTF-8 文本,您可以使用 SvPVutf8
和相关的宏。这与先编码为 UTF-8 然后调用相应的 SvPVbyte
相关的宏具有相同的效果。
一些 C 库可能期望其他编码(例如,UTF-16LE)。要将 Perl 字符串传递给此类库,您必须在 Perl 中执行该编码,然后使用 SvPVbyte
,或者使用中间 C 库将 Perl 存储字符串的方式转换为所需的编码。
还要注意,Perl 字符串中的 NUL 字符不会混淆 C 库。如果可能,请将字符串的长度传递给 C 库;如果不可能,请考虑拒绝包含 NUL 字节的字符串。
SvPV
、SvPV_nolen
等怎么样?考虑一个包含 3 个字符的 Perl 字符串 $foo = "\x64\x78\x8c"
。Perl 可以用两种方式存储这 3 个字符
字节:0x64 0x78 0x8c
UTF-8:0x64 0x78 0xc2 0x8c
现在假设您将 $foo
转换为 C 字符串,如下所示
STRLEN strlen;
char *str = SvPV(foo_sv, strlen);
此时,str
可以指向一个 3 字节的 C 字符串或一个 4 字节的 C 字符串。
一般来说,我们希望 str
与 Perl 存储 $foo
的方式无关,因此这里的歧义是不希望的。SvPVbyte
和 SvPVutf8
通过提供可预测的输出来解决这个问题:如果您的 C 库期望字节字符串,请使用 SvPVbyte
,如果它期望 UTF-8,请使用 SvPVutf8
。
如果您的 C 库恰好支持这两种编码,那么 SvPV
(始终与对 SvUTF8
的查找结合使用!)可能是安全的,并且(稍微)更高效。
测试 提示:在您的测试中使用 utf8 的 upgrade
和 downgrade
函数,以确保无论 Perl 的内部编码如何,都能一致地处理。
如果您混合使用 UTF-8 和非 UTF-8 字符串,则需要将非 UTF-8 字符串升级为 UTF-8。如果您有一个 SV,最简单的方法是
sv_utf8_upgrade(sv);
但是,您不能这样做,例如
if (!SvUTF8(left))
sv_utf8_upgrade(left);
如果您在二元运算符中这样做,您实际上会更改进入运算符的其中一个字符串,虽然最终用户可能不会注意到,但它可能会在有缺陷的代码中造成问题。
相反,bytes_to_utf8
将为您提供其字符串参数的 UTF-8 编码的副本。这对于拥有可用于比较等的数据非常有用,而不会损害原始 SV。还有 utf8_to_bytes
用于反向操作,但自然地,如果字符串包含任何无法用单个字节表示的超过 255 的字符,这将失败。
"sv_cmp" 在 perlapi 中 和 "sv_cmp_flags" 在 perlapi 中 对两个 SV 进行词典排序比较,并正确处理 UTF-8。但是请注意,Unicode 指定了一种更复杂的排序机制,可以通过 Unicode::Collate 模块使用。
要比较两个字符串是否相等/不相等,您可以像往常一样使用 memEQ()
和 memNE()
,但字符串必须都是 UTF-8 编码或都不是 UTF-8 编码。
要比较两个字符串不区分大小写,请使用 foldEQ_utf8()
(字符串不必具有相同的 UTF-8 属性)。
其实没有。只要记住这些事情
无法判断 char *
或 U8 *
字符串是否是 UTF-8。但是,您可以通过在使用 SvPV
或类似宏将其字符串化后,对它调用 DO_UTF8
来判断 SV 是否应该被视为 UTF-8。此外,您可以通过查看其 SvUTF8
标志(同样在将其字符串化后)来判断 SV 是否实际上是 UTF-8(即使它不被视为 UTF-8)。不要忘记在需要时设置标志。将标志视为 PV 的一部分,即使它不是——如果您将 PV 传递到其他地方,也请将标志一起传递。
如果字符串是 UTF-8,始终使用 utf8_to_uvchr_buf
获取其值,除非 UTF8_IS_INVARIANT(*s)
,在这种情况下您可以使用 *s
。
将字符 UV 写入 UTF-8 字符串时,始终使用 uvchr_to_utf8
,除非 UVCHR_IS_INVARIANT(uv))
,在这种情况下您可以使用 *s = uv
。
混合使用 UTF-8 和非 UTF-8 字符串很棘手。使用 bytes_to_utf8
获取一个新的 UTF-8 编码的字符串,然后将它们组合起来。
自定义运算符支持是一项实验性功能,允许您定义自己的运算符。这主要用于允许在 Perl 内核中构建其他语言的解释器,但也允许通过创建“宏运算符”(执行通常一起执行的多个运算符功能的运算符,例如 gvsv, gvsv, add
)来进行优化。
此功能实现为一种新的运算符类型 OP_CUSTOM
。Perl 内核对这种运算符类型没有任何特殊“了解”,因此它不会参与任何优化。这也意味着您可以将自定义运算符定义为任何您喜欢的运算符结构——一元、二元、列表等等。
了解自定义操作符无法为您做什么很重要。它们不会直接让您向 Perl 添加新的语法。它们甚至不会直接让您添加新的关键字。事实上,它们根本不会改变 Perl 编译程序的方式。您必须在 Perl 编译程序之后自己进行这些更改。您可以通过使用 `CHECK` 块和 `B::Generate` 模块来操作操作树,或者通过使用 `optimize` 模块添加自定义窥视孔优化器来实现这一点。
当您执行此操作时,您通过创建类型为 `OP_CUSTOM` 且 `op_ppaddr` 为您自己的 PP 函数的操作来用自定义操作替换普通 Perl 操作。这应该在 XS 代码中定义,并且应该类似于 `pp_*.c` 中的 PP 操作。您有责任确保您的操作从堆栈中获取适当数量的值,并且您有责任在必要时添加堆栈标记。
您还应该将您的操作“注册”到 Perl 解释器,以便它可以生成合理的错误和警告消息。由于在一个“逻辑”操作类型 `OP_CUSTOM` 中可以有多个自定义操作,Perl 使用 `o->op_ppaddr` 的值来确定它正在处理哪个自定义操作。您应该为每个使用的 ppaddr 创建一个 `XOP` 结构,使用 `XopENTRY_set` 设置自定义操作的属性,并使用 `Perl_custom_op_register` 将结构注册到 ppaddr。一个简单的示例可能如下所示
static XOP my_xop;
static OP *my_pp(pTHX);
BOOT:
XopENTRY_set(&my_xop, xop_name, "myxop");
XopENTRY_set(&my_xop, xop_desc, "Useless custom op");
Perl_custom_op_register(aTHX_ my_pp, &my_xop);
结构中可用的字段是
您操作的简短名称。这将包含在一些错误消息中,并且也将作为 `$op->name` 由 B 模块返回,因此它将出现在像 B::Concise 这样的模块的输出中。
操作功能的简短描述。
此操作使用哪些不同的 `*OP` 结构。这应该是来自 op.h 的 `OA_*` 常量之一,即
这应该被解释为“PVOP
”而已。_OR_SVOP
是因为唯一的核心 PVOP
,OP_TRANS
,有时也可能是一个 SVOP
。
其他 OA_*
常量不应该被使用。
这个成员的类型是 Perl_cpeep_t
,它扩展为 void (*Perl_cpeep_t)(aTHX_ OP *o, OP *oldop)
。如果它被设置,这个函数将在 Perl_rpeep
中被调用,当 peephole 优化器遇到这种类型的操作时。o 是需要优化的操作;oldop 是之前被优化的操作,它的 op_next
指向 o。
B::Generate
直接支持通过名称创建自定义操作。
上面的描述偶尔会提到“堆栈”,但实际上 perl 解释器中有很多类似堆栈的数据结构。在没有其他限定的情况下,“堆栈”通常指的是值堆栈。
各种堆栈有不同的用途,并且以略微不同的方式运行。它们的差异将在下面说明。
这个堆栈存储常规 perl 代码正在操作的值,通常是语句中表达式的中间值。堆栈本身是由一个 SV 指针数组组成的。
这个堆栈的底部由解释器变量 PL_stack_base
指向,类型为 SV **
。
堆栈的头部是 PL_stack_sp
,它指向最近被压入的项目。
通过使用 PUSHs()
宏或上面描述的它的变体,将项目压入堆栈;XPUSHs()
、mPUSHs()
、mXPUSHs()
以及类型化的版本。请注意,这些宏的非 X
版本不检查堆栈的大小,并假设它足够大。这些必须与对堆栈大小的适当检查配对,例如 EXTEND
宏,以确保它足够大。例如
EXTEND(SP, 4);
mPUSHi(10);
mPUSHi(20);
mPUSHi(30);
mPUSHi(40);
这比在四个单独的 mXPUSHi()
调用中进行四个单独的检查略微更高效。
作为进一步的性能优化,各种 PUSH
宏都使用局部变量 SP
来操作,而不是解释器全局变量 PL_stack_sp
。这个变量由 dSP
宏声明 - 虽然它通常由 XSUB 和类似的函数隐式声明,所以你很少需要直接考虑它。一旦声明,PUSH
宏将只对这个局部变量进行操作,所以在调用任何其他 perl 核心函数之前,你必须使用 PUTBACK
宏将值从局部 SP
变量返回到解释器变量。类似地,在调用可能已经移动堆栈或向其压入/弹出值的 perl 核心函数之后,你必须使用 SPAGAIN
宏,它将局部 SP
值从解释器值刷新回来。
通过使用 POPs
宏或它的类型化版本,从堆栈中弹出项目。还有一个宏 TOPs
,它可以检查最顶部的项目而不将其移除。
特别注意,值堆栈上的 SV 指针不会影响被引用的 xV 的总引用计数。如果新创建的 xV 被压入堆栈,你必须安排在适当的时间销毁它们;通常通过使用 mPUSH*
宏之一或 sv_2mortal()
来使 xV 成为 mortal。
值栈存储单个 Perl 标量值作为表达式之间的临时值。一些 Perl 表达式对整个列表进行操作;为此,我们需要知道每个列表在栈上的起始位置。这就是标记栈的作用。
标记栈存储整数作为 I32 值,这些值是在列表开始之前值栈的高度;因此,标记本身实际上指向列表之前的值栈条目。列表本身从 `mark + 1` 开始。
此栈的底部由解释器变量 `PL_markstack` 指向,类型为 `I32 *`。
栈的头部是 `PL_markstack_ptr`,指向最近压入的项目。
使用 `PUSHMARK()` 宏将项目压入栈。即使栈本身存储(值)栈索引作为整数,`PUSHMARK` 宏也应该直接给出栈指针;它将通过与 `PL_stack_sp` 变量比较来计算索引偏移量。因此,几乎总是执行此操作的代码是
PUSHMARK(SP);
使用 `POPMARK` 宏从栈中弹出项目。还有一个宏 `TOPMARK` 可以检查最顶部的项目而不将其删除。这些宏直接返回 I32 索引值。还有一个 `dMARK` 宏,它声明一个新的 SV 双指针变量,称为 `mark`,它指向标记的栈槽;这是 C 代码在操作栈上给定的列表时通常使用的宏。
如上所述,`mark` 变量本身将指向列表开始之前值栈上最近压入的值,因此列表本身从 `mark + 1` 开始。列表的值可以通过以下代码进行迭代:
for(SV **svp = mark + 1; svp <= PL_stack_sp; svp++) {
SV *item = *svp;
...
}
特别要注意,如果列表已经为空,`mark` 将等于 `PL_stack_sp`。
由于mark
变量被转换为值栈上的指针,如果在函数内部调用EXTEND
或任何XPUSH
宏,则必须格外小心,因为可能需要移动栈以扩展它,因此现有的指针将变得无效。如果这可能是一个问题,一个可能的解决方案是将标记偏移量跟踪为整数,并在栈移动后跟踪标记本身。
I32 markoff = POPMARK;
...
SP **mark = PL_stack_base + markoff;
如上所述,主值栈上的 xV 引用不会影响 xV 的引用计数,因此使用另一种机制来跟踪何时必须释放位于栈上的临时值。这是临时栈的工作。
临时栈存储指向 xV 的指针,这些 xV 的引用计数将很快递减。
此栈的底部由解释器变量PL_tmps_stack
指向,类型为SV **
。
栈的头部由PL_tmps_ix
索引,它是一个整数,存储数组中最最近压入项目的索引。
没有公共 API 可以直接将项目压入临时栈。相反,API 函数sv_2mortal()
用于使 xV 成为临时值,将其地址添加到临时栈。
同样,也没有公共 API 可以从临时栈读取值。相反,使用宏SAVETMPS
和FREETMPS
。SAVETMPS
宏通过将PL_tmps_ix
的当前值捕获到PL_tmps_floor
并将先前值保存到保存栈中来建立临时栈的基准级别。此后,每当调用FREETMPS
时,都会回收自该级别以来压入的所有临时值。
虽然在ENTER
/LEAVE
对中成对看到这两个宏很常见,但没有必要匹配它们。允许多次调用FREETMPS
,因为最近的SAVETMPS
;例如,在循环遍历列表元素时。虽然可以在范围对中多次调用SAVETMPS
,但这不太可能有用。后续调用将使临时栈底部向上移动,从而有效地将现有临时值捕获,仅在范围结束时释放。
Perl 使用保存栈来实现 local
关键字和其他类似的行为;任何在离开当前作用域时需要执行的清理操作。推送到此栈的项目通常会捕获某些内部变量或状态的当前值,这些值将在作用域因离开、return
、die
、goto
或其他原因而展开时恢复。
而其他 Perl 内部栈存储所有相同类型的单个项目(通常是 SV 指针或整数),推送到保存栈的项目由许多不同类型组成,它们具有多个字段。例如,SAVEt_INT
类型需要存储要恢复的 int
变量的地址以及要恢复的值。这些信息可以使用 struct
的字段存储,但必须足够大以在最大情况下存储三个指针,这将在大多数较小情况下浪费大量空间。
相反,栈以 ANY
结构的可变长度编码存储信息。推送到栈的最终值存储在 UV
字段中,该字段编码前一个项目所持有的项目类型;其数量和类型将取决于要存储的项目类型。类型字段最后被推入,因为在从栈中展开项目时,它将是第一个被弹出字段。
此栈的底部由解释器变量 PL_savestack
指向,其类型为 ANY *
。
栈的头部由 PL_savestack_ix
索引,这是一个整数,它存储数组中应推送下一个项目的索引。(请注意,这与大多数其他栈不同,它们引用最近推送的项目)。
使用各种 SAVE...()
宏将项目推送到保存栈。这些宏中的许多宏都接受一个变量,并在保存栈上存储其地址和当前值,确保该值在作用域退出时恢复。
SAVEI8(i8)
SAVEI16(i16)
SAVEI32(i32)
SAVEINT(i)
...
还有一些其他特殊用途的宏,它们保存特定类型或感兴趣的值。SAVETMPS
已在上面提到。其他宏包括 SAVEFREEPV
,它安排释放 PV(即字符串缓冲区),或 SAVEDESTRUCTOR
,它安排在作用域退出时调用给定的函数指针。可以在 scope.h 中找到所有此类宏的完整列表。
没有用于从保存栈中弹出单个值或项目的公共 API。相反,通过作用域栈,ENTER
和 LEAVE
对形成了一种启动和停止嵌套作用域的方法。通过 LEAVE
离开嵌套作用域将恢复自最近的 ENTER
以来推送到栈的所有保存值。
与标记栈到值栈一样,作用域栈与保存栈形成一对。作用域栈存储嵌套作用域开始时保存栈的高度,并允许在离开作用域时将保存栈展开回该点。
当 Perl 启用调试功能构建时,此堆栈的第二部分存储描述堆栈上下文类型的可读字符串名称。每次 push 操作都会保存名称以及保存堆栈的高度,每次 pop 操作都会检查最顶层的名称是否与预期一致,如果名称不匹配,则会导致断言失败。
此堆栈的底部由解释器变量 PL_scopestack
指向,类型为 I32 *
。如果启用,范围堆栈名称将存储在由 PL_scopestack_name
指向的单独数组中,类型为 const char **
。
堆栈的头部由 PL_scopestack_ix
索引,这是一个整数,存储数组或数组中应推送下一个项目的索引。(请注意,这与大多数其他堆栈不同,其他堆栈引用最近推送的项目)。
使用 ENTER
宏将值推送到范围堆栈,该宏开始一个新的嵌套范围。然后,在 LEAVE
宏的下一个嵌套调用中恢复推送到保存堆栈的任何项目。
注意:本节描述的是非公开的内部 API,可能会在未经通知的情况下更改。
在 Perl 中,动态范围指的是子程序调用、eval 等的运行时嵌套,以及块范围的进入和退出。例如,local
化变量的恢复由动态范围决定。
Perl 通过一个名为上下文栈的数据结构来跟踪动态作用域,该结构是一个包含 PERL_CONTEXT
结构的数组,本身也是所有上下文类型的联合体。每当进入一个新的作用域(例如一个代码块、一个 for
循环或一个子程序调用)时,一个新的上下文条目就会被压入栈顶。类似地,当离开一个代码块或从子程序调用返回时,一个上下文会被弹出。由于上下文栈代表当前的动态作用域,因此可以对其进行搜索。例如,next LABEL
会在栈中回溯搜索与标签匹配的循环上下文;return
会弹出上下文,直到找到一个子程序或 eval 上下文或类似的上下文;caller
会检查栈上的子程序上下文。
每个上下文条目都用一个上下文类型 cx_type
进行标记。典型的上下文类型包括 CXt_SUB
、CXt_EVAL
等,以及 CXt_BLOCK
和 CXt_NULL
,它们分别代表一个基本作用域(由 pp_enter
压入)和一个 sort 代码块。类型决定了上下文联合体的哪个部分是有效的。
上下文结构中的主要划分是替换作用域 (CXt_SUBST
) 和代码块作用域,后者包括所有其他作用域。前者仅在执行 s///e
时使用,这里不再赘述。
所有代码块作用域类型共享一个公共基类,它对应于 CXt_BLOCK
。它存储了各种与作用域相关的变量(如 PL_curpm
)的旧值,以及有关当前作用域的信息,例如 gimme
。在作用域退出时,旧变量会被恢复。
特定的代码块作用域类型存储额外的特定类型信息。例如,CXt_SUB
存储当前正在执行的 CV,而各种 for 循环类型可能会保存原始循环变量 SV。在作用域退出时,特定类型的数据会被处理;例如,CV 的引用计数会递减,原始循环变量会被恢复。
宏 cxstack
返回当前上下文栈的基地址,而 cxstack_ix
是当前帧在该栈中的索引。
事实上,上下文栈实际上是栈中栈系统的一部分;每当执行一些不寻常的操作(例如调用 DESTROY
或 tie 处理程序)时,就会压入一个新的栈,并在最后弹出。
请注意,这里描述的 API 在 perl 5.24 中发生了很大变化;在此之前,使用的是像 PUSHBLOCK
和 POPSUB
这样的宏;在 5.24 中,它们被下面描述的内联静态函数取代。此外,这些宏/函数的工作方式的顺序和细节在许多方面都发生了变化,通常是细微的变化。特别是,它们没有处理保存 savestack 和 temps 栈位置,并且与新的函数相比,需要额外的 ENTER
、SAVETMPS
和 LEAVE
。旧式的宏将不再进一步描述。
要推送新的上下文,有两个基本函数:cx = cx_pushblock()
,它会推送一个新的基本上下文块并返回其地址;以及一系列类似的函数,例如 cx_pushsub(cx)
,它们会填充 cx
结构体中额外的类型相关字段。请注意,CXt_NULL
和 CXt_BLOCK
没有自己的推送函数,因为它们除了 cx_pushblock
推送的数据之外,没有存储任何其他数据。
上下文结构体的字段和 cx_*
函数的参数可能会在不同 Perl 版本之间发生变化,这取决于该版本最方便或最有效的方式。
可以在 pp_entersub
中找到典型的上下文堆栈推送操作;以下展示了一个简化且精简的非 XS 调用示例,以及显示每个函数大致功能的注释。
dMARK;
U8 gimme = GIMME_V;
bool hasargs = cBOOL(PL_op->op_flags & OPf_STACKED);
OP *retop = PL_op->op_next;
I32 old_ss_ix = PL_savestack_ix;
CV *cv = ....;
/* ... make mortal copies of stack args which are PADTMPs here ... */
/* ... do any additional savestack pushes here ... */
/* Now push a new context entry of type 'CXt_SUB'; initially just
* doing the actions common to all block types: */
cx = cx_pushblock(CXt_SUB, gimme, MARK, old_ss_ix);
/* this does (approximately):
CXINC; /* cxstack_ix++ (grow if necessary) */
cx = CX_CUR(); /* and get the address of new frame */
cx->cx_type = CXt_SUB;
cx->blk_gimme = gimme;
cx->blk_oldsp = MARK - PL_stack_base;
cx->blk_oldsaveix = old_ss_ix;
cx->blk_oldcop = PL_curcop;
cx->blk_oldmarksp = PL_markstack_ptr - PL_markstack;
cx->blk_oldscopesp = PL_scopestack_ix;
cx->blk_oldpm = PL_curpm;
cx->blk_old_tmpsfloor = PL_tmps_floor;
PL_tmps_floor = PL_tmps_ix;
*/
/* then update the new context frame with subroutine-specific info,
* such as the CV about to be executed: */
cx_pushsub(cx, cv, retop, hasargs);
/* this does (approximately):
cx->blk_sub.cv = cv;
cx->blk_sub.olddepth = CvDEPTH(cv);
cx->blk_sub.prevcomppad = PL_comppad;
cx->cx_type |= (hasargs) ? CXp_HASARGS : 0;
cx->blk_sub.retop = retop;
SvREFCNT_inc_simple_void_NN(cv);
*/
请注意,cx_pushblock()
设置了两个新的底部:用于参数堆栈(到 MARK
)和临时堆栈(到 PL_tmps_ix
)。在执行此作用域级别时,每个 nextstate
(以及其他操作)都会将参数和临时堆栈级别重置到这些底部。请注意,由于 cx_pushblock
使用 PL_tmps_ix
的当前值而不是将其作为参数传递,因此这决定了 cx_pushblock
应该在何时调用。特别是,任何应该仅在作用域退出时(而不是在下一个 nextstate
时)释放的新 mortal 应该首先创建。
大多数 cx_pushblock
的调用者只是将新的参数堆栈底部设置为前一个堆栈帧的顶部,但对于 CXt_LOOP_LIST
,它会将正在迭代的项目存储在堆栈上,因此将 blk_oldsp
设置为这些项目的顶部。请注意,与它的名称相反,blk_oldsp
并不总是代表在作用域退出时恢复 PL_stack_sp
的值。
请注意,PL_savestack_ix
的早期捕获到 old_ss_ix
,它稍后作为参数传递给 cx_pushblock
。在 pp_entersub
的情况下,这是因为,尽管大多数需要保存的值存储在上下文结构的字段中,但只有在调试器运行时才需要保存一个额外的值,并且为这种罕见的情况而膨胀结构没有意义。因此,它是在保存堆栈上保存的。由于此值在上下文被推入之前被计算和保存,因此有必要将 PL_savestack_ix
的旧值传递给 cx_pushblock
,以确保保存的值在作用域退出期间被释放。对于大多数 cx_pushblock
的用户,如果不需要在保存堆栈上推送任何内容,则 PL_savestack_ix
只是直接作为参数传递给 cx_pushblock
。
请注意,在可能的情况下,应在上下文结构中而不是在保存堆栈上保存值;这样快得多。
通常,cx_pushblock
应该紧随其后的适当的 cx_pushfoo
,两者之间没有任何内容;这是因为,如果中间的代码可能死亡(例如,警告升级为致命错误),那么 dounwind
中的上下文堆栈展开代码将看到(在上面的示例中)一个 CXt_SUB
上下文帧,但没有设置所有子例程特定的字段,并且很快就会发生崩溃。
如果两者必须分开,则最初将类型设置为 CXt_NULL
或 CXt_BLOCK
,然后在执行 cx_pushfoo
时将其更改为 CXt_foo
。这正是 pp_enteriter
在确定要推入的循环类型后所做的。
上下文使用 cx_popsub()
等和 cx_popblock()
弹出。但是请注意,与 cx_pushblock
不同,这两个函数都不会实际递减当前上下文堆栈索引;这是使用 CX_POP()
单独完成的。
上下文弹出主要有两种方式。在正常执行期间,当作用域退出时,诸如 pp_leave
、pp_leaveloop
和 pp_leavesub
之类的函数使用 cx_popfoo
和 cx_popblock
处理并弹出单个上下文。另一方面,诸如 pp_return
和 next
之类的东西可能必须弹出多个作用域,直到找到子例程或循环上下文,并且异常(例如 die
)需要弹出上下文,直到找到 eval 上下文。这两种方法都是由 dounwind()
完成的,它能够处理和弹出目标上下文之上的所有上下文。
以下是在 pp_leavesub
中找到的上下文弹出的典型示例(略微简化)
U8 gimme;
PERL_CONTEXT *cx;
SV **oldsp;
OP *retop;
cx = CX_CUR();
gimme = cx->blk_gimme;
oldsp = PL_stack_base + cx->blk_oldsp; /* last arg of previous frame */
if (gimme == G_VOID)
PL_stack_sp = oldsp;
else
leave_adjust_stacks(oldsp, oldsp, gimme, 0);
CX_LEAVE_SCOPE(cx);
cx_popsub(cx);
cx_popblock(cx);
retop = cx->blk_sub.retop;
CX_POP(cx);
return retop;
上面的步骤按照非常特定的顺序排列,设计为上下文被压入时的逆序。首先要做的是复制和/或保护任何返回值,并释放当前作用域中的任何临时变量。像 rvalue 子例程这样的作用域退出通常会返回其返回值的死亡副本(与 lvalue 子例程相反)。在弹出保存栈或恢复变量之前进行此复制非常重要,否则可能会发生以下不良情况
sub f { my $x =...; $x } # $x freed before we get to copy it
sub f { /(...)/; $1 } # PL_curpm restored before $1 copied
虽然我们希望同时释放所有临时变量,但我们必须注意不要释放任何使返回值保持存活的临时变量;也不要释放我们在进行返回值的死亡复制时刚刚创建的临时变量。幸运的是,leave_adjust_stacks()
能够进行返回值的死亡复制,将参数向下移动到栈中,并且只处理临时变量栈中可以安全处理的那些条目。
在 void 上下文中,没有返回值,因此跳过调用 leave_adjust_stacks()
更有效。同样在 void 上下文中,nextstate
操作很可能即将被调用,它将执行 FREETMPS
,因此也不需要执行该操作。
下一步是弹出保存栈条目:CX_LEAVE_SCOPE(cx)
只是定义为 LEAVE_SCOPE(cx->blk_oldsaveix)
。请注意,在弹出过程中,perl 可能调用析构函数,调用 STORE
来撤销绑定变量的本地化,等等。任何这些操作都可能导致死亡或调用 exit()
。在这种情况下,将调用 dounwind()
,并且当前上下文栈帧将被重新处理。因此,弹出上下文的每个步骤都必须以支持重入的方式完成。另一种选择,即在处理帧之前递减 cxstack_ix
,如果在中途发生死亡或覆盖当前帧,会导致泄漏等问题。
CX_LEAVE_SCOPE
本身是安全的重入:如果在死亡并被 eval 捕获之前只弹出了一半的保存栈条目,那么 dounwind
或 pp_leaveeval
中的 CX_LEAVE_SCOPE
将从第一个弹出的地方继续。
下一步是类型特定的上下文处理;在本例中为 cx_popsub
。部分内容如下
cv = cx->blk_sub.cv;
CvDEPTH(cv) = cx->blk_sub.olddepth;
cx->blk_sub.cv = NULL;
SvREFCNT_dec(cv);
其中它正在处理刚刚执行的 CV。请注意,在它递减 CV 的引用计数之前,它会将 blk_sub.cv
设置为 null。这意味着如果它重新进入,CV 不会被释放两次。这也意味着你不能依赖于从 cx_popfoo
返回后,此类类型特定的字段具有有用的值。
接下来,cx_popblock
将所有不同的解释器变量恢复到它们以前的值或以前的高水位标记;它扩展为
PL_markstack_ptr = PL_markstack + cx->blk_oldmarksp;
PL_scopestack_ix = cx->blk_oldscopesp;
PL_curpm = cx->blk_oldpm;
PL_curcop = cx->blk_oldcop;
PL_tmps_floor = cx->blk_old_tmpsfloor;
请注意,它不会恢复 PL_stack_sp
;如前所述,恢复它的值取决于上下文类型(特别是 for (list) {}
),以及它返回的任何参数;这将由 leave_adjust_stacks()
在之前已经解决。
最后,上下文堆栈指针实际上由 CX_POP(cx)
递减。在此之后,当前上下文帧可能会被其他被推入的上下文覆盖。虽然像 ties 和 DESTROY
应该在新的上下文堆栈中工作,但最好不要假设这一点。实际上,在调试版本中,CX_POP(cx)
会故意将 cx
设置为 null,以检测仍然依赖于该上下文帧中字段值的代码。请注意,在上面的 pp_leavesub()
示例中,我们在调用 CX_POP
之前 获取了 blk_sub.retop
。
最后,有 cx_topblock(cx)
,它在将各种变量重置为其基本值方面类似于超级 nextstate
。它用于 pp_next
、pp_redo
和 pp_goto
等地方,我们希望重新初始化作用域,而不是退出作用域。除了像 nextstate
一样重置 PL_stack_sp
之外,它还重置 PL_markstack_ptr
、PL_scopestack_ix
和 PL_curpm
。请注意,它不会执行 FREETMPS
。
注意:本节描述的是非公开的内部 API,可能会在未经通知的情况下更改。
Perl 的内部错误处理机制使用 longjmp 实现 die
(及其内部等效项)。如果这发生在词法分析、解析或编译期间,我们必须确保在编译过程中分配的任何运算符都被释放。(旧版本的 Perl 没有充分处理这种情况:当解析失败时,它们会泄漏存储在 C auto
变量中且未链接到任何其他地方的运算符。)
为了处理这种情况,Perl 使用运算符 slab,这些 slab 附加到当前正在编译的 CV。slab 是一个分配的内存块。新的运算符被分配为 slab 的区域。如果 slab 填满,就会创建一个新的 slab(并从前一个 slab 中链接)。当发生错误并且 CV 被释放时,任何剩余的运算符都会被释放。
每个运算符前面有两个指针:一个指向 slab 中的下一个运算符,另一个指向拥有它的 slab。下一个运算符指针是必要的,这样 Perl 就可以遍历一个 slab 并释放所有它的运算符。(运算符结构的大小不同,因此 slab 的运算符不能仅仅被视为一个密集数组。)slab 指针是必要的,用于访问 slab 上的引用计数:当 slab 上的最后一个运算符被释放时,slab 本身也会被释放。
slab 分配器将操作放在 slab 的末尾。这将倾向于首先分配操作树的叶子,因此布局有望对缓存友好。此外,这意味着不需要存储 slab 的大小(有关 slab 大小不同的原因,请参见下文),因为 Perl 可以通过指针找到最后一个操作。
可能看起来可以完全消除 slab 引用计数,方法是将所有操作在分配时隐式附加到 PL_compcv
,并在 CV 释放时释放。这还将允许 op_free
完全跳过 FreeOp
,从而更快地释放操作。但这在操作需要在 CV 之后生存的情况下不起作用,例如重新评估。
CV 还必须在 slab 上有一个引用计数。有时,第一个创建的操作会立即释放。如果 slab 的引用计数达到 0,那么它将与 CV 一起释放,而 CV 仍然指向它。
CV 使用 CVf_SLABBED
标志来指示 CV 在 slab 上有一个引用计数。当设置此标志时,当 CvROOT
未设置时,可以通过 CvSTART
访问 slab,或者当 CvROOT
设置时,从 CvROOT
减去两个指针 (2*sizeof(I32 *))
。这种将 slab 偷偷塞入编译期间 CvSTART
的方法的替代方法是将 xpvcv
结构体扩大一个指针。但这将使所有 CV 都更大,即使基于 slab 的操作释放通常只对大量使用字符串评估的程序有益。
当设置 CVf_SLABBED
标志时,CV 负责释放 slab。如果在 CV 释放或取消定义时 CvROOT
未设置,则假定发生了编译错误,因此将遍历操作 slab 并释放所有操作。
在正常情况下,CV 会在附加根时忘记其 slab(递减引用计数)。因此,在释放操作时发生的 slab 引用计数负责释放 slab。在某些情况下,会告诉 CV 忘记 slab(cv_forget_slab
),正是为了让操作在 CV 完成后继续存在。
在根节点附加时忘记 slab 并不是严格必要的,但可以避免 CvROOT
被覆盖的潜在问题。在核心代码和 CPAN 上都有很多代码会使用 CvROOT
,所以忘记 slab 可以使代码更健壮,并避免潜在问题。
由于 CV 在被标记时会拥有其 slab,因此在克隆 CV 时永远不会复制该标记,因为一个 CV 可能会释放另一个 CV 仍然指向的 slab,因为强制释放操作会忽略引用计数(但会断言它看起来正确)。
为了避免 slab 碎片,已释放的操作会被标记为已释放并附加到 slab 的已释放链(从 DBM::Deep 中借鉴的想法)。这些已释放的操作会在可能的情况下被重用。不重用已释放的操作会更简单,但会导致具有大型 if (DEBUG) {...}
块的程序的内存使用量明显增加。
SAVEFREEOP
在这种方案下略有问题。有时它会导致操作在 CV 之后被释放。如果 CV 强制释放了其 slab 上的操作以及 slab 本身,那么我们将操作一个已释放的 slab。将 SAVEFREEOP
设置为无操作并不能解决问题,因为有时操作可以在没有编译错误的情况下被保存释放,因此该操作永远不会被释放。它在 slab 上保存一个引用计数,因此整个 slab 会泄漏。所以 SAVEFREEOP
现在在操作上设置了一个特殊标记(->op_savefree
)。编译错误后强制释放操作不会释放任何这样标记的操作。
由于许多代码片段会创建仅包含几个操作的微型子例程,并且由于一个巨大的 slab 对于这些子例程来说会是一个很大的负担,因此第一个 slab 总是非常小。为了避免为单个 CV 分配太多 slab,每个后续 slab 的大小都是前一个 slab 的两倍。
智能匹配期望能够在运行时分配一个操作,运行它,然后丢弃它。为了使这能够工作,当 PL_compcv
尚未设置时,操作会简单地被 malloc。因此,所有 slab 分配的操作都被标记为这样(->op_slabbed
),以区分它们和 malloc 操作。
直到 1997 年 5 月,这份文档由 Jeff Okamoto <[email protected]> 维护。现在它作为 Perl 本身的一部分由 Perl 5 维护者 <[email protected]> 维护。
感谢 Dean Roehrich、Malcolm Beattie、Andreas Koenig、Paul Hudson、Ilya Zakharevich、Paul Marquess、Neil Bowers、Matthew Green、Tim Bunce、Spider Boardman、Ulrich Pfeifer、Stephen McCamant 和 Gurusamy Sarathy 的大量帮助和建议。