本指南介绍了 R 的内部结构和核心团队为 R 本身开发的编码标准。
本手册适用于 R 版本 4.3.3 (2024-02-29)。
版权 © 1999–2023 R 核心团队
允许制作和分发本手册的逐字副本,前提是在所有副本上保留版权声明和本许可声明。
允许在逐字复制的条件下复制和分发本手册的修改版本,前提是整个生成的衍生作品在与本许可声明相同的许可声明条款下分发。
允许在上述修改版本的条件下复制和分发本手册的翻译版本,但本许可声明可以以 R 核心团队批准的翻译版本的形式陈述。
.Internal
vs .Primitive
本章是关于 R 内部结构的文档的开始。它是为核心团队和其他研究 src/main 目录中代码的人编写的。
它是一个正在进行的工作,应该与当前版本的源代码进行核对。R 2.x.y 版本包含有关何时引入功能的历史注释:此版本适用于 3.x.y 系列。
R 用户所认为的 变量 或 对象 是绑定到值的符号。该值可以被认为是 SEXP
(一个指针),或者它指向的结构,一个 SEXPREC
(对于向量,还有替代形式,即指向 VECTOR_SEXPREC
结构的 VECSXP
)。因此,R 对象的基本构建块通常被称为 节点,指的是 SEXPREC
或 VECTOR_SEXPREC
。
请注意,SEXPREC
的内部结构对 R 扩展不可用:相反,SEXP
是一个不透明的指针,只能通过提供的函数访问其内部结构。
两种类型的节点结构的前三个字段都是一个 64 位的 sxpinfo
头部,然后是三个指针(指向属性以及双向链表中的前一个和下一个节点),以及一些其他字段。在 32 位平台上,一个节点1 占用 32 字节:在 64 位平台上,通常占用 56 字节(取决于对齐约束)。
sxpinfo
头部的前五个位指定了最多 32 个 SEXPTYPE
之一。
目前,SEXPTYPE
0:10 和 13:25 正在使用。值 11 和 12 用于内部因子和有序因子,现已撤回。请注意,SEXPTYPE
编号存储在 save
d 对象中,并且使用类型的排序,因此该间隙无法轻松地重复使用。
否 SEXPTYPE 描述 0
NILSXP
NULL
1
SYMSXP
符号 2
LISTSXP
配对列表 3
CLOSXP
闭包 4
ENVSXP
环境 5
PROMSXP
承诺 6
LANGSXP
语言对象 7
SPECIALSXP
特殊函数 8
BUILTINSXP
内置函数 9
CHARSXP
内部字符字符串 10
LGLSXP
逻辑向量 13
INTSXP
整数向量 14
REALSXP
数值向量 15
CPLXSXP
复数向量 16
STRSXP
字符向量 17
DOTSXP
点-点-点对象 18
ANYSXP
使“任何”参数工作 19
VECSXP
列表(通用向量) 20
EXPRSXP
表达式向量 21
BCODESXP
字节码 22
EXTPTRSXP
外部指针 23
WEAKREFSXP
弱引用 24
RAWSXP
原始向量 25
S4SXP
S4 类,不是简单类型
其中许多在 R 级别上是熟悉的:原子向量类型是 LGLSXP
、INTSXP
、REALSXP
、CPLXSP
、STRSXP
和 RAWSXP
。列表是 VECSXP
,名称(也称为符号)是 SYMSXP
。配对列表(LISTSXP
,名称追溯到 R 作为类似 Scheme 的语言的起源)在 R 级别上很少见,但例如用于参数列表。字符向量实际上是所有元素都是 CHARSXP
的列表,这是一种在 R 级别上很少见到的类型。
语言对象(LANGSXP
)是调用(包括公式等)。在内部,它们是配对列表,第一个元素是对要调用的函数的引用2,其余元素是调用的实际参数(如果有标签,则标签给出指定的参数名称)。虽然没有强制执行,但代码中的许多地方假设配对列表的长度为一个或多个,通常不进行检查。
表达式是 EXPRSXP
类型:它们是(通常是语言)对象的向量,最常见的是作为 parse()
的结果。
函数的类型为 CLOSXP
、SPECIALSXP
和 BUILTINSXP
:其中 SEXPTYPE
存储在整数中,有时它们被归类为伪类型 FUNSXP
,代码为 99。通过 function
定义的函数是 CLOSXP
类型,具有形式参数、主体和环境。
S4 对象的 SEXPTYPE
为 S4SXP
,它不完全由简单类型组成,例如原子向量或函数。
请注意,头部的大小和结构在 R 3.5.0 中发生了变化:有关以前的布局,请参阅本手册的早期版本。
sxpinfo
头部由以下 64 位 C 结构定义:
#define NAMED_BITS 16
struct sxpinfo_struct {
SEXPTYPE type : 5; /* discussed above */
unsigned int scalar: 1; /* is this a numeric vector of length 1?
unsigned int obj : 1; /* is this an object with a class attribute? */
unsigned int alt : 1; /* is this an ALTREP
object? */
unsigned int gp : 16; /* general purpose, see below */
unsigned int mark : 1; /* mark object as ‘in use’ in GC */
unsigned int debug : 1;
unsigned int trace : 1;
unsigned int spare : 1; /* debug once and with reference counting */
unsigned int gcgen : 1; /* generation for GC */
unsigned int gccls : 3; /* class of node for GC */
unsigned int named : NAMED_BITS; /* used to control copying */
unsigned int extra : 32 - NAMED_BITS;
}; /* Tot: 64 */
debug
位用于闭包和环境。对于闭包,它由 debug()
设置,由 undebug()
取消设置,并指示函数的评估应该在浏览器下运行。对于环境,它指示是否处于单步模式。
trace
位用于函数的 trace()
,以及在跟踪复制时用于其他对象(参见 tracemem
)。
spare
位用于闭包,以标记它们进行一次性调试。
named
字段由 SET_NAMED
和 NAMED
宏设置和访问,并取值 0
、1
和 2
,或者如果 NAMEDMAX
设置为更高的值,则可能更高。R 具有“按值调用”的错觉,因此像这样的赋值:
b <- a
[NAMED
机制已被引用计数取代。]
看起来会复制 a
并将其称为 b
。但是,如果 a
和 b
之后都没有被修改,则无需复制。实际上,一个新的符号 b
被绑定到与 a
相同的值,并且值对象的 named
字段被设置(在本例中为 2
)。当一个对象即将被修改时,会查询 named
字段。值为 2
或更大意味着必须在修改对象之前复制它。(注意,这并不意味着必须复制,而只是说无论是否需要,都应该复制。)值为 0
意味着已知没有其他 SEXP
与该对象共享数据,因此可以安全地修改它。值为 1
用于以下情况:
dim(a) <- c(7, 2)
在原则上,a
的两个副本在计算过程中存在(原则上)
a <- `dim<-`(a, c(7, 2))
但不会更久,因此一些基本函数可以优化以在这种情况下避免复制。[此机制计划在 R 4.0.0 中被替换。]
根据定义,gp
位是“通用”的。我们将它们从 0 到 15 标记。位 0-5 和位 14-15 已被使用,如下所述(主要来自对源代码的侦查工作)。
可以通过 LEVELS
和 SETLEVELS
宏访问和设置这些位,这些宏的名称似乎可以追溯到内部因子和有序类型,现在仅在代码中的少数地方使用。对于除 NILSXP
、SYMSXP
和 ENVSXP
之外的 SEXPTYPE
,gp
字段会被序列化/反序列化。
gp
的位 14 和 15 用于“花式绑定”。位 14 用于锁定绑定或环境,位 15 用于指示活动绑定。(有关“活动绑定”的定义,请参见文件 src/main/envir.c 中的标头注释。)位 15 用于环境,以指示它是否参与全局缓存。
当匹配实际函数参数和形式函数参数时,宏 ARGUSED
和 SET_ARGUSED
会使用,它们的值为 0、1 和 2。
宏 MISSING
和 SET_MISSING
用于参数对列表。保留了四个位,但只使用了两个(以及确切的用途没有解释)。似乎位 0 被 matchArgs_NR
用于标记返回参数列表上的缺失,位 1 用于标记将默认值复制到闭包的求值框架中的参数的使用。
位 0 被宏 DDVAL
和 SET_DDVAL
使用。这表示 SYMSXP
是符号 ..n
之一,这些符号是在处理 ...
时隐式创建的,因此表示可能需要在 DOTSXP
中查找它。
位 0 用于 PRSEEN
,一个标志,用于指示在评估承诺期间是否已经看到过承诺(从而避免递归循环)。
位 0 用于 HASHASH
,在环境帧的 TAG
的 PRINTNAME
上。(此位不会为 CHARSXP
对象序列化。)
位 0 和 1 用于弱引用(用于指示“准备终结”,“退出时终结”)。
位 0 由条件处理系统(在 VECSXP
上)使用,以指示调用处理程序。
位 4 被打开以标记 S4 对象。
位 1、2、3、5 和 6 用于 CHARSXP
,以表示其编码。位 1 表示 CHARSXP
应被视为一组字节,不一定代表任何已知编码中的字符。位 2、3 和 6 用于指示它已知为 Latin-1、UTF-8 或 ASCII。
位 5 用于 CHARSXP
,表示它通过其地址进行哈希处理,即 NA_STRING
或在 CHARSXP
缓存中(这不会被序列化)。只有在极少数情况下 CHARSXP
不会被哈希处理,并且这种情况在最终用户代码中永远不会发生。
一个 SEXPREC
是一个 C 结构,包含上面描述的 64 位标题,三个指针(指向属性、前一个节点和下一个节点)以及节点数据,一个联合体
union { struct primsxp_struct primsxp; struct symsxp_struct symsxp; struct listsxp_struct listsxp; struct envsxp_struct envsxp; struct closxp_struct closxp; struct promsxp_struct promsxp; } u;
除了第一个(一个 int
)之外,所有这些替代方案都是三个指针,因此联合体占用三个字。
向量类型是 RAWSXP
、CHARSXP
、LGLSXP
、INTSXP
、REALSXP
、CPLXSXP
、STRSXP
、VECSXP
、EXPRSXP
和 WEAKREFSXP
。请记住,这些类型是 VECTOR_SEXPREC
,它同样包含标题和相同的三个指针,但后面跟着两个整数,分别表示向量的长度和“真实长度”3,然后是数据(按要求对齐:在大多数 32 位系统上,具有 24 字节 VECTOR_SEXPREC
节点的系统,数据可以紧随节点之后)。数据是内存块,其长度足以存储“真实长度”元素(向上舍入到 8 字节的倍数,其中 8 字节块是 gc()
文档中提到的“Vcells”)。
各种类型的“数据”在下面的表格中给出。其中很多是解释,即类型没有被检查。
NILSXP
只存在一个类型为 NILSXP
的对象,即 R_NilValue
,没有数据。
SYMSXP
指向三个节点的指针,分别是名称、值和内部,分别通过 PRINTNAME
(一个 CHARSXP
)、SYMVALUE
和 INTERNAL
访问。(如果符号的值是一个 .Internal
函数,最后一个是指向相应 SEXPREC
的指针。)许多符号的 SYMVALUE
为 R_UnboundValue
。
LISTSXP
指向 CAR、CDR(通常为 LISTSXP
或 NULL
)和 TAG(一个 SYMSXP
或 NULL
)的指针。
CLOSXP
指向形式参数(一个配对列表)、主体和环境的指针。
ENVSXP
指向帧、封闭环境和哈希表(NULL
或 VECSXP
)的指针。帧是一个带标签的配对列表,标签为符号,CAR 为绑定值。
PROMSXP
指向值、表达式和环境(在其中评估表达式)的指针。一旦承诺被评估,环境将被设置为 NULL
。
LANGSXP
一种用于函数调用的特殊类型的 LISTSXP
。(CAR 引用函数(可能通过符号或语言对象),CDR 引用参数列表,其中包含命名参数的标签。)R 级别的文档引用“表达式”/“语言对象”主要是 LANGSXP
,但也可以是符号 (SYMSXP
) 或表达式向量 (EXPRSXP
)。
SPECIALSXP
BUILTINSXP
一个整数,表示在基本函数/ .Internal
表中的偏移量。
CHARSXP
length
、truelength
,后面跟着一个字节块(允许 nul
终止符)。
LGLSXP
INTSXP
length
、truelength
,后面跟着一个 C int
块(在所有 R 平台上都是 32 位)。
REALSXP
length
、truelength
,后面跟着一个 C double
块。
CPLXSXP
length
、truelength
,后面跟着一个 C99 double complex
块。
STRSXP
length
、truelength
,后面跟着一个指针块 (SEXP
,指向 CHARSXP
)。
DOTSXP
一种特殊类型的 LISTSXP
,用于绑定到 ...
符号的值:一个承诺的配对列表。
ANYSXP
这用作任何类型的占位符:实际上没有这种类型的对象。
VECSXP
EXPRSXP
length
, truelength
后面跟着一个指针块。这些在内部是相同的(并且与 STRSXP
相同),但对元素的解释不同。
BCODESXP
对于编译器生成的“字节码”对象。
EXTPTRSXP
有三个指针,指向指针、保护值(一个 R 对象,如果存活则保护此对象)和标签(一个 SYMSXP
?)。
WEAKREFSXP
一个 WEAKREFSXP
是一个特殊的 VECSXP
,长度为 4,元素为“key”、“value”、“finalizer”和“next”。“key”是 NULL
、一个环境或一个外部指针,而“finalizer”是一个函数或 NULL
。
RAWSXP
length
, truelength
后面跟着一个字节块。
S4SXP
两个未使用的指针和一个标签。
正如我们所见,头部的 gccls
字段是三个位,用于标记最多 8 类节点。非向量节点属于类 0,而“小型”向量节点属于类 1 到 5,自定义分配器向量节点属于类 6,“大型”向量节点属于类 7。“小型”向量节点能够存储最多 8、16、32、64 和 128 字节的向量数据:更大的向量是单独使用 malloc
分配的,而“小型”节点是从大约 2000 字节的页面中分配的。使用自定义分配器(通过 allocVector3
)分配的向量节点不会计入 gc 内存使用统计信息,因为它们的内存语义不受 R 的控制,并且可能是非标准的(例如,内存可能在节点之间部分共享)。
用户认为的“变量”是绑定到“环境”中的对象的符号。“环境”一词在 R 中被模棱两可地使用,表示 要么 ENVSXP
的框架(符号-值对的配对列表)要么 ENVSXP
,一个框架加上一个封闭体。
还有其他地方可以查找“变量”,在代码中的注释中称为“用户数据库”。这些似乎在 R 源代码中没有记录,但显然指的是 RObjectTable 包,以前可在 https://www.omegahat.net/RObjectTables/ 获取。
基础环境是特殊的。存在一个名为 ENVSXP
的环境,它包含空环境 R_EmptyEnv
,但该环境的帧未使用。相反,它的绑定是全局符号表的一部分,即全局符号表中值为非 R_UnboundValue
的那些符号。当 R 启动时,内部函数(通过 C 代码)被安装到符号表中,原始函数具有值,而 .Internal
函数具有在 INTERNAL
宏访问的字段中的值。然后计算 .Platform
和 .Machine
,并将基础包加载到基础环境中,然后加载系统配置文件。
环境(和符号表)的帧通常被哈希以实现更快的访问(包括插入和删除)。
默认情况下,R 会维护一个(哈希的)全局缓存,用于存储已找到的“变量”(即符号及其绑定),这仅适用于已标记为参与的那些环境,包括全局环境(也称为用户工作区)、基础环境以及已 attach
的环境4。当环境被 attach
或 detach
时,其符号的名称将从缓存中清除。在从全局环境搜索变量(可能作为递归搜索的一部分)时,将使用缓存。
S 具有“搜索路径”的概念:对“变量”的查找将(可能通过一系列帧)导致“会话帧”,“工作目录”,然后沿着搜索路径进行。搜索路径是一系列数据库(由 search()
返回),其中包含系统函数(但不一定在路径的末尾,因为默认情况下,等效于包的项将被添加到末尾)。
R 对 S 模型进行了变体。存在一个搜索路径(也由 search()
返回),它由全局环境(也称为用户工作区)组成,后面是已附加的环境,最后是基础环境。请注意,与 S 不同,在工作区之前或基础环境之后附加环境是不可能的。
然而,变量查找的概念在 R 中更为普遍,因此本小节标题中的复数。由于环境具有封闭性,从任何环境中都可以找到一个搜索路径,该路径通过查看框架、然后查看其封闭框架,依此类推。由于不允许循环,因此此过程最终将终止:它可以在基本环境或空环境中终止。(从概念上讲,可以更简单地认为搜索始终在空环境中终止,但通过优化可以在基本环境中停止。)因此,“搜索路径”描述了在搜索到达全局环境后遍历的环境链。
命名空间是与包关联的环境(再次,基本包是特殊的,将单独考虑)。一个包 pkg
定义了两个环境 namespace:pkg
和 package:pkg
:是 package:pkg
可以被 attach
并成为搜索路径的一部分。
包中的 R 代码定义的对象是 namespace:pkg
环境中具有绑定的符号。 package:pkg
环境由 namespace:pkg
环境(导出)中的选定符号填充。该环境的封闭环境是一个环境,其中填充了来自其他命名空间的显式导入,而 该环境的封闭环境是基本命名空间。(因此,通过环境树创建了导入位于命名空间环境中的错觉。)基本命名空间的封闭环境是全局环境,因此从包命名空间的搜索通过(显式和隐式)导入到标准“搜索路径”。
基本命名空间环境 R_BaseNamespace
是另一个 ENVSXP
,它是特殊情况。它实际上与基本环境 R_BaseEnv
相同,除了它的封闭环境是全局环境而不是空环境:内部代码将对其框架中的查找重定向到全局符号表。
R 中的环境通常包含一个哈希表,如今这是 new.env()
的默认设置。它存储为一个 VECSXP
,其中 length
用于表分配的大小,而 truelength
是正在使用的主要槽位数量——指向 VECSXP
的指针是类型为 ENVSXP
的 SEXP
的标头的一部分,如果环境未被哈希,则它指向 R_NilValue
。
有关哈希的优缺点,请参阅计算机科学的基本文本。
实现哈希环境的代码位于 src/main/envir.c 中。除非另行设置(例如,由 new.env()
的 size
参数设置),否则初始表大小为 29
。当负载因子(正在使用的主要槽位的比例)达到 85% 时,表将按 1.2 倍的比例调整大小。
哈希链存储为 VECSXP
的配对列表元素:项目插入配对列表的前面。哈希主要用于快速搜索环境,这些环境偶尔会被添加,但很少被删除,因此项目实际上不会被删除,而是将其值设置为 R_UnboundValue
。
正如我们所见,每个 SEXPREC
都包含一个指向节点属性的指针(默认值为 R_NilValue
)。可以使用宏/函数 ATTRIB
和 SET_ATTRIB
访问/设置属性,但这种直接访问通常仅用于检查属性是否为 NULL
或重置它们。否则,访问将通过函数 getAttrib
和 setAttrib
进行,这些函数会对属性施加限制。需要注意的是,如果您将属性从一个对象复制到另一个对象,您可能会(取消)设置 "class"
属性,因此也需要复制对象和 S4 位。有一个宏/函数 DUPLICATE_ATTRIB
可以自动执行此操作。
请注意,CHARSXP
的“属性”用作 CHARSXP
缓存管理的一部分:当然,CHARSXP
对用户不可见,但 C 级代码可能会查看它们的属性。
代码假设节点的属性要么是 R_NilValue
,要么是非零长度的配对列表(这由 SET_ATTRIB
检查)。属性通过配对列表上的标签进行命名。替换函数 attributes<-
确保在配对列表中 "dim"
位于 "dimnames"
之前。属性 "dim"
是几个特殊处理的属性之一:会检查其值,并删除任何 "names"
和 "dimnames"
属性。类似地,您不能在没有设置 "dim"
的情况下设置 "dimnames"
,并且分配的值必须是长度正确且元素长度正确的列表(所有零长度元素都将被替换为 NULL
)。
其他接受特殊处理的属性包括 "names"
、"class"
、"tsp"
、"comment"
和 "row.names"
。对于类似配对列表的对象,名称不会作为属性存储,而是作为标签(以符号形式)存储:但是,R 接口使它们看起来像传统的属性,并且对于一维数组,它们作为 "dimnames"
属性的第一个元素存储。C 代码确保 "tsp"
属性是 REALSXP
,频率为正数,并且隐含长度与分配给对象的行的数量一致。类和注释限制为字符向量,分配零长度的注释或类将删除该属性。设置或删除 "class"
属性将相应地设置对象位。整数行名将转换为内部紧凑表示形式,反之亦然。
在向具有非标准复制语义类型的对象添加属性时,需要格外小心。只存在一个类型为 NILSXP
的对象,即 R_NilValue
,它不应该具有属性(这在 installAttrib
中得到强制执行)。对于环境、外部指针和弱引用,属性应该与对象的全部用途相关:例如,为环境命名是合理的,并且为从包中的 R 代码填充的环境添加 "path"
属性也是合理的。
何时应在对象操作下保留属性?Becker、Chambers 和 Wilks(1988 年,第 144-146 页)提供了一些指导。标量函数(那些对向量逐元素操作且输出类似于输入的函数)应保留属性(可能除了类,如果它们保留类,则需要保留 OBJECT
和 S4 位)。二元运算通常调用 copyMostAttrib
从较长的参数复制大多数属性(如果它们具有相同的长度,则优先考虑第一个参数的值)。这里“大多数”是指除 names
、dim
和 dimnames
之外的所有属性,这些属性由运算符代码适当地设置。
子集(除了空索引)通常会删除所有属性,除了 names
、dim
和 dimnames
,这些属性会根据需要重置。另一方面,子赋值通常会保留这些属性,即使长度发生了改变。强制转换会删除所有属性。例如
> x <- structure(1:8, names=letters[1:8], comm="a comment") > x[] a b c d e f g h 1 2 3 4 5 6 7 8 attr(,"comm") [1] "a comment" > x[1:3] a b c 1 2 3 > x[3] <- 3 > x a b c d e f g h 1 2 3 4 5 6 7 8 attr(,"comm") [1] "a comment" > x[9] <- 9 > x a b c d e f g h 1 2 3 4 5 6 7 8 9 attr(,"comm") [1] "a comment"
上下文是用于跟踪计算进行到何处(以及从何处开始)的内部机制,以便控制流结构可以工作,并且可以在错误条件下(例如 通过回溯)以及其他情况下生成合理的信息(sys.xxx
函数)。
执行上下文是 C structs
的堆栈
typedef struct RCNTXT { struct RCNTXT *nextcontext; /* The next context up the chain */ int callflag; /* The context ‘type’ */ JMP_BUF cjmpbuf; /* C stack and register information */ int cstacktop; /* Top of the pointer protection stack */ int evaldepth; /* Evaluation depth at inception */ SEXP promargs; /* Promises supplied to closure */ SEXP callfun; /* The closure called */ SEXP sysparent; /* Environment the closure was called from */ SEXP call; /* The call that effected this context */ SEXP cloenv; /* The environment */ SEXP conexit; /* Interpretedon.exit
code */ void (*cend)(void *); /* Con.exit
thunk */ void *cenddata; /* Data for Con.exit
thunk */ char *vmax; /* Top of theR_alloc
stack */ int intsusp; /* Interrupts are suspended */ SEXP handlerstack; /* Condition handler stack */ SEXP restartstack; /* Stack of available restarts */ struct RPRSTACK *prstack; /* Stack of pending promises */ } RCNTXT, *context;
加上字节码编译器的附加字段。这些“类型”来自
enum { CTXT_TOPLEVEL = 0, /* toplevel context */ CTXT_NEXT = 1, /* target fornext
*/ CTXT_BREAK = 2, /* target forbreak
*/ CTXT_LOOP = 3, /*break
ornext
target */ CTXT_FUNCTION = 4, /* function closure */ CTXT_CCODE = 8, /* other functions that need error cleanup */ CTXT_RETURN = 12, /*return()
from a closure */ CTXT_BROWSER = 16, /* return target on exit from browser */ CTXT_GENERIC = 20, /* rather, running an S3 method */ CTXT_RESTART = 32, /* a call torestart
was made from a closure */ CTXT_BUILTIN = 64 /* builtin internal function */ };
其中 CTXT_FUNCTION
位在涉及函数闭包的任何地方都处于打开状态。
上下文由对 begincontext
的调用创建,并由对 endcontext
的调用结束:代码可以通过 findcontext
在堆栈中搜索特定类型的上下文(并跳转到该上下文)或通过 R_JumpToContext
跳转到特定上下文。 R_ToplevelContext
是“空闲”状态(通常是命令提示符),而 R_GlobalContext
是堆栈的顶部。
请注意,虽然对闭包的调用会设置上下文,但内部函数永远不会设置上下文,而原始内置函数仅在分析或它们是外部函数的接口时才会设置上下文。
字节码编译器在编译时生成一个指令到源引用和表达式的映射,这允许生成错误条件的信息。作为一种优化,字节码解释器在某些情况下不会设置上下文,例如在简单的循环中或内联简单的内置函数或内部函数的包装器时。
从 S3 通用函数(通过 UseMethod
或其内部等效项)进行调度或调用 NextMethod
会将上下文类型设置为 CTXT_GENERIC
。这用于将方法调用的 sysparent
设置为 generic
的 sysparent
,因此方法看起来是在通用函数的位置被调用,而不是从通用函数调用。
R 的 sys.frame
和 sys.call
函数通过从上下文堆栈的两端计算对闭包(类型 CTXT_FUNCTION
)的调用来工作。
请注意,结构的 sysparent
元素与 sys.parent()
不同。元素 sysparent
主要用于管理正在评估的函数的更改,例如通过 Recall
和方法调度。
CTXT_CCODE
上下文目前用于 cat()
、load()
、scan()
和 write.table()
(在错误时关闭连接)、PROTECT
、序列化(从错误中恢复,例如释放缓冲区)以及错误处理代码中(提高 C 堆栈限制并重置一些变量)。
正如我们所见,R 中的函数有三种类型:闭包(SEXPTYPE
CLOSXP
)、特殊函数(SPECIALSXP
)和内置函数(BUILTINSXP
)。在本节中,我们将考虑何时(以及是否)实际评估函数调用的参数。内部(特殊/内置)函数和 R 级函数(闭包)的规则不同。
对于闭包的调用,实际参数和形式参数会进行匹配,并构建一个匹配的调用(另一个 LANGSXP
)。这个过程首先用对提供值的承诺列表替换实际参数列表。然后,它构建一个新的环境,其中包含与实际值或默认值匹配的形式参数的名称:所有匹配的值都是承诺,默认值是承诺,将在刚创建的环境中进行评估。然后,该环境用于评估函数体,并且承诺将在遇到时被强制(因此实际参数或默认参数被评估)。 (评估承诺会将其值的 NAMED
设置为 NAMEDMAX
,因此如果参数是符号,则在评估闭包调用期间,其绑定被视为具有多个引用。)[NAMED
机制已被引用计数取代。]
如果闭包是 S3 通用函数(即包含对 UseMethod
的调用),则评估过程与遇到 UseMethod
调用之前相同。此时,将评估用于分派的论点(通常是第一个),如果它尚未被评估。如果找到一个方法,它是一个闭包,则会为它创建一个新的评估环境,其中包含该方法的匹配参数以及在评估通用函数体期间定义的任何新变量。(注意,这意味着对通用函数体中形式参数值的更改在调用方法时会被丢弃,但 实际 参数承诺在被强制时会保留找到的值。另一方面,缺少的参数的值是承诺使用该方法提供的默认值,而不是通用函数提供的默认值。)如果找到的方法是基本函数,则使用用于通用函数的匹配参数承诺列表(可能已被强制)调用它。
特殊函数和内置函数之间的本质区别5在于特殊函数的参数在调用 C 代码之前不会被评估,而内置函数的参数会被评估。请注意,是特殊函数/内置函数与是否是基本函数或 .Internal
是分开的:quote
是一个特殊基本函数,+
是一个内置基本函数,cbind
是一个特殊 .Internal
,而 grep
是一个内置 .Internal
。
许多内部函数是内部通用函数,对于特殊函数来说,这意味着它们在调用时不会评估其参数,而是 C 代码从调用 DispatchOrEval
开始。后者评估第一个参数,并根据其类查找方法。(如果 S4 分派已开启,则会首先查找 S4 方法,即使对于 S3 类也是如此。)如果找到方法,它会使用基于承诺来评估剩余参数的调用分派到该方法。如果没有找到方法,则在返回到内部通用函数之前会评估剩余参数。
内部函数可以泛化的另一种方式是分组泛化。大多数此类函数是内置函数(因此立即评估所有参数),并且都包含对 C 函数 DispatchGeneric
的调用。对于 "Math"
分组泛化,参数数量有一些特殊情况,一些成员只允许一个参数,一些成员有两个参数(第二个参数有默认值),而 trunc
允许一个或多个参数,但默认方法只接受一个参数。
传递给(非内部)R 函数的实际参数可能少于与函数的形参匹配所需的个数。如果参数从未使用(通过惰性求值),则不匹配的形参不会有任何影响,但当参数被评估时,要么评估其默认值(在函数的评估环境中),要么抛出错误,并显示类似于以下内容的消息:
argument "foobar" is missing, with no default
在内部,缺失值由两种机制处理。对象 R_MissingArg
用于指示形参没有(默认)值。当将实际参数与形参匹配时,将从形参列表构造一个新的参数列表,该列表的所有值均为 R_MissingArg
,并且第一个 MISSING
位被设置。然后,每当形参与实际参数匹配时,新参数列表的对应成员的值将被设置为匹配的实际参数的值,如果该值不是 R_MissingArg
,则缺失位将被清除。
此新参数列表用于形成函数的评估帧,如果随后为命名参数赋予新值(在它们被评估之前),则缺失位将被清除。
可以使用 missing()
函数查询参数的缺失情况。如果参数的缺失位被设置,或者其值为 R_MissingArg
,则该参数显然是缺失的。但是,缺失情况可以从一个函数传递到另一个函数,因为在函数调用中使用形参作为实际参数并不算作评估。因此,missing()
必须检查尚未评估的形参的值(一个承诺),以查看它是否可能缺失,这可能涉及调查一个承诺,等等……。
特殊基本函数也需要处理缺失参数,在某些情况下(例如 log
),这就是它们是特殊函数而不是内置函数的原因。这通常通过测试参数的值是否为 R_MissingArg
来完成。
省略号参数在编写函数时很方便,但会使参数评估的内部代码变得复杂。
带 ...
参数的函数形式表示该参数与其他参数一样,是一个单独的参数,其标签为符号 R_DotsSymbol
。当实际参数与形式参数匹配时,...
参数的值为 SEXPTYPE
DOTSXP
,它是一个承诺的配对列表(用于匹配参数),但由 SEXPTYPE
区分。
回想一下,函数的求值框架最初包含来自匹配调用的 name=value
对,因此这对 ...
也适用。 ...
的值是一个(特殊的)配对列表,其元素由特殊符号 ..1
、..2
等引用,这些符号设置了 DDVAL
位:当遇到这些符号之一时,它将在求值框架中 ...
符号的值中查找(通过 ddfindVar
)。
与 ...
参数匹配的参数值可能缺失。
特殊原语可能需要处理 ...
参数:例如,请参阅文件 src/main/builtin.c 中 switch
的内部代码。
顶层 R 表达式的返回值是否打印由全局布尔变量 R_Visible
控制。它在进入所有原语和内部函数时(设置为 true 或 false),基于文件 src/main/names.c 中表格的 eval
列:适当的设置可以通过宏 PRIMPRINT
提取。
R 原语函数 invisible
利用了这种机制:它只是在进入之前设置 R_Visible = FALSE
并返回其参数。
对于大多数函数,意图是当它们进入时 R_Visible
的设置是它们返回时使用的设置,但需要有例外。R 函数 identify
、options
、system
和 writeBin
确定结果是否应从参数或用户操作中可见。其他函数本身会调度可能更改可见性标志的函数:例如6 是 .Internal
、do.call
、eval
、withVisible
、if
、NextMethod
、Recall
、recordGraphics
、standardGeneric
、switch
和 UseMethod
。
“特殊”基元函数和内部函数在R_Visible
被设置之后内部评估其参数,并且参数的评估(例如 PR#9263 中的赋值)可能会改变标志的值。
在函数评估期间,R_Visible
标志也可能被更改,代码中的注释提到了warning
、writeChar
和图形函数调用GText
(PR#7397)。(由于 C 级函数eval
设置了R_Visible
,因此这可能适用于任何调用它的函数。由于它在评估承诺时被调用,因此即使对象查找也会更改R_Visible
。)内部函数和基元函数强制在返回时将R_Visible
设置为文档化的设置,除非 C 代码被允许更改它(上面的异常由PRIMPRINT
的值为 2 指示)。
实际的自动打印由print.c文件中的PrintValueEnv
完成。如果要打印的对象设置了 S4 位并且 S4 方法调度已开启,则调用show
来打印对象。否则,如果设置了对象位(因此对象具有"class"
属性),则调用print
来调度方法:对于没有类的对象,将调用print.default
的内部代码。
R 长期以来一直使用分代垃圾收集器,sxpinfo
头部的gcgen
位用于实现它。它与mark
位一起使用,用于识别两个之前的代。
有三个级别的收集。级别 0 只收集最年轻的代,级别 1 收集两个最年轻的代,级别 2 收集所有代。在 20 次级别 0 收集之后,下一次收集将处于级别 1,在 5 次级别 1 收集之后将处于级别 2。此外,如果级别-n收集未能提供 20% 的空闲空间(对于节点和向量堆中的每一个),则下一次收集将处于级别 n+1。(R 级函数gc()
执行级别 2 收集。)
分代收集器需要有效地“老化”对象,尤其是列表状对象(包括STRSXP
)。这是通过确保列表的元素被认为至少与列表一样旧来完成的当它们被分配时。这是由函数SET_VECTOR_ELT
和SET_STRING_ELT
处理的,这就是为什么它们是函数而不是宏的原因。确保此类操作的完整性被称为写屏障,它是通过使SEXP
不透明并且只提供通过函数访问来完成的(在 C 中不能用作赋值中的左值)。
R 扩展中的所有代码都在写入屏障之后。R 扩展不能直接访问 SEXPREC
的内部结构。如果定义了 ‘USE_RINTERNALS’,则基本代码可以访问内部结构。这通常在编译 R 时在 Defn.h 中定义。为了启用对访问方式使用的检查,R 可以使用标志 --enable-strict-barrier 编译,这将确保头文件 Defn.h 不定义 ‘USE_RINTERNALS’,因此 SEXP
在大多数 R 本身中都是不透明的。(有一些必要的例外:最重要的是在文件 memory.c 中定义访问器函数,以及在文件 size.c 中,它需要访问内部结构的大小。)
有关背景资料,请参阅 https://homepage.stat.uiowa.edu/~luke/R/barrier.html 和 https://homepage.stat.uiowa.edu/~luke/R/gengcnotes.html。
R 对象的序列化版本由 load
/save
使用,并且在稍低级别由 saveRDS
/readRDS
(以及它们早期的“内部”点名版本)和 serialize
/unserialize
使用。它们在序列化到的内容(文件、连接、原始向量)以及它们是用于序列化单个对象还是对象集合(通常是工作空间)方面有所不同。 save
在文件开头写入一个头文件(一行以 LF 结尾),而低级别版本则没有。
save
和 saveRDS
允许各种形式的压缩,并且 gzip
压缩是默认的(除了 ASCII 保存)。压缩应用于整个文件流,包括头文件,因此序列化文件可以通过外部程序解压缩或重新压缩。 load
和 readRDS
都可以在从文件读取时读取 gzip
、bzip2
和 xz
形式的压缩,以及在从连接读取时读取 gzip
压缩。
从 2001 年 12 月的 R 1.4.0 到 2019 年 3 月的 R 3.5.3,R 一直使用相同的序列化格式,称为“版本 2”。它自创建以来以向后兼容的方式扩展,例如,支持额外的 SEXPTYPE
。早期格式仍然通过 load
和 save
支持,但此处没有描述这些格式。当前默认的序列化格式称为“版本 3”,它是在 R 3.5.0 中引入的。
save
的工作原理是写入一个单行标题(通常对于二进制保存来说是 RDX2\n
,当前唯一另一个值是 RDA2\n
,用于 save(files=TRUE)
),然后创建一个要保存对象的标记对列表,并序列化该单个对象。 load
读取标题行,反序列化单个对象(一个对列表或一个向量列表),并在指定的环境中分配对象的元素。标题行在 R 中有两个作用:它识别序列化格式,以便 load
可以切换到相应的读取器代码,而换行符 \n
允许检测经过非二进制传输且重新映射了行尾的文件。它也可以被认为是 file
程序中使用的“魔数”(尽管 R 保存文件目前默认情况下不被该程序识别)。
R 中的序列化需要考虑到对象可能包含对环境的引用,而这些环境又具有封闭环境,依此类推。(被识别为包或命名空间环境的环境按名称保存。)存在“引用对象”,它们在复制时不会被复制,并且应该在反序列化时保持共享。这些是弱引用、外部指针以及除与包、命名空间和全局环境相关的环境之外的其他环境。这些通过哈希表进行处理,第一个引用之后的引用以表项索引的引用标记形式写入。
版本 2 序列化首先写入一个指示格式的标题(通常对于 XDR 格式二进制保存来说是 ‘X\n’,但 ‘A\n’(ASCII)和 ‘B\n’(本机字序二进制)也可能出现),然后写入三个整数,分别表示格式的版本和两个 R 版本(由 Rversion.h 中的 R_Version
宏打包)。(反序列化将两个版本解释为写入文件的 R 版本,以及读取格式所需的最小 R 版本。)序列化然后使用 src/main/serialize.c 文件中的 WriteItem
函数递归地写入对象。
某些对象被写入,就好像它们是 SEXPTYPE
一样:此类伪 SEXPTYPE
包括 R_NilValue
、R_EmptyEnv
、R_BaseEnv
、R_GlobalEnv
、R_UnboundValue
、R_MissingArg
和 R_BaseNamespace
。
对于所有 SEXPTYPE
,除了 NILSXP
、SYMSXP
和 ENVSXP
,序列化从一个整数开始,该整数在位 0:7 中包含 SEXPTYPE
7,后面跟着对象位,两个位指示是否有任何属性以及是否有标签(对于配对列表类型),一个未使用的位,然后是 gp
字段8 在位 12:27 中。配对列表类对象写入它们的属性(如果有),标签(如果有),CAR 然后是 CDR(使用尾递归):其他对象在自身之后写入它们的属性。原子向量对象写入它们的长度,然后是数据:通用向量列表对象写入它们的长度,然后对每个元素调用 WriteItem
。CHARSXP
的代码对 NA_STRING
进行特殊处理,并将其写入为长度 -1
,没有数据。长度不超过 2^31 - 1
以这种方式写入,而更大的长度(仅在 64 位系统上出现)则以 -1
以及高 32 位和低 32 位作为整数(视为无符号)写入。
环境以多种方式处理:正如我们所见,一些环境被写入为特定的伪 SEXPTYPE
。包和命名空间环境被写入为伪 SEXPTYPE
,后面跟着名称。‘普通’环境被写入为 ENVSXP
,其中包含一个整数,指示环境是否被锁定,后面跟着封闭环境、帧、‘标签’(哈希表)和属性。
在 ‘XDR’ 格式中,整数和双精度数以大端序写入:但是,该格式并非完全 XDR(如 RFC 1832 中所定义),因为字节数量(如 CHARSXP
和 RAWSXP
类型的內容)按原样写入,不会填充到 4 字节的倍数。
‘ASCII’ 格式写入 7 位字符。整数使用 %d
格式化(除了 NA_integer_
被写入为 NA
),双精度数使用 %.16g
格式化(加上 NA
、Inf
和 -Inf
),字节使用 %02x
格式化。字符串使用标准转义符(例如 \t
和 \013
)来写入非打印字符和非 ASCII 字节。
版本 3 序列化通过支持 ALTREP
框架对象的自定义序列化来扩展版本 2。它还在序列化时存储当前的本机编码,以便在不同本机编码下运行的 R 中反序列化时,可以转换未标记的字符串。
R 中的字符数据存储在 sexptype CHARSXP
中。
除了当前区域设置的编码之外,还支持其他编码,特别是 UTF-8 和 Windows 用于 CJK 语言的多字节编码。指示 CHARSXP
编码的有限方法是通过两个“通用”位,这两个位用于声明编码为 Latin-1 或 UTF-8。(请注意,字符向量可能包含不同编码的元素。)打印和绘图都会注意到声明并将字符串转换为当前区域设置(可能使用 <xx>
以十六进制字节显示在当前区域设置中无效的字节)。许多(但并非全部)字符操作函数将保留声明或重新编码字符字符串。
引用操作系统的字符串(如文件名)需要在某些操作系统(例如 Windows)上通过宽字符接口传递。
何时声明字符字符串具有已知编码?一种方法是通过 Encoding
直接声明。如果已知,解析器将声明编码,无论是通过 parse
的 encoding
参数,还是从 R 命令行中进行解析的区域设置。(其他方法记录在 Encoding
的帮助页面上。)
没有必要声明 ASCII 字符串的编码,因为它们将在任何区域设置中工作。ASCII 字符串永远不应该有标记的编码,因为任何编码在将此类字符串输入 CHARSXP
缓存时都会被忽略。
仅考虑 UTF-8 和 Latin-1 的基本原理是,大多数系统能够生成 UTF-8 字符串,这是我们最接近通用格式的。对于那些没有(例如那些没有足够强大的 iconv
的系统),它们很可能在 Latin-1 中工作,这是旧的 R 假设。然后,解析器可以返回一个 UTF-8 编码的字符串,如果它遇到一个“\uxxxx”转义符,用于表示当前字符集中无法表示的 Unicode 点。(这需要 MBCS 支持,并且在过去仅在 Windows 上启用9。)现在,这已在所有平台上启用,并且“\uxxxx”或“\Uxxxxxxxx”转义符确保解析后的字符串将被标记为 UTF-8。
大多数字符操作函数现在保留 UTF-8 编码:在文件 src/main/character.c 的顶部和文件 src/library/base/man/Encoding.Rd 中有一些关于哪些函数保留 UTF-8 编码的说明。
图形设备可以通过将 hasTextUTF8
设置为 ‘TRUE’ 并提供期望 UTF-8 编码输入的函数 textUTF8
和 strWidthUTF8
,来处理 UTF-8 编码的字符串,而无需重新编码为本地字符集。通常情况下,符号字体使用 Adobe Symbol 编码,但可以通过将 wantSymbolUTF8
设置为 ‘TRUE’ 将其重新编码为 UTF-8。cairographics 的 Windows 端口有一个相当奇怪的假设:它希望符号字体以 UTF-8 编码,就好像它以 Latin-1 而不是 Adobe Symbol 编码一样:这可以通过 wantSymbolUTF8 = NA_LOGICAL
来选择。
使用 MSVCRT 作为 C 运行时的 Windows 没有 UTF-8 本地化,而是期望使用 UCS-210 字符串。R(用标准 C 编写)在没有大量更改的情况下,不会在内部使用 UCS-2。 Rgui 控制台11 在内部使用 UCS-2,但以本地编码与 R 引擎通信。为了允许 UTF-8 字符串在 Rgui.exe 中以 UTF-8 格式打印,cat
、print
和自动打印使用了一种转义约定(参见头文件 rgui_UTF8.h)。
‘Unicode’ (UCS-2LE) 文件在 Windows 世界中很常见,如果在传递给这些函数的未打开连接上显式声明编码,则 readLines
和 scan
会将它们读入 Windows 上的 UTF-8 字符串。
Windows 对当前区域设置编码有多种概念,一种是在 C 运行时(C 库)中,另一种是活动代码页(系统区域设置)。活动代码页在调用 Windows API 函数的非 UTF-16 变体(以前称为 ANSI 调用)时使用,无论是直接调用还是通过 MinGW-w64 中的 POSIX 包装器间接调用,从 R、R 包和它们链接到的库调用。虽然 R 通过直接调用 Windows API 的 UTF-16 变体处理了许多情况,但它有时仍然可能使用非 UTF-16 变体,并且主要为 POSIX 系统开发的外部库通常也这样做。因此,为了让 R 在 Windows 上可靠地处理(非 ASCII)字符串,Windows 上的 C 区域设置编码和活动代码页必须相同,默认情况下它们是相同的。
Windows UCRT C 运行时支持 UTF-8 本地化。从历史上看,活动代码页是一个系统范围的设置,更改它需要重新启动,并且不支持 UTF-8。后来,添加了一个“BETA:使用 Unicode UTF-8 进行全球语言支持”功能,将活动代码页设置为 UTF-8,但这仍然需要重新启动,并且会影响所有应用程序,其中许多应用程序无法正确使用此意外设置,因此它无法在实践中使用。
从 Windows 10(版本 1903)、Windows Server 2022(LTSC)和 Windows Server 1903(半年频道)开始,Windows 允许在应用程序清单中将活动代码页设置为 UTF-8。这仅更改给定应用程序的活动代码页,并且与将 UCRT C 区域设置更改为 UTF-8 一起进行。适用于 Windows 的 R 4.2 使用此功能在 Windows 上将 UTF-8 作为本地编码。为了实现这一点,R 必须切换到 UCRT,这反过来又需要创建 Rtools42。
旧版本的 Windows 仍然依赖于之前的编码支持,其中原生编码不能是 UTF-8。R 4.2 需要 UCRT 才能工作,但 UCRT 可以安装在 Windows Vista SP2 和 Windows Server 2008 SP2 及更高版本上。它从 Windows 10 和 Windows Server 2016 开始随 Windows 一起提供。
有一个用于 CHARSXP
的全局缓存,该缓存由 mkChar
创建——缓存确保大多数具有相同内容的 CHARSXP
共享存储(“内容”包括任何声明的编码)。并非所有 CHARSXP
都是缓存的一部分——特别是“NA_STRING”不是。从 R 0.99.0 之前的 save
格式重新加载的 CHARSXP
不会被缓存(因为使用的代码是固定的,并且很少有示例仍然存在)。
缓存记录字符串的编码以及字节:所有创建 CHARSXP
的请求都应该通过调用 mkCharLenCE
来进行。如果字符串的字节都是 ASCII 字符,则在 mkCharLenCE
调用中给出的任何编码都将被忽略。
warning
和 stop
都有两个 C 级别的等效项,分别是 warning
、warningcall
、error
和 errorcall
。这两对之间的关系类似:warning
尝试找出合适的调用,然后在成功时将该调用作为第一个参数调用 warningcall
,如果失败则调用 call = R_NilValue
。当调用 warningcall
时,它会在其打印输出中包含解语法后的调用,除非 call = R_NilValue
。
warning
和 error
会查看上下文堆栈。如果最顶层的上下文不是 CTXT_BUILTIN
类型,则使用它来提供调用,否则下一个上下文提供调用。这意味着当从基本函数或 .Internal
调用这些函数时,推断的调用将不是对基本函数/.Internal
的调用,而是对调用基本函数/.Internal
的函数的调用。这正是人们对 .Internal
所期望的,因为它将提供对闭包包装器的调用。(此外,对于 .Internal
,调用是 .Internal
的参数,因此可能不对应于任何 R 函数。)但是,对于基本函数来说,这不太可能是需要的。
简而言之,warningcall
和 errorcall
通常用于从基本函数调用的代码,而 warning
和 error
则用于从 .Internal
(以及必然地从 .Call
、.C
等,其中调用不会向下传递)调用的代码。但是,有两个复杂情况。一是代码可能从基本函数或 .Internal
中调用,在这种情况下,warningcall
可能更合适。另一个涉及替换函数,其中调用曾经是
> length(x) <- y ~ x Error in "length<-"(`*tmp*`, value = y ~ x) : invalid value
这对于最终用户来说是不可接受的。对于替换函数,堆栈顶部将有一个合适的上下文,因此应使用 warning
。(对于 .Internal
替换函数(如 substr<-
)的结果并不理想。)
[本节目前为初步草案,不应视为最终版本。描述假设 R_NO_METHODS_TABLES
未设置。]
S4 对象可以是任何 SEXPTYPE
。它们要么是具有 S4 类信息的简单类型对象(如原子向量或函数),要么是 S4SXP
类型。在所有情况下,都会设置“S4 位”(“通用”字段的第 4 位),并且可以通过宏/函数 IS_S4_OBJECT
进行测试。
S4 对象是通过 new()
12 创建的,然后通过 C 函数 R_do_new_object
创建。这会复制类的原型,添加一个类属性并设置 S4 位。所有 S4 类属性都应该是长度为 1 的字符向量,其属性以字符字符串的形式给出包含类定义的包(或 .GlobalEnv
)的名称。由于 S4 对象具有类属性,因此会设置 OBJECT
位。
目前尚不清楚如果从 S4 对象中删除类属性会发生什么,或者是否允许这样做。
S4 类存储为 R 对象,位于创建它们的的环境中,名称为 .__C__classname
:因此,它们默认情况下不会被 ls
列出。
这些对象是 S4 对象,属于 "classRepresentation"
类,该类在 methods 包中定义。
由于这些只是对象,它们受正常的范围规则约束,可以像其他对象一样从命名空间导入和导出。指令 importClassesFrom
和 exportClasses
只是方便地引用类对象,而无需知道它们的内部“元名称”(尽管 exportClasses
通过 isClass
进行一些完整性检查)。
方法的详细信息存储在环境中(通常隐藏在各自的命名空间中),具有非语法名称,形式为 .__T__generic:package
,包含 MethodDefinition
类的对象,用于当前环境中为从特定包(可能是 .GlobalEnv
)派生的命名泛型定义的所有方法。这有时被称为“方法表”。
例如,
length(nM <- asNamespace("Matrix") ) # 941 for Matrix 1.2-6 length(meth <- grep("^[.]__T__", names(nM), value=TRUE))# 107 generics with methods length(meth.Ops <- nM$`.__T__Ops:base‘) # 71 methods for the ’Ops' (group)generic head(sort(names(meth.Ops))) ## "abIndex#abIndex" ... "ANY#ddiMatrix" "ANY#ldiMatrix" "ANY#Matrix"
在 R 会话期间,每个非原始泛型都与一个环境相关联,该环境包含对象 .AllMTable
、.Generic
、.Methods
、.MTable
、.SigArgs
和 .SigLength
。 .MTable
和 AllMTable
是合并的方法表,分别包含直接定义和通过继承定义的所有方法。 .Methods
是一个合并的方法列表。
从命名空间导出方法比导出类更复杂。首先要注意,您不是导出方法,而是指令 exportMethods
将导出命名空间中为指定泛型定义的所有方法:代码还将任何直接导出的泛型添加到泛型列表中。对于通过 exportMethods
列出或自身导出的泛型,相应的环境将被导出,因此将出现在包环境中(作为隐藏对象)。
对于内部为 S4 通用函数(见下文)的原始函数,其方法始终会被导出,无论是否在 NAMESPACE 文件中提及。
方法可以通过 importMethodsFrom
指令或通过 import
导入命名空间来导入。此外,如果通用函数通过 importFrom
导入,其方法也会被导入。在所有情况下,如果通用函数在命名空间中,它将被导入,因此 importMethodsFrom
最适合用于定义在其他包中的通用函数上的方法。由于通用函数的方法可以从多个不同的包中导入,因此方法表会合并。
当一个包被附加时,会调用 methods:::cacheMetaData
来更新内部表:只有可见的方法会被缓存。
本节不讨论如何选择 S4 方法:请参见 https://developer.r-project.org/howMethodsWork.pdf。
对于除原始函数之外的所有函数,在现有函数(本身不是 S4 通用函数)上设置方法会在当前环境中创建一个新对象,该对象是对 standardGeneric
的调用,旧定义作为默认方法。此类 S4 通用函数也可以通过调用 setGeneric
13 来创建,并且是 R 语言中的标准闭包,其环境是创建它们的環境。随着命名空间的出现,这有点问题:如果 myfn
之前在一个具有命名空间的包中,那么在搜索路径上将有两个名为 myfn
的函数,哪个函数被调用取决于哪个搜索路径正在使用。对于基本命名空间中的函数,这一点最为明显,因为原始函数将在来自任何其他包的 newly created function 之前被找到。
出于效率原因,原始函数的处理方式完全不同:这会导致不同的语义。 setGeneric
不允许用于原始函数。 methods 命名空间包含一个名为 .BasicFunsList
的列表,该列表以原始函数命名:条目要么是 FALSE
,要么是显示有效定义的标准 S4 通用函数。当调用 setMethod
(或 setReplaceMethod
)时,它要么失败(如果列表条目是 FALSE
),要么在列表中给出的有效通用函数上设置方法。
几乎所有基本类型的 S4 方法的实际分派都依赖于 S3 分派机制,因此 S4 方法只能为内部是 S3 通用类型的基本类型分派。当一个内部是 S3 通用的基本类型被调用,其第一个参数是 S4 对象,并且 S4 分派处于开启状态(即 methods 命名空间已加载)时,DispatchOrEval
会调用 R_possible_dispatch
(定义在文件 src/main/objects.c 中)。(S3 组通用类型的成员,包括所有通用运算符,处理方式略有不同:会检查前两个参数并调用 DispatchGroup
。)R_possible_dispatch
首先检查一个内部表,以查看是否有任何 S4 方法为该通用类型设置(并且 S4 分派当前为该通用类型启用),如果有,则使用存储在另一个内部表中的方法进行 S4 分派。所有基本类型都在基本命名空间中,这种机制意味着可以为(某些)基本类型设置 S4 方法,并且这些方法将始终被使用,这与为非基本类型设置方法形成对比。
例外是 %*%
,它是 S4 通用类型,但不是 S3 通用类型,因为它的 C 代码包含对 R_possible_dispatch
的直接调用。
基本类型 as.double
是特殊的,因为 as.numeric
和 as.real
是它的副本。 methods 包代码部分通过名称引用通用类型,部分通过函数引用,并将 as.double
和 as.real
映射到 as.numeric
(因为这是导出其方法的包使用的名称)。
语言的一些元素被实现为基本类型,例如 }
。这包括子集和子赋值“函数”,它们是 S4 通用类型,同样依赖于 S3 分派。
.BasicFunsList
在安装 methods 时生成,通过计算所有基本类型,最初禁止对所有基本类型使用方法,然后为 .GenericArgsEnv
的成员、S4 组通用类型和文件 BasicFunsList.R 中的简短例外列表设置通用类型:目前这包含子集和子赋值运算符以及对 c
的覆盖。
R 的内存分配几乎全部通过文件 src/main/memory.c 中的例程完成。
R 的其余部分应尽可能使用文件 src/main/memory.c 提供的分配器,这也是 Writing R Extensions 中 内存分配 部分推荐在 R 包中使用的方法,即使用 R_alloc
、R_Calloc
、R_Realloc
和 R_Free
。由 R_alloc
分配的内存将在调用 vmaxset
后重置“水位线”时由垃圾收集器释放。这由调用基本函数和 .Internal
函数的包装代码(以及 .Call
和 .External
的包装代码)自动完成,但 vmaxget
和 vmaxset
可用于在内部代码中重置水位线,如果内存仅在短时间内需要。
到目前为止提到的所有内存分配方法都比较昂贵。所有 R 平台都支持 alloca
,并且在几乎所有情况下14,这是由编译器管理的,在 C 栈上分配内存,效率很高。
使用 alloca
有两个缺点。首先,它很脆弱,需要小心避免写入(甚至读取)返回的分配块的边界之外。其次,它增加了 C 栈溢出的风险。建议仅将其用于较小的分配(最多数万字节),并且
R_CheckStack();
在分配后立即调用(因为 R 的栈检查机制会在距离栈限制足够远的地方发出警告,以允许适度使用 alloca)。(文件 src/main/unique.c 中的 do_makeunique
提供了这两个方面的示例。)
R_CheckStack2(size_t extra);
在尝试分配 extra
字节之前立即调用。
另一种策略已用于各种需要不同大小(但通常很小)的中间存储块的函数,并且已合并到头文件 src/main/RBufferUtils.h 中的例程中。这使用了一个包含缓冲区、当前大小和默认大小的结构。调用
R_AllocStringBuffer(size_t blen, R_StringBuffer *buf);
将 buf->data
设置为至少 blen+1
字节的内存区域。至少使用默认大小,这意味着对于小的分配,可以使用相同的缓冲区。调用 R_FreeStringBufferL
会释放内存(如果分配的内存超过默认大小),而调用 R_FreeStringBuffer
会释放任何分配的内存。
需要初始化 R_StringBuffer
结构,例如:
static R_StringBuffer ex_buff = {NULL, 0, MAXELTSIZE};
它使用 MAXELTSIZE = 8192
字节的默认大小。大多数当前使用情况都具有静态的 R_StringBuffer
结构,这允许在调用例如 grep
以及函数之间共享(默认大小的)缓冲区:如果 R 允许并发评估线程,则需要更改此行为。因此,惯用法是
static R_StringBuffer ex_buff = {NULL, 0, MAXELTSIZE}; ... char *buf; for(i = 0; i < n; i++) { compute len buf = R_AllocStringBuffer(len, &ex_buff); use buf } /* free allocation if larger than the default, but leave default allocated for future use */ R_FreeStringBufferL(&ex_buff);
R_alloc
使用的内存被分配为 R 向量,类型为 RAWSXP
。因此,分配以 8 字节为单位,并向上取整。目前,对零字节的请求返回 NULL
(但不应依赖于此)。出于历史原因,在所有其他情况下,在向上取整之前添加 1 字节,因此分配始终比请求的字节数多 1-8 字节:同样,也不应依赖于此。
分配的向量通过设置 R_VStack
来保护,因为垃圾收集器会标记从该位置可以访问的所有内容。当向量被 R_alloc
分配时,其 ATTRIB
指针被设置为当前的 R_VStack
,而 R_VStack
被设置为最新的分配。因此,R_VStack
是通过 R_alloc
当前分配的向量的单链表。函数 vmaxset
重置位置 R_VStack
,并且应该设置为之前通过 vmaxget
获得的值:在获得该值之后进行的分配将不再受到保护,因此可供垃圾收集。
本节记录系统对这些环境的已知使用情况:目的是尽量减少或消除此类使用。
图形设备系统在基本环境中维护两个变量 .Device
和 .Devices
:这两个变量始终被设置。变量 .Devices
给出一个打开设备名称的字符向量列表,而 .Device
是对应于当前活动设备的元素。空设备将始终处于打开状态。
似乎存在一个变量 .Options
,它是一个配对列表,用于提供当前选项设置。但实际上,它只是一个带有赋值的符号,因此显示为基本变量。
类似地,求值器创建一个符号 .Last.value
,它在基本环境中显示为变量。
错误可能会在基本环境中产生对象 .Traceback
和 last.warning
。
随机数生成器的种子存储在全局环境中的对象 .Random.seed
中。
一些错误处理程序可能会在全局环境中产生对象:例如,dump.frames
默认情况下会产生 last.dump
。
windows()
设备使用变量 .SavedPlots
来存储已保存绘图的显示列表,以便稍后显示。这被视为用户创建的变量。
R 使用存储在 modules 目录中的多个共享对象/DLL。这些是代码的一部分,已选择按需加载,而不是作为动态库链接或合并到主可执行文件/动态库中。
对于其余模块,其动机是它们将通过链接到的库引入的(通常是可选的)代码量。via
internet
内部 HTTP 和 FTP 客户端以及套接字支持,它们链接到特定于系统的支持库。这可能会加载 libcurl
,并且在 Windows 上将加载 wininet.dll 和 ws2_32.dll。
lapack
使用 LAPACK 库的代码,并链接到 libRlapack 或外部 LAPACK 库。
X11
(仅限类 Unix 系统。)X11()
、jpeg()
、png()
和 tiff()
设备。这些是可选的,并链接到一些或所有 X11
、pango
、cairo
、jpeg
、libpng
和 libtiff
库。
我们利用了在 控制可见性 中讨论的可见性机制,在 Writing R Extensions 中,不需要在主 R 可执行文件/动态库(特别是任何包或模块)之外使用的 C 入口点应该以 attribute_hidden
为前缀。 最小化 R 动态库中符号的可见性将加快与其链接的速度(包将执行此操作)并减少链接到相同名称的错误入口点的可能性。此外,在某些平台上,减少入口点的数量允许使用更有效的 PIC 版本:大约一半以上的入口点被隐藏。隐藏变量(与函数不同)的一种便捷方法是在头文件 Defn.h 中将它们声明为 extern0
。
所使用的可见性机制仅在某些编译器和平台上可用,尤其是在 Windows 上不可用,在 Windows 上使用替代机制。如果入口点在文件 src/gnuwin32/Rdll.hide 中列出,则它们将不会在 R.dll 中提供。 该文件中的条目以空格开头,并且必须严格按照 C 本地化的字母顺序排列(如果您更改它,请使用 sort
对文件进行排序以确保这一点)。可以通过此文件隐藏 Fortran 和 C 入口点:前者为小写并以下划线作为后缀,并且应在文件中包含后缀名称。某些入口点仅存在于 Windows 上或仅需要在 Windows 上可见,文件 src/gnuwin32/Maintainters.notes 中提供了一些关于这些入口点的说明。
由于减少可见入口点数量的优势,应尽可能地将它们声明为 attribute_hidden
。请注意,这仅对共享 R 库构建有效,因此需要小心不要隐藏包合法使用的入口点。因此,最好在创建新的入口点时做出关于可见性的决定,包括是否应将其包含在头文件 Rinternals.h 中。在共享 R 库构建中,在相当标准的类 Unix 系统上,可见入口点的列表可以通过以下方式创建:
nm -g libR.so | grep ‘ [BCDT] ’ | cut -b20-
Windows 的独特之处在于它在传统上将导入变量的处理方式与函数区分开来:从 DLL 导入的变量在链接到(“导入”)时需要指定前缀(通常为 ‘_imp_’),但在链接自(“导出”)时则不需要。具体细节取决于编译器系统,并且在 MinGW 的生命周期中发生了变化。它们主要隐藏在头文件 R_ext/libextern.h 中定义的一些宏后面。
在主 R 源代码中,需要在 R.dll 之外(在包、模块或其他 DLL 中,例如 Rgraphapp.dll)引用的(非函数)变量应使用前缀 LibExtern
声明。主要用途是在 Rinternals.h 中,但需要考虑任何公共头文件以及 Defn.h。
如今,可以使用 MinGW 移植的 ld
的“自动导入”功能来修复来自 DLL 的导入(如果 R 是为 Cygwin 平台构建的,则会发生这种情况)。但是,在 1998 年左右首次构建 R 的 MinGW 版本时,这还不可行,它允许对可见性进行更少的控制,并且不适用于其他 Windows 编译器套件。
只有在 Windows 上编译 R 源代码时才能检查是否已正确处理此问题。
延迟加载始终用于包中的代码,但对于包中的数据集是可选的(由包维护者选择)。当使用延迟加载的包/命名空间被加载时,包/命名空间环境将填充所有命名对象的承诺:当这些承诺被评估时,它们将从数据库中加载实际的代码。
代码和数据有单独的数据库,存储在 R 和 data 子目录中。数据库由两个文件组成,name.rdb 和 name.rdx。 .rdb 文件是序列化对象的串联,而 .rdx 文件包含索引。对象存储在(通常)gzip
压缩格式中,带有 4 字节的头部,给出未压缩的序列化长度(以 XDR 格式,即大端字节序),并通过调用原始 lazyLoadDBfetch
函数读取。(请注意,这使得延迟加载不适合非常大的对象:R 对象的未序列化长度可能超过 4GB。)
索引或“映射”文件 name.rdx 是一个压缩的序列化 R 对象,由 readRDS
读取。它是一个包含三个元素的列表:variables
、references
和 compressed
。前两个是长度为 2 的整数向量的命名列表,给出序列化对象在 name.rdb 文件中的偏移量和长度。元素 variables
为每个命名对象提供一个条目:references
序列化一个临时环境,该环境在将命名环境添加到数据库时使用。 compressed
是一个逻辑值,指示序列化对象是否被压缩:如今总是使用压缩。我们后来添加了值 compressed = 2
和 3
用于 bzip2
和 xz
压缩(将来可能扩展到其他方法):这些格式在标头中添加第五个字节以表示压缩类型,并在压缩扩展序列化对象时以未压缩形式存储它们。
出于性能原因,源引用被特殊处理:来自 srcfile
环境的绑定 lines
和 parseData
是延迟加载的。这使用了一种机制,允许从环境中延迟加载选定的绑定。此类环境的键是一个包含两个元素的列表:eagerKey
给出用于急切加载的绑定的长度为 2 的整数键,而 lazyKeys
给出一个长度为 2 的整数键向量,每个延迟加载的绑定一个。
用于代码或数据的延迟加载数据库的加载器是 base 包中的函数 lazyLoad
,但请注意,在文件 R_HOME/base/R/base 中有一个单独的副本用于加载 base 本身。
延迟加载数据库由 src/library/tools/R/makeLazyLoad.R 中的代码创建:主要工具是未导出的函数 makeLazyLoadDB
,数据库条目的插入通过调用 .Call("R_lazyLoadDBinsertValue", ...)
完成。
小于 10MB 的延迟加载数据库在第一次使用时会缓存在内存中:在使用具有高延迟的文件系统(可移动设备和 Windows 上的网络挂载文件系统)时发现这很有必要。
延迟加载数据库被加载到包的导出中,但不会加载到命名空间环境本身。因此,当包被 附加 时,它们是可见的,并且也 通过 ::
运算符可见。这是一个有意设计的决定,因为包主要使数据集可供最终用户(或其他包)使用,并且它们不应该优先从包中的函数中找到,这会让期望使用正常搜索路径的用户感到惊讶。(存在一种替代机制,sysdata.rda,用于主要用于包内的“系统数据集”。)
相同的数据库机制用于存储解析后的 Rd 文件。通过调用 tools:::fetchRdDB
获取一个或所有解析后的对象。
.Internal
vs .Primitive
¶在构建时编译到 R 的 C 代码可以直接在称为 primitives 的地方调用,或者通过 .Internal
接口调用,该接口与 .External
接口非常相似,只是语法不同。更准确地说,R 维护着一个 R 函数名称和对应 C 函数的调用表,该表按照惯例都以 ‘do_’ 开头并返回一个 SEXP
。该表(R_FunTab
在文件 src/main/names.c 中)还指定了函数需要或允许多少个参数,参数是否在调用之前进行评估,以及函数是否是“内部”的,即它必须通过 .Internal
接口访问,或者直接访问,在这种情况下它在 R 中打印为 .Primitive
。
使用 .Internal()
并封装在闭包中的函数通常是首选,因为这确保了对命名参数和默认参数的标准处理。例如,grep
定义为
grep <- function (pattern, x, ignore.case = FALSE, perl = FALSE, value = FALSE, fixed = FALSE, useBytes = FALSE, invert = FALSE) { if (!is.character(x)) x <- structure(as.character(x), names = names(x)) .Internal(grep(as.character(pattern), x, ignore.case, value, perl, fixed, useBytes, invert)) }
使用 as.character
允许调度方法(例如,对于因子)。
但是,出于方便和效率的考虑(因为在使用封装在函数闭包中的 .Internal
接口时存在一些开销),原始函数是例外,可以直接访问。当然,原始函数是基本操作所必需的——例如 .Internal
本身就是一个原始函数。请注意,原始函数不使用 R 代码,因此与通常的解释函数有很大不同。特别是,对于此类对象,formals
和 body
返回 NULL
,并且参数匹配可以以不同的方式处理。对于某些原始函数(包括 call
、switch
、.C
和 .subset
),位置匹配对于避免第一个参数的局部匹配非常重要。
原始函数列表可能会发生变化;目前,它包括以下内容。
{ ( if for while repeat break next return function quote switch
foo(a, b, ...)
调用的函数)用于子集、赋值、算术、比较和逻辑
[ [[ $ @ <- <<- = [<- [[<- $<- @<- + - * / ^ %% %*% %/% < <= == != >= > | || & && !
当算术、比较和逻辑运算符作为函数调用时,任何参数名称都会被丢弃,因此使用位置匹配。
abs sign sqrt floor ceiling
exp expm1 log2 log10 log1p cos sin tan acos asin atan cosh sinh tanh acosh asinh atanh cospi sinpi tanpi
gamma lgamma digamma trigamma
cumsum cumprod cummax cummin
Im Re Arg Conj Mod
log
是一个具有一个或两个参数的原始函数,具有命名参数匹配。
trunc
是一个困难的案例:它是一个可以有一个或多个参数的原始函数:原始函数中处理的默认方法只有一个。
nargs missing on.exit interactive as.call as.character as.complex as.double as.environment as.integer as.logical as.raw is.array is.atomic is.call is.character is.complex is.double is.environment is.expression is.finite is.function is.infinite is.integer is.language is.list is.logical is.matrix is.na is.name is.nan is.null is.numeric is.object is.pairlist is.raw is.real is.recursive is.single is.symbol baseenv emptyenv globalenv pos.to.env unclass invisible seq_along seq_len
browser proc.time gc.time tracemem retracemem untracemem
length length<- class class<- oldClass oldClass<- attr attr<- attributes attributes<- names names<- dim dim<- dimnames dimnames<- environment<- levels<- storage.mode<-
请注意,优化NAMED = 1
仅在原始函数内部有效(因为.Internal
的闭包包装器将在评估对参数的承诺时设置NAMED = NAMEDMAX
),因此替换函数应尽可能为原始函数以避免复制(至少在它们的默认方法中)。[NAMED
机制已被引用计数取代。]
: ~ c list call expression substitute UseMethod standardGeneric .C .Fortran .Call .External round signif rep seq.int
以及以下仅供内部使用的函数
.Primitive .Internal .Call.graphics .External.graphics .subset .subset2 .primTrace .primUntrace lazyLoadDBfetch
多参数原始函数
call switch .C .Fortran .Call .External
故意使用位置匹配,并且需要这样做以避免对其第一个参数进行部分匹配。它们确实检查第一个参数是否未命名,或者对于前两个参数,是否部分匹配形式参数名称。另一方面,
attr attr<- browser rememtrace substitute UseMethod log round signif rep seq.int
管理自己的参数匹配并在标准方式下工作。
所有单参数原始函数都检查,如果它们被命名参数调用,则此参数(部分)匹配文档中给出的名称:这也适用于具有一个参数加value
的替换函数。
最终效果是,为最终用户使用作为函数的原始函数进行参数匹配的方式与解释函数相同,除了六个需要位置匹配的例外情况。
少数基本类型是特殊的,而不是内置的,也就是说它们以未求值的参数输入。对于语言结构和赋值运算符,以及对于&&
和||
(它们有条件地求值其第二个参数),以及~
,.Internal
,call
,expression
,missing
,on.exit
,quote
和substitute
(它们不求值其某些参数),这显然是必要的。
rep
和seq.int
是特殊的,因为它们根据哪些参数不缺失来求值其某些参数。
log
,round
和signif
是特殊的,以允许为缺失参数提供默认值。
子集、子赋值和@
运算符都是特殊的。(对于提取和替换形式,$
和@
接受符号参数,而[
和[[
允许缺失参数。)
UseMethod
是特殊的,以避免对内置函数调用添加额外的上下文。
还有一些特殊的.Internal
函数:NextMethod
,Recall
,withVisible
,cbind
,rbind
(以允许使用deparse.level
参数),eapply
,lapply
和vapply
。
基本函数和运算符的原型可用,这些原型用于打印、args
和包检查(例如,由tools::checkS3methods
和包codetools)。在base包(和命名空间)中,有两个环境:‘.GenericArgsEnv’用于那些作为内部 S3 泛型的基本类型,以及‘.ArgsEnv’用于其余的基本类型。这些环境包含与基本类型同名的闭包,从帮助页面(手动)派生的形式参数,一个作为对UseMethod
或NULL
的适当调用的主体,以及基本命名空间的环境。
用于 print.default
和 args
的 C 代码优先使用这些环境中的闭包,而不是 base 中的定义(作为基本类型)。
QC 函数 undoc
检查这些环境中原型化的所有函数当前是否都是基本类型,以及未包含的基本类型是否更适合被视为语言元素(在撰写本文时)。
$ $<- && ( : @ @<- [ [[ [[<- [<- { || ~ <- <<- = break for function if next repeat return while
人们可能会争论 ~
,但它为解析器所知,其语义与普通函数完全不同。而 :
在其两种含义中使用不同的参数名称进行记录。
QC 函数 codoc
和 checkS3methods
也使用这些环境(实际上将它们放在 base 的搜索路径之前),因此它们包含的函数的形式参数由 codoc
根据帮助页面进行检查。但是,通用基本类型存在两个问题。第一个问题是,许多运算符是 S3 组通用 Ops
的一部分,并且定义了它们的参数为 e1
和 e2
:虽然这很不寻常,但运算符可以像 "+"(e1=a, e2=b)
那样调用,如果方法调度发生在闭包上,则会发生参数名称不匹配。因此,环境 .GenericArgsEnv
中的定义必须使用参数名称 e1
和 e2
,即使传统文档使用的是 x
和 y
:codoc
通过 tools:::.make_S3_primitive_generic_env
进行适当的调整。第二个差异在于 Math
组通用类型,其中组通用类型定义的参数列表为 (x, ...)
,但大多数成员在用作默认方法时只允许一个参数(而 round
和 signif
允许两个作为默认方法):同样使用修复程序。
那些位于 .GenericArgsEnv
中的基本类型通过定义它们的方法进行检查(通过 tests/primitives.R)以确定它们是否为通用类型,并且通过设置方法并检查它是否未被调度到(但这可能因其他原因而失败)来检查剩余的基本类型可能不是通用类型。但是,除了阅读源代码之外,没有确定的方法可以知道其他 .Internal
或基本函数是否在内部是通用类型。
[供 R 核心使用:反转此过程以移除一个基本类型。最常见的情况是将一个 .Internal
更改为基本类型或反之。]
基本类型列在 src/main/names.c 中的表 R_FunTab
中:基本类型在 ‘eval’ 字段中具有 ‘Y = 0’。
在 base 包中的帮助文件中需要有一个 ‘\alias’ 条目,并且需要将基本类型添加到本节开头列出的列表之一中。
一些基本类型被视为语言元素(当前列出的元素)。这些需要添加到两个异常列表中,langElts
在 undoc()
中(在文件 src/library/tools/R/QC.R 中)和 lang_elements
在 tests/primitives.R 中。
所有其他基本类型都被视为函数,应列在 src/library/base/R/zzz.R 中定义的环境之一中,即 .ArgsEnv
或 .GenericArgsEnv
:内部泛型也需要列在字符向量 .S3PrimitiveGenerics
中。还要注意上面关于参数匹配的讨论:如果您通过转换 .Internal
添加了一个具有多个参数的基本类型函数,则需要在 C 代码中添加参数匹配,对于那些只有一个参数的函数,则需要添加参数名称检查。
请确保已运行 make check-devel
:它测试了这些要求中的大多数。
在 R 包中标记消息(错误、警告等)以进行翻译的过程在 Writing R Extensions 中的 国际化 中进行了描述,并且与 R 一起提供的标准包(除了 grDevices 中的 windows()
设备菜单的例外)以与其他包相同的方式进行了国际化。
R 代码的国际化与扩展包的国际化方式完全相同。由于所有包含 R 代码的标准包也都有命名空间,因此无需指定 domain
,但为了效率,当消息通过 gettextf
、gettext
或 ngettext
构建时,对 message
、warning
和 stop
的调用应包含 domain = NA
。
对于每个包,提取的消息和翻译源存储在源包的包目录 po 下,编译后的翻译存储在 inst/po 下,以便安装到已安装包的包目录 po 下。这也适用于包中的 C 代码。
主 C 代码(例如,在文件 src/*/*.c 和模块中的代码)是 R 最接近于为其编写 ‘gettext’ 的应用程序类型的地方。主 C 代码中的消息位于域 R
中,并存储在顶级目录 po 下,编译后的翻译存储在 share/locale 下。
R 域涵盖的文件列表在文件 po/POTFILES.in 中指定。
标记要翻译的消息的正常方法是使用 _("msg")
,就像包一样。但是,有时需要标记要翻译的段落,而不想在此时进行翻译,例如,在声明字符串常量时。这就是 N_
宏的用途,例如
{ ERROR_ARGTYPE, N_("invalid argument type")},
来自文件 src/main/errors.c。
宏 P_
#ifdef ENABLE_NLS #define P_(StringS, StringP, N) ngettext (StringS, StringP, N) #else #define P_(StringS, StringP, N) (N > 1 ? StringP: StringS) #endif
可用作 ngettext
的包装器:但是,在某些情况下,首选方法是使用 ngettext
对代码进行条件化(在 ENABLE_NLS
上)。
宏 _("msg")
可以安全地用于目录 src/appl 中;独立 ‘nmath’ 的头文件跳过了可能的翻译。(这并不适用于 N_
或 P_
)。
Windows GUI 的消息位于单独的域“RGui”中。这样做有两个原因:
iconv
在 Windows 下运行良好,因此这不像预期的那样重要。)
“RGui”域的消息由 G_("msg")
标记,这是一个在头文件 src/gnuwin32/win-nls.h 中定义的宏。要考虑的文件列表在文件 src/library/tools/R/translations.R 中的函数 update_RGui_po
中硬编码,该函数通过文件 po/Makefile.win 中的 update-RGui
目标调用:请注意,这包括 devWindows.c,因为 windows
设备上的菜单被认为是 GUI 的一部分。(还有 GN_("msg")
,它是 N_("msg")
的类似物。)
“RGui”域的模板和消息目录位于包 base 的顶层 po 目录中。
已安装包的结构在 Writing R Extensions 中的 创建 R 包 中进行了描述:本章关注的是 已安装 包的结构。
已安装包具有顶级文件 DESCRIPTION,该文件是包源代码中同名文件的副本,并附加了“Built”字段,以及文件 INDEX,通常描述可获得帮助的对象,如果包具有命名空间,则还有一个文件 NAMESPACE,以及可选文件,例如 CITATION、LICENCE 和 NEWS,以及从 inst 中复制的任何其他文件。它将具有目录 Meta、help 和 html(即使包没有帮助页面),几乎总是具有目录 R,并且通常具有目录 libs 来包含编译后的代码。其他对 R 具有已知含义的目录是 data、demo、doc 和 po。
函数 library
会查找命名空间,如果找到命名空间,则将控制权传递给 loadNamespace
。然后,library
或 loadNamespace
会查找文件 R/pkgname,如果找不到,则会发出警告,否则会将代码(使用 sys.source
)源到包的环境中,然后如果存在,则会延迟加载数据库 R/sysdata。因此,R 代码的加载方式取决于 R/pkgname 的内容:share/R/nspackloader.R 中提供了用于加载延迟加载数据库的标准模板。
编译后的代码通常在包的命名空间通过 NAMESPACE 文件中的 useDynlib
指令或包的 .onLoad
函数加载时加载。按照惯例,编译后的代码通过调用 library.dynam
加载,该函数在目录 libs(如果使用子体系结构,则在适当的子目录中)中查找共享对象(类 Unix)或 DLL(Windows)。
子目录 data 具有两个目的。在使用延迟加载数据的包中,它包含一个延迟加载数据库 Rdata,以及一个文件 Rdata.rds,该文件包含一个命名字符向量,在(不常见)事件中,如果它用于此类包,则由 data()
使用。否则,它是源代码中 data 目录的副本,如果使用了 R CMD INSTALL --resave-data
,则保存的图像将被重新压缩。
子目录 demo 支持 demo
函数,并从源代码中复制而来。
子目录 po 包含(在子目录中)编译后的消息目录。
目录 Meta 包含几个 .rds
格式的文件,即由 saveRDS
写入的序列化 R 对象。所有包都有文件 Rd.rds, hsearch.rds, links.rds, features.rds 和 package.rds。具有命名空间的包有一个文件 nsInfo.rds,而具有数据、演示或小插图的包则有 data.rds, demo.rds 或 vignette.rds 文件。
这些文件的结构(以及它们的存在和名称)对 R 来说是私有的,因此这里的描述是针对那些试图跟踪 R 源代码的人:在非基础包中不应该引用这些文件。
文件 package.rds 是从 DESCRIPTION 文件中提取的信息的转储。它是一个包含多个组件的列表。第一个是“DESCRIPTION”,是一个字符向量,表示 read.dcf
读取的 DESCRIPTION 文件。后面的元素“Depends”、“Suggests”、“Imports”、“Rdepends” 和 “Rdepends2” 记录了“Depends”、“Suggests” 和 “Imports” 字段。这些都是列表,可以为空。前三个列表中每个包都有一个条目,每个条目都是一个长度为 1 或 3 的列表,其中包含元素“name”(包名)以及可选元素“op”(一个字符字符串)和“version”(一个类为“"package_version"”的对象)。元素“Rdepends” 用于第一个 R 版本依赖项,而“Rdepends2” 是一个包含零个或多个 R 版本依赖项的列表,每个依赖项都是一个三元素列表,其形式与包的描述相同。元素“Rdepends” 已经不再使用,但它仍然可能需要,以便 R < 2.7.0 可以检测到该包没有为其安装。
文件 nsInfo.rds 记录了一个列表,它是 NAMESPACE 文件的解析版本。
文件 Rd.rds 记录了一个数据框,其中每行对应一个帮助文件。列包括“File”(带扩展名的文件名)、“Name”(“\name” 部分)、“Type”(来自可选的“\docType” 部分)、“Title”、“Encoding”、“Aliases”、“Concepts” 和 “Keywords”。除了“Aliases” 是一个字符向量列表之外,所有列都是字符向量。
文件 hsearch.rds 记录了由 ‘help.search’ 使用的信息。这是一个包含四个未命名元素的列表,它们是帮助文件、别名、关键字和概念的字符矩阵。所有矩阵都有 ‘ID’ 和 ‘Package’ 列,用于将别名、关键字和概念(后三个元素的剩余列)与特定的帮助文件关联。第一个元素还有 ‘LibPath’(存储为 ""
并填充文件加载的位置)、‘name’、‘title’、‘topic’(第一个别名,用于在将结果呈现为 ‘pkgname::topic’ 时使用)和 ‘Encoding’ 列。
文件 links.rds 记录了一个命名的字符向量,名称是别名,值是形式为
"../../pkgname/html/filename.html"
文件 data.rds 记录了一个包含两列的字符矩阵,列分别是数据集名称和对应帮助文件中的标题。文件 demo.rds 对包演示具有相同的结构。
文件 vignette.rds 记录了一个数据框,每个 ‘vignette’ (.[RS]nw 文件在 inst/doc 中) 对应一行,并包含 ‘File’(源代码中的完整文件路径)、‘Title’、‘PDF’(已安装的 PDF 版本的无路径文件名,如果存在)、‘Depends’、‘Keywords’ 和 ‘R’(已安装的 R 代码的无路径文件名,如果存在)列。
所有已安装的包,无论它们是否有任何 .Rd 文件,都具有 help 和 html 目录。后者通常只包含单个文件 00Index.html,即包索引,其中包含指向帮助主题(如果有)的超链接。
目录 help 包含文件 AnIndex、paths.rds 和 pkgname.rd[bx]。后两个文件是解析后的 .Rd 文件的延迟加载数据库,由 tools:::fetchRdDB
访问。文件 paths.rds 是保存的 .Rd 文件的原始路径名称的字符向量,用于更新数据库时使用。
文件 AnIndex 是一个两列制表符分隔的文件:第一列包含帮助文件中定义的别名,第二列包含包含该别名的文件的基名(不带 .Rd 或 .rd 扩展名)。它由 utils:::index.search
读取以搜索与主题(别名)匹配的文件,并由 scan
在 utils:::matchAvailableTopics
中读取,它是完成系统的一部分。
文件 aliases.rds 与 AnIndex 中的信息相同,但以命名字符向量形式表示(名称为主题,值为文件名基名),以便更快地访问。
R 提供了许多函数来处理文件和目录:其中许多函数是最近添加的,以方便在 R 中进行脚本编写,特别是用 R 脚本替换 Perl 脚本以管理 R 本身。
这些函数由标准 C/POSIX 库调用实现,Windows 除外。这意味着文件名必须以当前区域设置编码,因为操作系统没有其他方法访问文件系统:越来越多的文件名以 UTF-8 存储,操作系统将在其他区域设置中将文件名转换为 UTF-8。因此,使用 UTF-8 区域设置可以透明地访问整个文件系统。
Windows 是另一个故事。在那里,文件名的内部视图是 UTF-16LE(所谓的“Unicode”),标准 C 库调用只能访问其名称可以用当前代码页表示的文件。为了规避此限制,有一组并行的 Windows 特定调用,它们接受用于文件路径的宽字符参数。R 中的大部分文件处理已迁移到使用这些函数,因此文件名可以在 R 中以 UTF-8 编码的字符字符串进行操作,转换为宽字符(在 Windows 上为 UTF-16LE),然后传递给操作系统。实用程序 RC_fopen
和 filenameToWchar
有助于此过程。目前,file.copy
到目录、list.files
、list.dirs
和 path.expand
仅适用于以当前代码页编码的文件路径。
所有这些函数都执行波浪号扩展,与 path.expand
相同,Sys.glob
除外。
文件名可能区分大小写,也可能不区分大小写:后者是 Windows 和 macOS 的规范,前者是其他类 Unix 系统的规范。请注意,这是操作系统和文件系统的属性:在挂载文件系统时,通常可以将名称映射到大写或小写。这会影响 list.files
和 Sys.glob
中模式的匹配。
Windows 和 macOS 上的文件名通常包含空格,但在其他系统上则没有。由于文件名在 R 中被视为字符字符串,因此空格通常不是问题,除非文件名被传递给其他进程,例如通过 system
调用。
Windows 还有另外几个特殊之处。POSIX 文件系统只有一个根目录(其他物理文件系统被挂载到该根目录下的逻辑目录),而 Windows 为每个物理或逻辑文件系统(“卷”)设置了单独的根目录,这些根目录组织在 驱动器(文件路径以 D:
开头,不区分大小写)和 网络共享(路径类似于 \netname\topdir\myfiles\a file
)下。存在一个当前驱动器,没有驱动器部分的路径名相对于当前驱动器。此外,每个驱动器都有一个当前目录,相对路径相对于该当前目录,如果指定了特定驱动器,则相对于该驱动器。因此,D:dir\file 和 D: 是有效的路径规范(最后一个是驱动器 D: 上的当前目录)。
R 的图形内部进行了重新设计,以使多个图形系统能够安装在图形“引擎”之上 - 目前有两个这样的系统,一个支持“基本”图形(基于 S 中的图形,其 R 代码15 在包 graphics 中),另一个在包 grid 中实现。
有关历史更改的一些说明可以在 https://www.stat.auckland.ac.nz/~paul/R/basegraph.html 和 https://www.stat.auckland.ac.nz/~paul/R/graphicsChanges.html 中找到。
在最低级别是图形设备,它管理绘图表面(屏幕窗口或要写入文件的表示)。它实现了一组图形原语,用于“绘制”
以及信息请求,例如
以及采取行动的请求/机会,例如
设备还设置了一些变量,主要是布尔标志,指示其功能。设备完全在“设备单位”中工作,这取决于其开发人员:它们可以是像素、大点(1/72 英寸)、缇、……,并且可以在“x”和“y”方向上有所不同16。
下一层是图形“引擎”,它是设备的主要接口(尽管图形子系统确实直接与设备通信)。它负责裁剪线、矩形和多边形,将pch
值0...26
转换为线/圆集,居中(以及其他调整)文本,渲染数学表达式(“plotmath”)并将颜色描述(例如名称)映射到内部表示。
引擎的另一个功能是管理显示列表和快照。某些(但并非所有)图形设备实例维护显示列表,这是一个“列表”,其中包含已在设备上执行的操作,以生成当前绘图(自设备打开或绘图上次清除以来,例如通过 plot.new
)。屏幕设备通常维护一个显示列表来处理重绘和调整大小事件,而基于文件格式则不维护 - 显示列表也用于实现 dev.copy()
及其相关函数。显示列表是 .Internal
(基本图形)或 .Call.graphics
(网格图形)调用的配对列表,这意味着在重放显示列表时,将重新调用实现图形操作的 C 代码:除了成功记录操作的部分之外。
当前图形状态的快照由 GEcreateSnapshot
拍摄,并在会话的后期由 GEplaySnapshot
重放。这些被 recordPlot()
、replayPlot()
和 windows()
设备的 GUI 菜单使用。“状态”包括显示列表。
顶层包含图形子系统。尽管自 2001 年左右以来,已为 24 个子系统提供支持,但目前仍然只有两个子系统存在,“基本”和“网格”。基本子系统在 R 初始化时向引擎注册,并在 R 会话关闭时注销(通过 KillAllDevices
)。网格子系统在其 .onLoad
函数中注册,并在 .onUnload
函数中注销。图形子系统也可以在快照中保存“状态”信息(目前基本子系统保存,网格子系统不保存)。
包 grDevices 最初是用来存放基本的图形设备的(尽管 X11
由于其包含的大量外部库而被放在一个单独的按需加载模块中)。从那时起,它被用于其他被认为对 grid 有用的功能,因此已从包 graphics 转移到 grDevices。这主要与颜色处理以及绘图的记录和回放有关。
R 附带了几个图形设备,并且支持第三方包提供额外的设备——现在已经有几个包提供了。本节从图形设备编写者的角度描述了设备内部结构。
内部使用两种类型,它们是指向与图形设备相关的结构的指针。
类型 DevDesc
是在头文件 R_ext/GraphicsDevice.h(由 R_ext/GraphicsEngine.h 包含)中定义的结构。它描述了设备的物理特性、设备驱动程序的功能,并包含一组回调函数,这些函数将被图形引擎用来获取有关设备的信息并启动操作(例如新页面、绘制线条或一些文本)。类型 pDevDesc
是指向此类型的指针。
当适当的默认行为将由图形引擎执行时,以下回调可以省略(或设置为空指针,它们的默认值):activate
、cap
、deactivate
、locator
、holdflush
(API 版本 9)、mode
、newFrameConfirm
、path
、raster
和 size
。
设备单位与物理尺寸之间的关系由 DevDesc
结构的元素 ipr
设置:长度为 2 的 ‘double’ 数组。
类型 GEDevDesc
是在 R_ext/GraphicsEngine.h(文件中有注释)中定义的结构,如下所示:
typedef struct _GEDevDesc GEDevDesc; struct _GEDevDesc { pDevDesc dev; Rboolean displayListOn; SEXP displayList; SEXP DLlastElt; SEXP savedSnapshot; Rboolean dirty; Rboolean recordGraphics; GESystemDesc *gesd[MAX_GRAPHICS_SYSTEMS]; Rboolean ask; }
因此,这本质上是一个设备结构,以及图形引擎维护的有关设备的信息,通常17 对引擎可见,而对设备不可见。类型 pGEDevDesc
是指向此类型的指针。
图形引擎维护一个设备数组,作为指向 GEDevDesc
结构的指针。该数组的大小为 64,但第一个元素始终被 "空设备"
占用,最后一个元素保留为 NULL 作为哨兵。18 此数组反映在 R 变量 ‘.Devices’ 中。一旦设备被杀死,其元素就会变得可用以重新分配(并且其名称将在 ‘.Devices’ 中显示为 ""
)。只有一个设备是 ‘活动的’:如果尚未打开其他设备且未被杀死,则为空设备。
每个图形设备实例都需要通过与以下代码非常相似的代码来设置一个 GEDevDesc
结构
pGEDevDesc gdd; R_GE_checkVersionOrDie(R_GE_version); R_CheckDeviceAvailable(); BEGIN_SUSPEND_INTERRUPTS { pDevDesc dev; /* Allocate and initialize the device driver data */ if (!(dev = (pDevDesc) calloc(1, sizeof(DevDesc)))) return 0; /* or error() */ /* set up device driver or free ‘dev’ and error() */ gdd = GEcreateDevDesc(dev); GEaddDevice2(gdd, "dev_name"); } END_SUSPEND_INTERRUPTS;
DevDesc
结构包含一个 void *
指针 ‘deviceSpecific’,用于存储特定于设备的数据。设置设备驱动程序包括初始化 DevDesc
结构的所有非零元素。
请注意,设备结构在分配时被清零:这提供了一些保护,防止将来扩展结构,因为图形引擎可以添加需要为非 NULL/非零才能 ‘打开’ 的元素(并且结构以 64 个保留字节结束,这些字节将被清零并允许将来扩展)。
引擎/设备 API 的版本号 R_GE_version
提供了更多保护,该版本号在 R_ext/GraphicsEngine.h 中定义,并带有访问函数
int R_GE_getVersion(void); void R_GE_checkVersionOrDie(int version);
如果图形设备调用 R_GE_checkVersionOrDie(R_GE_version)
,它可以确保它只会在提供为其设计和编译的 API 的 R 版本中使用。
DevDesc
结构还包含一个 int
‘deviceVersion’,用于指示设备支持的引擎/设备 API 的版本。如果设备驱动程序正确设置了此值,则设备驱动程序无需使用 R_GE_checkVersionOrDie(R_GE_version)
,因为图形引擎不会使用来自高于设备支持的版本的 API 的回调。
以下“功能”可以在设备的 DevDesc
结构中定义。
canChangeGamma
– Rboolean
: 是否可以调整显示器伽马值?此功能现已忽略,因为伽马支持已被移除。
canHadj
– integer
: 设备是否可以通过 text
回调进行文本水平调整,如果是,精度如何?0 = 不调整,1 = {0, 0.5, 1}(左、中、右对齐)或 2 = 在左、右对齐之间连续可变(在 [0,1] 之间)。
canGenMouseDown
– Rboolean
: 设备是否可以处理鼠标按下事件?此标志和接下来的三个标志目前未被 R 使用,但为了向后兼容而保留。
canGenMouseMove
– Rboolean
: 同上,针对鼠标移动事件。
canGenMouseUp
– Rboolean
: 同上,针对鼠标释放事件。
canGenKeybd
– Rboolean
: 同上,针对键盘事件。
hasTextUTF8
– Rboolean
: 是否应将非符号文本(以 UTF-8 格式)发送到 textUTF8
和 strWidthUTF8
回调,并以 Unicode 码点(负值)发送到 metricInfo
回调?
wantSymbolUTF8
– Rboolean
: 是否应以与其他文本相同的方式以 UTF-8 格式处理符号文本?需要 textUTF8 = TRUE
。
haveTransparency
: 设备是否支持半透明颜色?
haveTransparentBg
: 背景是否可以完全或半透明?
haveRaster
: 是否支持渲染光栅图像?
haveCapture
: 是否支持 grid::grid.cap
?
haveLocator
: 是否有交互式定位器?
deviceClip
: 引擎是否应将所有裁剪操作留给设备?
haveRaster
、haveCapture
和 haveLocator
通常可以通过是否存在 NULL
项(而不是相应的函数)来推断为 false。
此外,capabilities
回调允许设备驱动程序提供更详细的信息,特别是与引擎/设备 API 版本 13 或更高版本中的回调相关的。
当调用 capabilities
回调函数时,会返回一个整数向量列表,该列表代表图形引擎根据 DevDesc
结构中的标志和 ‘deviceVersion’ 所做出的最佳猜测。对于某些功能,整数向量长度为 1,其中 0
表示不支持,1
表示支持,NA
表示未知支持。对于支持程度可能更细致的功能,整数向量可能取更高的值,或者其长度可能大于 1,但长度为 1 且值为 0
仍然表示不支持,NA
仍然表示未知支持。
此列表中的以下组件可能需要修改(对于这些组件,图形引擎只能在 ‘deviceVersion’ 过低时猜测 0
,否则猜测 NA
)。
patterns
组件报告支持哪种类型的图案填充。如果设备支持一种或多种图案类型,则此组件应替换为包含每个支持图案类型的值的整数向量;图形引擎提供常量 R_GE_linearGradientPattern
、R_GE_radialGradientPattern
和 R_GE_tilingPattern
。如果设备不支持,则此组件应设置为 0。
clippingPaths
组件报告是否支持任意裁剪路径。如果设备支持裁剪路径,则此组件应设置为 1。如果设备不支持,则此组件应设置为 0。
masks
组件报告支持哪种类型的蒙版。如果设备支持一种或多种蒙版类型,则此组件应替换为包含每个支持蒙版类型的值的整数向量;图形引擎提供常量 R_GE_alphaMask
和 R_GE_luminanceMask
。如果设备不支持,则此组件应设置为 0。
compositing
组件报告支持哪些合成运算符。如果设备支持一种或多种合成运算符,则此组件应替换为包含每个支持运算符的值的整数向量;可能的运算符列表很长,包括 Porter-Duff 运算符和 Adobe PDF 混合模式;图形引擎提供常量 R_GE_compositeClear
等。如果设备不支持,则此组件应设置为 0。
transformations
组件报告是否支持仿射变换。如果设备支持变换,则此组件应设置为 1。如果设备不支持,则此组件应设置为 0。
paths
组件报告是否支持对由多个形状组成的路径进行描边和填充。如果设备支持描边和填充路径,则此组件应设置为 1。如果设备不支持,则此组件应设置为 0。
glyphs
组件报告是否支持渲染字形(例如,通过 grid::grid.glyph()
)。如果设备支持渲染字形,则此组件应设置为 1。如果设备不支持,则此组件应设置为 0。
图形引擎提供诸如 R_GE_capability_patterns
之类的常量,用于选择功能列表的适当组件。
设备驱动程序返回未修改的功能列表是有效的(即使没有帮助)。
处理文本可能是图形设备最困难的任务,设计允许设备可选地指示它具有其他功能。(如果设备没有,这些功能将在可能的情况下在图形引擎中处理。)
处理文本的三个回调必须存在于所有图形设备中,它们是 text
、strWidth
和 metricInfo
,声明如下
void text(double x, double y, const char *str, double rot, double hadj, pGgcontext gc, pDevDesc dd); double strWidth(const char *str, pGEcontext gc, pDevDesc dd); void metricInfo(int c, pGEcontext gc, double* ascent, double* descent, double* width, pDevDesc dd);
‘gc’ 参数提供图形上下文,最重要的是当前字体和字号,‘dd’ 是指向活动设备结构的指针。
text
回调应在 ‘(x, y)’19 处绘制 ‘str’,逆时针旋转 ‘rot’ 度。(有关 ‘hadj’,请参见下文。)水平文本的解释是基线位于 y
处,起点位于 x
处,因此第一个字符的任何左承将在 x
处开始。
回调函数 strWidth
计算字符串在当前字体下水平绘制时的宽度。 (宽度应包括(最好)或不包括左右边距。)
回调函数 metricInfo
计算单个字符的大小:ascent
是字符超出基线的距离,descent
是字符超出基线的距离。 width
是放置字符时光标应前进的距离。 对于 ascent
和 descent
,这指的是字形绘制的“墨迹”的边界框,而不是组装传统文本行时可能使用的边界框(例如,需要 hat(beta)
正确工作)。 但是,width
在 plotmath 中用于前进到下一个字符,因此需要包括左右边距。
‘c’ 的解释取决于区域设置。 在单字节区域设置中,值 32...255
表示区域设置中相应的字符(如果存在)。 对于符号字体(如 ‘graphics::par(font=5)’、‘grid::gpar(fontface=5’ 和 ‘plotmath’ 使用),值 32...126, 161...239, 241...254
表示 Adobe Symbol 编码中的字形。 在多字节区域设置中,c
表示 Unicode 代码点(符号字体除外)。 因此,该函数需要包含类似的代码
Rboolean Unicode = mbcslocale && (gc->fontface != 5); if (c < 0) { Unicode = TRUE; c = -c; } if(Unicode) UniCharMetric(c, ...); else CharMetric(c, ...);
此外,如果设备功能 hasTextUTF8
(见下文)为真,则 Unicode 代码点将作为负值传递:上面的代码片段显示了如何处理这种情况。 (这仅在设备功能 wantSymbolUTF8
为真时才适用于符号字体。)
如果可能,图形设备应处理文本的裁剪。 它通过结构元素 canClip
来指示这一点,如果为真,则会导致调用回调函数 clip
来设置裁剪区域。 如果没有这样做,引擎将非常粗略地裁剪(通过省略任何似乎不在裁剪区域内的文本)。
设备结构有一个整数元素 canHadj
,它指示设备是否可以进行文本的水平对齐。 如果为 1,则 text
的参数 ‘hadj’ 将被调用为 0 ,0.5, 1
,分别表示在指定位置的左对齐、居中对齐和右对齐。 如果为 2,则假定支持范围为 [0, 1]
的连续值。
如果功能 hasTextUTF8
为真,则有两个后果。首先,存在回调函数 textUTF8
和 strWidthUTF8
,它们的行为应与 text
和 strWidth
相同,只是假设 ‘str’ 是 UTF-8 编码而不是当前区域设置的编码。图形引擎将对所有文本调用这些函数,除了符号字体。其次,Unicode 码点将作为负整数传递给 metricInfo
回调函数。如果您的设备希望使用 UTF-8 编码的符号,请定义 wantSymbolUTF8
以及 hasTextUTF8
。在这种情况下,符号字体中的文本将发送到 textUTF8
和 strWidthUTF8
。
一些设备可以生成高质量的旋转文本,但基于位图的设备通常不能。能够生成高质量旋转文本的设备应从图形 API 版本 4 开始将 useRotatedTextInContour
设置为 true。
其他几个元素与图形引擎对文本的精确放置有关。
double xCharOffset; double yCharOffset; double yLineBias; double cra[2];
这些元素有点神秘。元素 cra
提供了字符大小的指示,在基本图形中为 par("cra")
,以设备单位表示。神秘之处在于“字符大小”的含义:哪个字符,哪个字体,哪个大小?通过查看其用途可以获得一些帮助。第一个元素“宽度”仅用于设置图形参数,R 不使用它。第二个元素“高度”用于设置行间距,即 par("mar")
和 par("mai")
之间的关系等等。建议一个好的选择是
dd->cra[0] = 0.9 * fnsize; dd->cra[1] = 1.2 * fnsize;
其中 ‘fnsize’ 是设备上标准字体 (cex=1
) 的“大小”,以设备单位表示。因此,对于 12 点字体(图形设备的默认值),‘fnsize’ 应为设备单位中的 12 点。
其余元素更加神秘。postscript()
设备表示
/* Character Addressing Offsets */ /* These offsets should center a single */ /* plotting character over the plotting point. */ /* Pure guesswork and eyeballing ... */ dd->xCharOffset = 0.4900; dd->yCharOffset = 0.3333; dd->yLineBias = 0.2;
似乎 xCharOffset
目前未使用,yCharOffset
用于基本图形系统在 text()
中设置垂直对齐方式,当指定 pos
时,以及在 identify()
中。图形引擎在尝试精确居中文本时偶尔会使用它,例如 pch
在 points()
或 grid.points()
中的字符字符串值,但是,仅在没有精确字符度量信息或对于多行字符串时才使用它。
yLineBias
用于基本图形系统中的 axis()
和 mtext()
,为它们的 ‘padj’ 参数提供默认值。
从 R_GE_version
16 (R_GE_glyphs
) 开始,也提供了一个 glyph
回调函数。
void glyph(int n, int *glyphs, double *x, double *y, SEXP font, double size, int colour, double rot, pDevDesc dd);
它指示设备在给定字体中绘制特定字形,其中字体由文件名(和索引)指定,并提供字体族、粗细和样式作为备选。
目标是使图形设备的(默认)输出尽可能相似。通常,人们遵循 postscript
和 pdf
设备的模型(它们共享大部分内部代码)。
以下约定已形成:
lwd = 1
应对应于 1/96 英寸的线宽。这对于基于像素的设备来说将是一个问题,通常最小线宽为 1 个像素(尽管这可能不适用于使用线抗锯齿的地方,而 cairo
倾向于使用 2 个像素的最小值)。
对于位图设备,这些约定不太明确,尤其是在位图格式没有设计分辨率的情况下。
线纹理的解释 (par("lty"
) 在头文件 GraphicsEngine.h 和 par
的帮助中进行了描述:请注意,图案的 ‘比例’ 应与线宽成正比(至少对于大于默认值的宽度)。
设备回调函数之一是 mode
函数,在头文件中进行了说明
* device_Mode is called whenever the graphics engine * starts drawing (mode=1) or stops drawing (mode=0) * GMode (in graphics.c) also says that * mode = 2 (graphical input on) exists. * The device is not required to do anything
由于 mode = 2
最近才在设备级别进行说明。它可以用来改变图形光标,但设备目前在 locator
回调函数中执行此操作。(在基本图形中,模式是在 locator
调用期间设置的,但如果 type != "n"
,则在进行注释时,每个点都会切换回来。)
许多设备确实对此调用不做任何操作,但一些屏幕设备确保在使用 mode = 0
调用时将绘图刷新到屏幕上。使用它进行某种缓冲很诱人,但请注意,“绘图”是在相当低的级别进行解释的,一个典型的单个图形将多次停止和开始绘图。在 X11()
设备中引入的缓冲利用 mode = 0
来指示活动:它在 ca 100ms 的不活动后更新屏幕。
如果此回调函数不执行任何操作,则无需提供它。
图形设备可以设计为处理用户交互:并非所有设备都支持。
用户可以使用 grDevices::setGraphicsEventEnv
在设备驱动程序中的 eventEnv
环境中设置事件处理程序。当用户调用 grDevices::getGraphicsEvent
时,R 将执行三个步骤。首先,它将每个具有非 NULL
eventEnv
条目的设备的设备驱动程序成员 gettingEvent
设置为 true
,如果回调已定义,则调用 initEvent(dd, true)
。然后它进入一个事件循环。每次循环时,R 将处理一次事件,然后检查是否有任何设备将 eventEnv
的 result
成员设置为非 NULL
值,并将找到的第一个值保存以返回。C 函数 doMouseEvent
和 doKeybd
用于调用 R 事件处理程序 onMouseDown
、onMouseMove
、onMouseUp
和 onKeybd
,并在此步骤中设置 eventEnv$result
。最后,initEvent
再次调用,init=false
用于通知设备循环已完成,并将结果返回给用户。
特定设备主要通过其源代码中的注释进行说明,尽管对于存在多年的设备,这些注释可能需要更新。本小节是有关设计决策的笔记库。
名为 X11(type="Xlib")
的设备可以追溯到 1990 年代中期,当时是用最基本的 X11 工具包 Xlib
编写的。从那时起,它可选地使用了其他工具包的一些功能:libXt
用于读取 X11 资源,而 libXmu
用于处理剪贴板选择。
使用基本的 Xlib
代码可以使绘图速度很快,但也有局限性。它不支持半透明颜色(这在 2000 年的 Xrender
工具包中出现),也不支持旋转文本(R 通过将文本渲染到位图并旋转位图来实现)。
X11 窗口的提示要求使用后台存储,一些窗口管理器可能会使用它来处理重绘,但似乎大多数重绘都是通过重放显示列表完成的(这里快速绘图非常有用)。
寻找字体一直存在问题。许多用户没有意识到字体是 X 服务器的函数,而不是 R 运行的机器的函数。在经历了许多困难之后,R 首先尝试在标准 75dpi 和 100dpi X11 字体包中提供的 Adobe 字体大小中找到最接近的大小匹配——即使这样在近 100dpi 屏幕的用户只安装了 75dpi 字体集时也会无法正常工作。75dpi 字体集允许在 100dpi 屏幕上将字体大小缩小到 6 点,但一些用户确实尝试使用更小的字体大小,即使 6 点和 8 点的位图字体看起来也不好。
UTF-8 本地化的引入引发了另一波困难。X11 只有很少的真正 UTF-8 字体,并为 iso10646-1
编码生成复合字体集。不幸的是,除了少数几种尺寸的等宽字体(不适合图形注释)之外,这些字体似乎覆盖范围很低,而且在缺少字形的地方,绘制的结果往往很不理想。
当前的方法是使用更现代的工具包,即 cairo
用于渲染,Pango
用于字体管理——因为它们与 Gtk+2
相关联,所以它们广泛可用。Cairo 支持半透明颜色和 alpha 混合(通过 Xrender
),以及用于显示线条和文本的抗锯齿。Pango 的字体管理基于 fontconfig
,有点神秘,但它似乎主要使用运行 R 的机器上的 Type 1 和 TrueType 字体,并将灰度位图发送到 cairo。
windows()
设备是一个设备家族:它支持绘制到 Windows(增强型)元文件、BMP
、JPEG
、PNG
和 TIFF
文件,以及 Windows 打印机。
在大多数情况下,主要绘制到位图:这用于屏幕设备的(默认)缓冲,这也使当前绘图能够保存到 BMP、JPEG、PNG 或 TIFF(它是以适当格式复制到文件的内部位图)。
设备单位是像素(元文件设备上的逻辑像素)。
代码最初由 Guido Masarotto 编写,大量使用了宏,这使得它难以解开。
对于屏幕设备,xd->gawin
是屏幕的画布,xd->bm
是屏幕外的位图。因此,宏 DRAW
会安排绘制到 xd->bm
,如果缓冲关闭,也会绘制到 xd->gawin
。对于所有其他设备,xd->gawin
是画布,jpeg()
和 png()
设备的位图,以及 win.metafile()
和 win.print
设备的 Windows 元文件的内部表示。由于“绘制”是通过 Windows GDI 对相应画布的调用完成的,因此它的确切性质被 GDI 系统隐藏了。
屏幕设备上的缓冲是通过运行一个计时器来实现的,当计时器触发时,它会将内部位图复制到屏幕。默认情况下,它设置为每 500 毫秒触发一次,并在绘制活动后重置为 100 毫秒。
重绘事件通过将内部位图复制到屏幕画布(然后重新初始化计时器)来处理,除非发生了调整大小。调整大小通过重播显示列表来处理:如果使用固定画布和滚动条,这可能没有必要,但这三种调整大小形式中最不受欢迎的一种。
近年来,设备上的文本已迁移至“Unicode”(UCS-2)。对于标准文本,建议使用 UTF-8(hasTextUTF8 = TRUE
),并在文件 src/extra/graphapp/gdraw.c 中的绘图函数中将其转换为 UCS-2。然而,GDI 不支持 Unicode 符号字体,符号使用 Adobe Symbol 编码处理。
屏幕设备和位图设备引入了对半透明颜色(alpha 通道在 0 到 255 之间)的支持。20 这是通过在另一个内部位图 xd->bm2
上绘制颜色的不透明版本,然后将该位图与 xd->bm
进行 alpha 混合来实现的。alpha 混合例程位于单独的 DLL msimg32.dll 中,该 DLL 在首次使用时加载。尽可能使用最小的矩形区域进行 alpha 混合(代码中的矩形 r
),但对于线和多边形边界,估计紧密的边界框需要太多工作。半透明颜色的线条并不常见,性能似乎可以接受。
对 png()
中透明背景的支持早于 libpng
中完全支持 alpha 通道(更不用说 PNG 浏览器了),因此利用了早期版本 PNG 中有限的透明度支持。在使用 24 位颜色时,这是通过将单一颜色标记为透明来实现的。R 选择了“#fdfefd”,并将其用作背景颜色(如果指定的背景颜色为透明(所有非不透明背景颜色都被视为透明),则在 GA_NewPage
中使用)。因此,这是通过在 PNG 文件中标记该颜色来实现的,没有透明度支持的浏览器会看到略微偏白的背景,就像有一个近乎白色的画布一样。如果在 PNG 文件中使用调色板(如果使用少于 256 种颜色),则该颜色将记录为完全透明,其余颜色为不透明。如果可以使用 32 位颜色,那么我们可以添加一个完整的 alpha 通道,但这取决于图形硬件和 GDI 的未公开属性。
设备接收颜色作为 typedef
rcolor
(一个 unsigned int
),定义在头文件 R_ext/GraphicsEngine.h 中)。4 个字节分别是 R ,G, B 和 alpha,从最低有效位到最高有效位。因此,RGB 的每个值都有 256 个亮度级别,从 0 到 255。alpha 字节表示不透明度,因此值 255 表示完全不透明,0 表示完全透明:许多但并非所有设备都处理半透明颜色。
可以在 C 中使用宏 R_RGBA
创建颜色,并且在 R_ext/GraphicsDevice.h 中定义了一组宏来提取各种组件。
基本图形系统中的颜色最初是从 S(以及之前的贝尔实验室的 GRZ 库)中采用的,其概念是使用数字“1...N”加上“0”(当前设备的背景颜色)来引用(可变大小的)颜色调色板。R 引入了通过字符字符串引用颜色的想法,形式可以是“#RRGGBB”或“#RRGGBBAA”(以十六进制表示字节),如函数 rgb()
所示,或者通过名称:657 个已知名称在字符向量 colors
中给出,并在包 grDevices 的文件 colors.c 中的表格中给出。请注意,半透明颜色不是“预乘的”,因此 50% 透明的白色是“#ffffff80”。
整数或字符 NA
颜色在内部映射到透明白色,字符字符串 "NA"
也是如此。
负颜色数字是错误。大于“N”的颜色会循环,例如,对于大小为 8 的默认调色板,颜色“10”是调色板中的颜色“2”。
整数颜色比基本图形子系统使用得更广泛,因为它们受包 grid 支持,因此也受 lattice 和 ggplot2 支持。(它们也受包 rgl 支持。)grid 重新定义了颜色“0”为透明白色,但 rgl 使用了 col2rgb
,因此使用了基本图形的背景颜色。
请注意,正整数颜色引用当前调色板,颜色“0”引用当前设备(如果需要,会打开一个设备)。这些在使用时映射到类型 rcolor
:这在重新播放显示列表时很重要,例如,当设备大小调整或使用 dev.copy
时。调色板应被视为每个会话的:它存储在包 grDevices 中。
惯例是设备使用“sRGB”色彩空间。这是一个行业标准:它被所有除高端数码相机以外的网页浏览器和 JPEG 使用。解释是图形设备和操作颜色的代码的事情,而不是图形引擎或子系统的事情。
R 使用类似于 PostScript 和 PDF 的绘画模型。这意味着,当形状(圆形、矩形和多边形)既可以填充又可以有描边边界时,应该先填充,然后描边(否则只有半边边界可见)。当填充和边界都是半透明时,对意图的解释有一定的空间。大多数设备首先绘制填充,然后绘制边界,在每一步都进行 alpha 混合。但是,PDF 会对对象进行一些自动分组,并且当填充和边界具有相同的 alpha 时,它们会被绘制到同一层上,然后在一步中进行 alpha 混合。(参见 PDF 参考第六版第 569 页,版本 1.7。不幸的是,虽然这是 PDF 标准规定应该发生的事情,但它没有被一些查看器正确实现。)
从颜色编号到类型 rcolor
的映射主要由函数 RGBpar3
完成:它从 R 二进制文件导出,但链接到包 grDevices 中的代码。第一个参数是指向字符、整数或双精度向量 SEXP
,第二个参数是颜色 0
(或 "0"
)的 rcolor
值。C 入口点 RGBpar
是一个包装器,它将 0
视为透明白色:它通常用于为设备设置颜色默认值。R 级别的包装器是 col2rgb
。
还有 R_GE_str2col
,它接受一个 C 字符串并将其转换为类型 rcolor
:"0'
被转换为透明白色。
有一个 R 级别的颜色转换为“##RRGGBBAA”的转换,由 image.default(useRaster = TRUE)
完成。
API 中另一个颜色转换入口点是 name2col
,它接受一个颜色名称(C 字符串)并返回一个 rcolor
类型的值。它处理 "NA"
、"transparent"
和 R 函数 colors()
中已知的 657 种颜色。
基础图形系统在 R 3.0.0 中迁移到包 graphics:它之前是在 src/main 中的文件中实现的。
出于历史原因,它主要在两层实现。文件 plot.c、plot3d.c 和 par.c 包含实现基本图形操作的约 30 个 .External
调用的代码。然后,此代码调用以 G
开头的名称的函数,这些函数在文件 graphics.c 中的标题 Rgraphics.h 中声明,进而调用图形引擎(其函数几乎都以 GE
开头)。
基础图形子系统的很大一部分基础设施是图形参数(由 par()
设置/读取)。这些参数存储在私有标题 Graphics.h 中声明的 GPar
结构中。此结构有两个变量(state
和 valid
)跟踪设备上基础子系统的状态,以及许多记录图形参数及其函数的变量。
基础系统状态包含在私有标题 GraphicsBase.h 中定义的 baseSystemState
结构中。它包含三个 GPar
结构和一个布尔变量,用于记录 plot.new()
(或 persp
)是否已在设备上成功使用。
三个 GPar
结构的副本用于存储当前参数(通过 gpptr
访问)、“设备副本”(通过 dpptr
访问)以及保存的“设备副本”参数的副本的空间。当前参数显然是当前正在使用的参数,并在每次调用 plot.new()
时从“设备副本”中复制(无论是否前进到下一“页”)。保存的副本保留设备上次完全清除时的状态(例如,当 plot.new()
以 par(new=TRUE)
调用时),并用于重播显示列表。
分离并不完全干净:如果通过 plot.window()
设置了带有对数刻度的绘图,则“设备副本”会发生改变。
在 graphics.c 中的 static
变量中还有另一个大多数图形参数的副本,这些副本用于在处理高级图形调用中的内联参数(由 ProcessInlinePars
处理)时保留当前参数。
基本子系统的快照记录了 GPar
结构的“保存的设备副本”。
一些图形参数(由 par
设置)和具有相同名称的基本图形函数的参数之间存在不幸的混淆。此描述可能有助于澄清记录。
大多数高级绘图函数接受图形参数作为附加参数,然后如果它们不是已命名的参数(这是混淆的主要来源),则通常将这些参数传递给低级函数。
图形参数 bg
是绘图的背景颜色。参数 bg
指的是填充符号 21
到 25
的填充颜色。它是函数 plot.xy
的参数,但通常由 points
的默认方法传递,通常来自 plot
方法。
图形参数 cex
、col
、lty
、lwd
和 pch
也作为 plot.xy
的参数出现,因此通常作为来自高级绘图函数(如 lines
、points
和 plot
方法)的参数传递。它们作为 legend
的参数出现,col
、lty
和 lwd
是 arrows
和 segments
的参数。当用作参数时,它们可以是向量,循环使用以控制各种线、点和线段。当设置为图形参数时,它们设置默认渲染:此外,par(cex=)
设置整体字符扩展,后续调用(作为参数或在线图形参数)会乘以该扩展。
在两种使用情况下,缺失值的处理方式不同。通常,在 par
中使用时,它们是错误,但在用作向量参数的元素时,会导致相应的绘图元素被省略。最初,参数的解释主要留给设备,但如今,图形引擎已经预先处理了其中的一些内容(但例如 lwd = 0
的处理方式仍然是设备特定的,有些设备将其解释为“尽可能细”的线)。
标准的 R 前端是运行在终端中的程序,但有几种方法可以提供 GUI 控制台。
这可以通过从基于终端的 R 加载的包来完成,该包在启动代码中启动控制台,或者通过用户运行特定函数来完成:包 Rcmdr 是一个使用 Tk 的 GUI 的知名示例。
曾经有一个基于 Gtk 的控制台,可以通过 R --gui=GNOME
调用:这依赖于前端 shell 脚本中的特殊情况来启动不同的可执行文件。仍然存在 R --gui=Tk
,它启动基于终端的 R 并运行 tcltk::tkStartGui()
作为修改后的启动序列的一部分。
但是,运行 GUI 控制台的主要方法是启动一个运行嵌入式 R 的独立程序:这由 Windows 上的 Rgui.exe
和 macOS 上的 R.app
完成。第一个是 R 的组成部分,控制台的代码目前位于 R.dll 中。
R.app
是一个提供控制台的 macOS 应用程序。它的源代码是一个独立的项目21,它的二进制文件链接到一个 R 安装,它作为动态库 libR.dylib 运行。macOS 的标准 CRAN 分发版捆绑了 GUI 和 R 本身,但安装 GUI 是可选的,这两个组件都可以单独更新。
R.app
依赖于 libR.dylib 位于特定位置,因此依赖于 R 作为 Mac macOS 的“框架”构建和安装。具体来说,它使用 /Library/Frameworks/R.framework/R。这是一个符号链接,因为框架可以包含多个版本的 R。它最终解析为 /Library/Frameworks/R.framework/Versions/Current/Resources/lib/libR.dylib,它(在 CRAN 发行版中)是一个包含多个子体系结构的“胖”二进制文件。
macOS 应用程序是目录树:每个 R.app
包含一个用 Objective-C 编写的针对一个子体系结构的前端:在标准发行版中,有针对 32 位和 64 位 Intel 体系结构的独立应用程序。
最初,R 源代码包含大量仅由 macOS GUI 使用的代码,但这些代码已迁移到 R.app
源代码中。
R.app
将 R 作为嵌入式应用程序启动,其命令行包含 --gui=aqua(见下文)。它使用在头文件 Rinterface.h 中定义的大多数接口指针,以及文件 src/main/sysutils.c 中的私有接口指针。它在搜索路径的第二个位置添加了一个名为 tools:RGUI
的环境。这包含许多用于支持菜单项的实用函数,例如 package.manager()
,以及函数 q()
和 quit()
,它们屏蔽了包 base 中的那些函数——自定义版本以特定于 R.app
的方式保存历史记录。
R 有一个 configure
选项 --with-aqua 用于自定义 R 的构建方式:这与 --enable-R-framework 选项不同,后者会导致 make install
将 R 安装为使用 R.app
所需的框架。(选项 --with-aqua 是 macOS 上的默认选项。)它在 config.h 中设置宏 HAVE_AQUA
,并在 make 变量 BUILD_AQUA_TRUE
中设置。这些有几个后果
quartz()
在包 grDevices 中构建(除了作为存根):这需要一个 Objective-C 编译器。然后,只要终端 R 可以访问 macOS 屏幕,就可以使用 quartz()
。
quartz()
的接口指针。
capabilities("aqua")
设置为 TRUE
。
system()
返回时,支持设置“繁忙”指示器。
R_ProcessEvents
被抑制。R.app
中的关联回调执行了不应该在子进程中执行的操作,并且派生会派生整个进程,包括控制台。
useaqua
被设置为真值。这会产生一些后果
R_Interactive
断言 R 会话是交互式的。
.Platform$GUI
被设置为 "AQUA"
。这会产生一些后果
DISPLAY
未设置,则将其设置为 ‘:0’。
PATH
,因为 gfortran
安装在该目录下。
R.app
中的浏览器。
R.app
中提供的版本:这些包括图形菜单、数据编辑器(但不包括 View()
使用的数据查看器)以及由 browseEnv()
调用的工作区浏览器。
R.app
下运行,因此会通知任何 quartz
设备,Quartz 事件循环已经在运行。
system
函数的使用(包括 system()
和 system2()
,以及启动编辑器和分页器)被 R.app
中的版本替换(默认情况下,它只是调用操作系统的 system
,并重置各种信号处理程序)。
Rstd_WriteConsoleEx
传输。这使用 ANSI 终端转义序列将发送到 stderr
的行在 stdout
上以粗体显示。
-psn
被允许但会被忽略。(似乎在 2003 年,‘r27492’,这是由 Finder 添加的。)
R CMD check
的行为可以通过各种命令行参数和环境变量来控制。
有一个内部 --install=value 命令行参数,没有显示在 R CMD check --help
中,可能的取值有
check:file
假设安装已经完成,标准输出/错误输出到 file,需要检查其内容(无需重复安装)。这对存储库维护者应用的检查很有用:它通过安装时间减少了检查时间,因为包已经安装。在这种情况下,还需要使用命令行选项 --library 指定包的安装位置。
fake
模拟安装,并关闭运行时测试。
skip
跳过安装,例如,在测试与 R 捆绑在一起的推荐包时。
否
与 --no-install 相同:关闭安装和需要安装包的测试。
以下环境变量可用于自定义 check
的操作:设置这些变量的便捷位置是检查环境文件(默认情况下,~/.R/check.Renviron)。
_R_CHECK_ALL_NON_ISO_C_
¶如果为真,则不忽略编译器(通常为 GCC)关于 系统头文件中非 ISO C 代码的警告。请注意,这也会显示额外的 ISO C++ 警告。默认值:false。
_R_CHECK_FORCE_SUGGESTS_
¶如果为真,则在建议的包不可用时给出错误。默认值:真(但对于 CRAN 提交检查为假)。
_R_CHECK_RD_CONTENTS_
¶如果为真,则检查 Rd 文件以查找需要编辑的自动生成内容和缺少的参数文档。默认值:真。
_R_CHECK_RD_LINE_WIDTHS_
¶如果为真,则检查 Rd 文件中用法和示例部分的行宽。默认值:假(但对于 CRAN 提交检查为真)。
_R_CHECK_RD_STYLE_
¶如果为真,则检查 Rd 文件中 S3 方法的用法条目是否使用完整的函数名而不是适当的 \method
标记。默认值:真。
_R_CHECK_RD_XREFS_
¶如果为真,则检查 .Rd 文件中的交叉引用。默认值:真。
_R_CHECK_SUBDIRS_NOCASE_
¶如果为真,则检查目录的大小写,例如 R 和 man。默认值:真。
_R_CHECK_SUBDIRS_STRICT_
¶--check-subdirs 的初始设置。默认值:‘default’(仅检查 tarball,如果不存在 configure 文件,则仅在 src 中检查)。
_R_CHECK_USE_CODETOOLS_
¶如果为真,则使用 codetools 包,该包提供对对象可见性的详细分析(但可能会给出误报)。默认值:真(如果推荐的包已安装)。
_R_CHECK_USE_INSTALL_LOG_
¶如果为真,则将安装包的输出记录到日志文件(默认情况下为 00install.out)中,即使在交互模式下运行也是如此。默认值:真。
_R_CHECK_VIGNETTES_NLINES_
¶在报告运行或重新构建小插图时出错时,从输出底部显示的最大行数。(值 0
表示将显示所有行。)默认值:运行时为 10,重新构建时为 25。
_R_CHECK_CODOC_S4_METHODS_
¶控制是否对 S4 方法执行 codoc()
测试。默认值:true。
_R_CHECK_DOT_INTERNAL_
¶控制是否扫描包代码以查找 .Internal
调用,这些调用应该只由基础包(偶尔也由推荐包)使用。默认值:true。
_R_CHECK_EXECUTABLES_
¶控制检查可执行(二进制)文件。默认值:true。
_R_CHECK_EXECUTABLES_EXCLUSIONS_
¶控制检查可执行(二进制)文件是否忽略包的 BinaryFiles 文件中列出的文件。默认值:true(但对于 CRAN 提交检查为 false)。但是,最有可能的是,这种包级覆盖机制最终会被移除。
_R_CHECK_PERMISSIONS_
¶控制是否应检查文件的权限。默认值:true,当且仅当 .Platform$OS.type == "unix"
时。
_R_CHECK_FF_CALLS_
¶允许关闭 checkFF()
测试。如果设置为 ‘registration’,则检查此类调用的注册信息(参数数量、.C/.Fortran/.Call/.External
的正确选择),前提是包已安装。默认值:true。
_R_CHECK_FF_DUP_
¶控制 checkFF(check_DUP)
默认值:true(并且对于 CRAN 提交检查强制为 true)。
_R_CHECK_LICENSE_
¶控制是否/如何执行许可证检查。一个可能的值是“maybe”(在出现问题时发出警告,但不会针对可标准化的非标准许可证规范发出警告)。默认值:true。
_R_CHECK_RD_EXAMPLES_T_AND_F_
¶控制check_T_and_F()
是否也查找示例中的“错误”(全局)“T”/“F”用法。默认情况下关闭,因为这会导致误报。
_R_CHECK_RD_CHECKRD_MINLEVEL_
¶控制从checkRd
报告警告的最低级别。默认值:-1。
_R_CHECK_RD_ALLOW_EMPTY_ITEM_IN_DESCRIBE_
¶控制是否允许\\describe
内部的空项,而不会发出警告。这是一个临时的权宜之计,旨在支持旧版软件包,并且可能会在未来的 R 版本中删除。默认值:false。
_R_CHECK_XREFS_REPOSITORIES_
¶如果设置为非空值,则为一个空格分隔的存储库列表,用于确定已知软件包。默认值:为空,此时使用 R 已知的 CRAN 和 Bioconductor 存储库。
_R_CHECK_SRC_MINUS_W_IMPLICIT_
¶控制是否检查安装输出中关于隐式函数声明的编译警告(如 GCC 使用命令行选项 -Wimplicit-function-declaration 检测到的,该选项隐含在 -Wall 中)。注意:隐式函数声明在一些最近的 C 编译器中是错误,包括 Apple clang
。默认值:从 R 4.2.0 开始为 true,之前为 false。
_R_CHECK_SRC_MINUS_W_UNUSED_
¶控制是否检查安装输出中是否有关于未使用代码部分的编译警告(如 GCC 使用命令行选项 -Wunused 检测到,该选项由 -Wall 隐含)。默认值:true。
_R_CHECK_WALL_FORTRAN_
¶控制在分析安装输出时是否使用 gfortran 4.0 或更高版本 -Wall 警告。默认值:false,即使这些警告是合理的。
_R_CHECK_ASCII_CODE_
¶如果为 true,则检查 R 代码中是否有非 ASCII 字符。默认值:true。
_R_CHECK_ASCII_DATA_
¶如果为 true,则检查数据中是否有非 ASCII 字符。途中,检查所有数据集是否可以加载以及它们的组件是否可以访问。默认值:true。
_R_CHECK_COMPACT_DATA_
¶如果为 true,则检查数据是否为 ASCII 和未压缩的保存,并检查使用 bzip2
或 xz
压缩是否会明显更好。默认值:true。
_R_CHECK_SKIP_ARCH_
¶在多架构设置中,将从检查中省略的架构的逗号分隔列表。默认值:无。
_R_CHECK_SKIP_TESTS_ARCH_
¶在多架构设置中,将从运行测试中省略的架构的逗号分隔列表。默认值:无。
_R_CHECK_SKIP_EXAMPLES_ARCH_
¶在多架构设置中,将从运行示例中省略的架构的逗号分隔列表。默认值:无。
_R_CHECK_VC_DIRS_
¶是否应检查解压缩的包目录中是否有版本控制目录(CVS,.svn …)?默认值:对于 tarball 为 true。
_R_CHECK_PKG_SIZES_
¶是否应使用 du
查找已安装包的大小?R CMD check
确实会检查 du
的可用性。但此选项允许在找到不合适的命令时(包括不尊重 -k 标志以以 1Kb 为单位报告,或以不同格式报告的命令 - 已测试过 GNU、macOS 和 Solaris du
命令)覆盖检查。默认值:如果找到 du
,则为 true。
_R_CHECK_PKG_SIZES_THRESHOLD_
¶用于 _R_CHECK_PKG_SIZES_
的阈值(以 Mb 为单位)。默认值:5
_R_CHECK_DOC_SIZES_
¶是否应使用 qpdf
检查已安装的 PDF 文件的大小?默认值:如果找到 qpdf
,则为 true。
_R_CHECK_DOC_SIZES2_
¶是否应使用 gs
检查已安装的 PDF 文件的大小?这比之前的检查速度更慢,而且是附加的检查,但可以检测到细节过多的图形(通常被过度绘制隐藏)或分辨率过高的位图图形。要求将 R_GSCMD
设置为有效的程序,或者 gs
(或在 Windows 上,gswin32.exe
或 gswin64c.exe
)位于路径中。默认值:false(但在 CRAN 提交检查中为 true)。
_R_CHECK_ALWAYS_LOG_VIGNETTE_OUTPUT_
¶默认情况下,仅在运行小插图中的 R 代码时出现错误时,才会保留输出。这也适用于从重新构建小插图的 build_vignettes.log 日志中获得的日志。默认值:false。
_R_CHECK_CLEAN_VIGN_TEST_
¶如果测试成功,是否应删除 vign_test 目录?默认值:true。
_R_CHECK_REPLACING_IMPORTS_
¶是否应报告有关替换导入的警告?这些警告有时来自其他包中自动生成的 NAMESPACE 文件,但最常见的是从导入整个命名空间而不是使用 importFrom
引起的。默认值:true。
_R_CHECK_UNSAFE_CALLS_
¶检查似乎篡改(或允许篡改)已加载的代码(不是来自当前包的代码)的调用:此类调用很可能违反 CRAN 政策。默认值:true。
_R_CHECK_TIMINGS_
¶可以选择在检查日志中报告安装、示例、测试和运行/重新构建小插图的时间。格式为“[as/bs]”,表示总 CPU 时间(包括子进程)“a”和经过时间“b”,除了在 Windows 上,格式为“[bs]”。在大多数情况下,时间仅针对“OK”检查给出。经过时间超过 10 分钟的时间将以分钟(缩写为“m”)报告。该值是应报告的经过时间的最小数值,非数值表示不需要报告,值为“0”表示始终需要报告。默认值:""
。 (对于 CRAN 检查,默认值为 10
,除非在环境中设置。)
_R_CHECK_EXAMPLE_TIMING_THRESHOLD_
¶如果正在记录时间,请设置以秒为单位的阈值,用于报告长时间运行的示例(用户+系统 CPU 时间或经过时间)。默认值:"5"
。
_R_CHECK_EXAMPLE_TIMING_CPU_TO_ELAPSED_THRESHOLD_
¶对于启用了时间的检查,报告 CPU 时间与经过时间之比超过此阈值(且 CPU 时间至少为一秒)的示例。这有助于检测多个 CPU 内核的同时使用。默认值:NA
。
_R_CHECK_TEST_TIMING_CPU_TO_ELAPSED_THRESHOLD_
¶如果运行单个测试时,CPU 时间与经过时间之比超过此阈值(且 CPU 时间至少为一秒),则报告。在 Windows 上不支持。默认值:NA
。
_R_CHECK_VIGNETTE_TIMING_CPU_TO_ELAPSED_THRESHOLD_
¶如果在运行/重新构建小插图(单独或汇总)时,CPU 时间与经过时间之比超过此阈值(且 CPU 时间至少为一秒),则报告。在 Windows 上不支持。默认值:NA
。
_R_CHECK_CODETOOLS_PROFILE_
¶一个包含逗号分隔的 name=value
对(value 为逻辑常量)的字符串,用于为用于分析包代码的 codetools 函数提供额外的参数。例如,使用 _R_CHECK_CODETOOLS_PROFILE_="suppressLocalUnused=FALSE"
来关闭关于未使用局部变量的警告。默认值:没有额外参数,对应于使用 skipWith = TRUE
、suppressPartialMatchArgs = FALSE
和 suppressLocalUnused = TRUE
。
_R_CHECK_CRAN_INCOMING_
¶检查包是否适合在 CRAN 上发布。默认值:false,除了 CRAN 提交检查。
_R_CHECK_CRAN_INCOMING_REMOTE_
¶在上述检查中包含需要远程访问的检查。默认值:与 _R_CHECK_CRAN_INCOMING_
相同。
_R_CHECK_XREFS_USE_ALIASES_FROM_CRAN_
¶在检查锚定的 Rd 引用时,除了本地安装的包中的 Rd 别名之外,还使用 CRAN 包网页区域中的 Rd 别名。默认值:false。
_R_SHLIB_BUILD_OBJECTS_SYMBOL_TABLES_
¶通过在安装时将对象的符号表(.o 文件)记录到 symbols.rds 文件中,使编译代码的检查更加准确。(目前仅在 Linux、Solaris、macOS、Windows 和 FreeBSD 上支持。)默认值:true。
_R_CHECK_CODE_ASSIGN_TO_GLOBALENV_
¶是否应该检查包代码是否对全局环境进行了赋值?默认值:false(但对于 CRAN 提交检查为 true)。
_R_CHECK_CODE_ATTACH_
¶是否应该检查包代码是否调用了 attach()
?默认值:false(但对于 CRAN 提交检查为 true)。
_R_CHECK_CODE_DATA_INTO_GLOBALENV_
¶是否应该检查包代码是否调用了 data()
,该函数将数据加载到全局环境中?默认值:false(但对于 CRAN 提交检查为 true)。
_R_CHECK_DOT_FIRSTLIB_
¶是否应该检查包代码中是否存在过时的函数 .First.lib()
?默认值:false(但对于 CRAN 提交检查为 true)。
_R_CHECK_DEPRECATED_DEFUNCT_
¶是否应该检查包代码中是否存在最近弃用或失效的函数(包括完全删除的函数)。以及平台特定的图形设备。默认值:false(但对于 CRAN 提交检查为 true)。
_R_CHECK_SCREEN_DEVICE_
¶如果设置为 ‘warn’,如果示例等打开屏幕设备,则发出警告。如果设置为 ‘stop’,则发出错误。默认值:空(但对于 CRAN 提交检查为 ‘stop’)。
_R_CHECK_WINDOWS_DEVICE_
¶如果设置为 ‘stop’,当在示例等中使用 Windows 专用设备时,会报错。这仅在 Windows 上有用:这些设备在其他地方不存在。默认值:空(但在 Windows 上的 CRAN 提交检查中为 ‘stop’)。
_R_CHECK_TOPLEVEL_FILES_
¶报告包源代码中未在 “Writing R Extensions” 中描述或不常见(如 ChangeLog)的顶层文件。标准名称的变体(例如 COPYRIGHT)也会被报告。默认值:false(但在 CRAN 提交检查中为 true)。
_R_CHECK_GCT_N_
¶是否应该让 --use-gct 使用 gctorture2(n)
而不是 gctorture(TRUE)
?使用正整数启用此功能。默认值:0
。
_R_CHECK_LIMIT_CORES_
¶如果设置,检查包 parallel 中是否使用了过多核心。如果设置为 ‘warn’ 会发出警告,设置为 ‘false’ 或 ‘FALSE’ 会跳过检查,任何其他非空值在生成超过 2 个子进程时会报错。默认值:未设置(但在 CRAN 提交检查中为 ‘TRUE’)。
_R_CHECK_CODE_USAGE_VIA_NAMESPACES_
¶如果设置,检查代码使用情况(通过 codetools)直接在包命名空间上,而无需加载和附加包及其建议和增强功能。默认值:true(在 CRAN 提交检查中也是 true)。
_R_CHECK_CODE_USAGE_WITH_ONLY_BASE_ATTACHED_
¶如果设置,检查代码使用情况(通过 codetools),仅附加基础包。默认值:true。
_R_CHECK_EXIT_ON_FIRST_ERROR_
¶如果设置为真值,检查将在第一个错误时退出。默认值:false。
_R_CHECK_OVERWRITE_REGISTERED_S3_METHODS_
¶如果设置为真值,则报告在加载此包的命名空间时被覆盖的 base/推荐包中已注册的 S3 方法。默认值:false(但对于 CRAN 提交检查为 true)。
_R_CHECK_TESTS_NLINES_
¶在日志中重现的测试输出的尾部行数。如果为 0
,则重现除 R 前言之外的所有行。默认值:13。
_R_CHECK_NATIVE_ROUTINE_REGISTRATION_
¶如果设置为真值,则报告如果在包的 DLL 中找不到注册本机例程和抑制动态搜索的入口点。(注意:这需要系统命令 nm
位于 PATH
上。在 Windows 上,首先在通过 Makeconf
指定的编译器工具链中搜索 objdump.exe
(可以通过环境变量 BINPREF
自定义)。如果在那里找不到,它必须位于 PATH
上。在 Unix 上,当使用包含编译代码的包(这是唯一检查的包)时,这将是正常的,但 Windows 用户应该检查。默认值:false(但对于 CRAN 提交检查为 true)。
_R_CHECK_NO_STOP_ON_TEST_ERROR_
¶如果设置为真值,则在第一个错误后不要停止运行测试(就像给出了命令行选项 --no-stop-on-test-error 一样)。默认值:false(但对于 CRAN 提交检查为 true)。
_R_CHECK_PRAGMAS_
¶对 C/C++ 源代码和头文件中的编译指示进行额外的检查。默认值:false(但对于 CRAN 提交检查为 true)。
_R_CHECK_COMPILATION_FLAGS_
¶如果包已安装并包含 C/C++/Fortran 代码,请检查安装日志以查找不可移植的标志(例如,在配置期间添加到 src/Makevars 的标志)。目前,会报告 -W 标志,除了 -Wall、-Wextra 和 -Weverything,以及似乎试图抑制警告的标志会被突出显示。有关此检查的理由(以及为什么即使 -Werror 也不安全),请参阅 Writing R Extensions 中的 编写可移植包。
环境变量 _R_CHECK_COMPILATION_FLAGS_KNOWN_
可以设置为一个空格分隔的标志集,这些标志来自用于测试的 R 构建(例如 -Wall 和 -Wextra 等标志已知)。例如,对于 macOS 上 R >= 4.0.0 的 CRAN 构建,可以使用
_R_CHECK_COMPILATION_FLAGS_KNOWN_="-mmacosx-version-min=10.13"
默认值:未设置。
_R_CHECK_R_DEPENDS_
¶检查对 R 的任何依赖是否不是最近的补丁级别版本,例如 R (>= 3.3.3)
,因为阻止安装包也会阻止其反向依赖项。可能的值为 ‘"note"’、‘"warn"’ 和逻辑值(其中当前的真值等效于 ‘"note"’)。默认值:false(但对于 --as-cran 则为 ‘"warn"’)。
_R_CHECK_SERIALIZATION_
¶检查包源代码中的序列化 R 对象是否使用版本 2 序列化,并且没有对 ‘R >= 3.5.0’ 的依赖。(版本 3 从 R 3.5.0 开始使用,但应仅在必要时使用。)默认值:false(但对于 CRAN 提交检查则为 true)。
_R_CHECK_R_ON_PATH_
¶这将检查包是否尝试从路径中使用 R
或 Rscript
而不是测试中的路径。它通过将脚本放在路径的开头来实现,这些脚本会打印一条消息并失败。默认值:false(但对于 CRAN 提交检查则为 true)。
_R_CHECK_PACKAGES_USED_IN_TESTS_USE_SUBDIRS_
¶如果设置为真值,还会检查 tests 的通用单元测试子目录中的 R 代码,以查找未声明的包依赖项。默认值:false(但 CRAN 提交检查为 true)。
_R_CHECK_SHLIB_OPENMP_FLAGS_
¶检查 src/Makevars(以及类似文件)中 SHLIB_OPENMP_*FLAGS
的正确和可移植使用。默认值:false(但 CRAN 提交检查为 true)。
_R_CHECK_CONNECTIONS_LEFT_OPEN_
¶在检查示例时,检查每个示例是否留下了打开的连接:如果发现任何连接,则会报告致命错误。注意:“连接”包括大多数文件的使用以及任何未被 stopCluster()
停止的并行集群。默认值:false(但 CRAN 提交检查为 true)。
_R_CHECK_FUTURE_FILE_TIMESTAMPS_
¶检查任何输入文件的时间戳是否在未来(为此,检查系统时钟是否在 5 分钟内正确)。默认值:false(但 CRAN 提交检查为 true)。
_R_CHECK_LENGTH_1_CONDITION_
¶不再使用:长度大于 1 的条件在 if
或 while
语句中现在是一个错误。
_R_CHECK_VIGNETTES_SKIP_RUN_MAYBE_
¶如果要重建小插图输出(这将涉及运行该代码),是否应该跳过运行小插图中的 R 代码。默认值:false(但 CRAN 检查为 true)
_R_CHECK_BUILD_VIGNETTES_SEPARATELY_
¶在 R 3.6.0 之前,重新构建 vignette 输出是在单个 R 会话中完成的,这允许意外地依赖一个 vignette 另一个 vignette(例如,在加载包时)。当前默认值是为每个 vignette 使用单独的会话;此选项允许测试旧的行为,默认值:true
_R_CHECK_SYSTEM_CLOCK_
¶作为由 --as-cran 启用的“检查未来文件时间戳”的一部分,检查系统时钟与外部时钟,以捕获错误,例如错误的日期甚至年份。在进行重复检查的系统上没有必要。默认值:true(但 CRAN 检查为 false)
_R_CHECK_AUTOCONF_
¶对于具有由 GNU autoconf
生成的 configure 文件以及 configure.ac 或 configure,.in 的包,检查 autoreconf
是否可以(如果可用)在源代码副本中运行(这将检测丢失的源代码文件并报告 autoconf
警告)。环境变量 AUTORECONf
控制使用的命令:它可以给出 autoreconf
的完整路径(不带空格),并且可以包含标志,例如 --warnings=obsolete(它被添加到 autoreconf
版本 2.68 或 2.69 中,并且是更高版本中的默认值)。默认值:false(但 CRAN 提交检查为 true)。
_R_CHECK_DATALIST_
¶检查文件 data/datalist 是否已过期。默认值:false(但 CRAN 提交检查为 true)。
_R_CHECK_THINGS_IN_CHECK_DIR_
¶在检查运行结束时检查并报告是否在检查目录中留下了文件。默认值:false(但 CRAN 提交检查为 true)。
_R_CHECK_THINGS_IN_TEMP_DIR_
¶检查并报告在检查运行结束时,如果在临时目录(通常在类 Unix 系统上为 /tmp)中留下了文件。它通过将环境变量 TEMPDIR
设置为 check
进程的 R 会话目录的子目录来实现:如果该目录中留下了任何文件或目录,则会将其删除。由于其中一些文件可能不受用户控制,环境变量 _R_CHECK_THINGS_IN_TEMP_DIR_EXCLUDE_
可以指定要排除的(扩展正则表达式)文件路径模式 - CRAN 使用 ' ^ompi. ' 来表示 OpenMPI 遗留的目录。在某些情况下,TEMPDIR
不会被遵守,因此文件会留在 /tmp 中(并且不会被报告,但请参阅下一项):一个例子是在某些操作系统上 /tmp/boost_interprocess。默认值:false(但对于 CRAN 提交检查为 true)。
_R_CHECK_THINGS_IN_OTHER_DIRS_
¶检查并在检查运行结束时报告在检查运行期间是否在选定的一组目录中创建了新文件或目录。(这仅限于运行检查进程的用户拥有的文件。)目前,监视的目录是主目录、/tmp(不包括 RtmpXXXXXX 目录)、/dav/shm、~/.cache(递归)和 ~/.local/share(递归)或它们在 Windows 和 macOS 上的等效目录(tools::R_user_dir()
的默认设置使用 R 子目录的目录)。可以在环境变量 _R_CHECK_THINGS_IN_OTHER_DIRS_XTRA_
中指定其他目录,用分号分隔。目录在所有平台上都以 ' / ' 结尾报告。
环境变量 _R_CHECK_THINGS_IN_OTHER_DIRS_EXCLUDE_
可以指定一个(扩展正则表达式)文件路径模式,这些路径将不会被报告 - 应该匹配包含用 ~ 表示的 home 的绝对文件路径。例如,在 Linux 系统上
'^~/.cache/(mozilla/firefox|mesa_shader_cache)/'
匹配 Firefox 和 OpenGL 使用的缓存目录(及其内容)。如果值以 ‘@’ 开头,则它被视为一个文件路径,该路径将被读取,每行都被视为要匹配的模式。
请注意,其他进程(包括并行运行的检查运行)可能会在这些目录中创建新文件,这些文件将被报告。但是,此可选检查对于缩小可能留下意外文件的包非常有用。默认值:false
_R_CHECK_BASHISMS_
¶使用 Perl 脚本 checkbashisms
(如果可用)检查顶层脚本 configure(除非由 autoconf 生成)和 cleanup 中是否存在非 Bourne shell 代码。这包括报告使用不可移植的 #! /bin/bash
的脚本。(脚本 checkbashisms
在大多数 Linux 发行版中都可用,位于名为 ‘devscripts’ 或 ‘devscripts-checkbashisms’ 的包中,也可以从 https://sourceforge.net/projects/checkbaskisms/files 获取。)默认值:false(但在 CRAN 提交检查中为 true,除了在 Windows 上)。
_R_CHECK_ORPHANED_
¶检查依赖项是否为孤立包。从 R 4.1.0 开始,此检查会递归地检查严格依赖项,因此会报告任何通过 library()
附加包所需的孤立包,以及任何建议的孤立包。默认值:false(但在 CRAN 提交检查中为 true)。
_R_CHECK_EXCESSIVE_IMPORTS_
¶一个正整数。如果设置,如果从非基本包导入的数量超过此阈值,则给出 NOTE。大量的导入会使包容易受到任何导入变得不可用的影响。默认值:未设置(但在 CRAN 提交检查中为 20)
_R_CHECK_DONTTEST_EXAMPLES_
¶如果为 true 并且在示例中找到带有 \donttest
部分的示例,则测试将在一个通过中运行,这些部分被注释掉,然后在第二个通过中包括 \donttest
部分(仅针对主架构)。只有在第一次通过时,结果才会与任何 .Rout.save 文件进行比较,并且会分析计时。被 --run-donttest 覆盖。默认值:false,除非指定了 --as-cran(可以通过设置 ‘_R_CHECK_DONTTEST_EXAMPLES_=false’ 来覆盖)。
_R_CHECK_XREFS_PKGS_ARE_DECLARED_
¶检查 .Rd 文件中“锚定”交叉引用(形式为 \link[pkg]{foo}
和 \link[pkg:bar]{foo}
)中使用的包是否在 DESCRIPTION 文件中声明,以便可以检查这些链接。默认值:false。
_R_CHECK_XREFS_MIND_SUSPECT_ANCHORS_
¶检查包锚定的 Rd 交叉引用是否指向 文件(而不是别名)。默认值:false。
_R_CHECK_BOGUS_RETURN_
¶如果为真且 _R_CHECK_USE_CODETOOLS_
也为真,则会扫描函数以查找使用 return
而不是 return()
的情况。默认值:false(但对于 CRAN 提交检查为真)。
_R_CHECK_MATRIX_DATA_
¶默认情况下,检查对 matrix
的调用中数据长度与维数之间的不匹配会发出警告:将此设置为真值会产生错误,并带有简洁的回溯。默认值:false(但对于 CRAN 提交检查为真)。
_R_CHECK_CODE_CLASS_IS_STRING_
¶检查包代码是否具有 if()
条件,该条件将对象的类与字符串进行比较。请参阅 https://blog.r-project.org.cn/2019/11/09/when-you-think-class.-think-again/,了解为什么这是一个坏主意。默认值:false(但对于 CRAN 提交检查为真)。
_R_CHECK_RD_VALIDATE_RD2HTML_
¶使用 HTML Tidy (https://www.html-tidy.org/) 检查包 HTML 帮助页面的有效性(如果系统路径上存在合适的可执行文件版本或其路径由环境变量 R_TIDYCMD
指定)。建议 macOS 用户从 https://binaries.html-tidy.org/ 安装 5.8.0 或更高版本。默认:false(但 CRAN 提交检查为 true)。--as-cran 可以通过将此变量设置为 false 值来覆盖。
_R_CHECK_RD_MATH_RENDERING_
¶检查通过 KaTeX 进行 HTML 数学渲染是否有效(如果包 V8 可用)。参见 https://blog.r-project.org.cn/2022/04/08/enhancements-to-html-documentation/。默认:false(但 CRAN 提交检查为 true)。
_R_CHECK_NEWS_IN_PLAIN_TEXT_
¶检查 news()
是否能够成功以纯文本(文件 NEWS)格式读取包新闻。默认:false(但 CRAN 提交检查为 true)。
_R_WIN_CHECK_INVALID_PARAMETERS_
¶检查传递给 Windows C 运行时的参数是否有效。启用后,如果 C 运行时函数被调用时传递了无效参数,R 会打印诊断信息并无条件终止。默认情况下,遵循 MinGW-W64 默认值的 Windows 版 R 会导致 UCRT 忽略无效参数,但它们可能会导致 R 终止,例如当 R 被嵌入到使用不同编译器工具链构建的应用程序中时。可以通过 _invoke_watson
将值设置为 "watson"
以在 Windows 上进行交互式调试。默认:禁用。
_R_CHECK_BROWSER_NONINTERACTIVE_
¶如果设置为真值,则会捕获非交互式调试器调用。 这些调用很可能是由包代码中遗留的 browser()
语句引起的。 默认值:未设置(但对于 CRAN 提交检查为真)。
以下变量控制对其他包的未声明/无条件使用的检查。 它们通过设置一个临时库目录并将 .libPaths()
设置为仅该目录和 .Library
来工作,因此只有在其他包安装在 .Library
以外的地方时才有效。 临时库由指向未安装在 .Library
中的已安装包的符号链接22 填充。
_R_CHECK_INSTALL_DEPENDS_
¶如果设置为真值并且要进行测试安装,则使用由所有 Depends/Imports/LinkingTo 包填充的临时库进行安装。 默认值:假(但对于 CRAN 提交检查为真)。
请注意,这实际上是在 R CMD INSTALL
中实现的,因此对于那些首先将记录安装到日志中,然后调用 R CMD check
的人来说是可用的。
_R_CHECK_SUGGESTS_ONLY_
¶如果设置为真值,则运行示例、测试和小插图时,将使用由所有 Depends/Imports/Suggests 包填充的临时库目录。 (作为例外,‘VignetteBuilder’ 字段中的包始终可用。)默认值:假(但对于 CRAN 提交检查为真:一些常规检查使用真值,而另一些则使用假值)。
_R_CHECK_DEPENDS_ONLY_
¶与 _R_CHECK_SUGGESTS_ONLY_
相同,但仅使用 Depends/Imports(以及例外,包括 ‘Suggests’ 中的测试套件管理器)。 默认值:假
_R_CHECK_DEPENDS_ONLY_DATA_
¶仅将 _R_CHECK_DEPENDS_ONLY_
应用于从 data 目录加载的检查,因此检查任何数据集是否依赖于 Suggests 中的包或未声明的包。 默认值:假(但对于 CRAN 提交检查为真)
_R_CHECK_DEPENDS_ONLY_EXAMPLES_
¶_R_CHECK_DEPENDS_ONLY_TESTS_
¶_R_CHECK_DEPENDS_ONLY_VIGNETTES_
¶仅将 _R_CHECK_DEPENDS_ONLY_
应用于示例、测试或小插图的检查。这些可以单独使用,也可以与假值一起使用来覆盖 _R_CHECK_DEPENDS_ONLY_
。默认值:_R_CHECK_DEPENDS_ONLY_
的值,如果未设置则为假。
_R_CHECK_NO_RECOMMENDED_
¶如果设置为真值,则会增强之前的检查,使推荐的包在未声明的情况下不可用(即使安装在 .Library
中)。默认值:假(但对于 CRAN 提交检查为真)。
这可能会对使用 grDevices::densCols
和 stats:::.asSparse
/ stats:::.Diag
的代码产生误报,因为这些代码分别调用了 KernSmooth 和 Matrix。(stats 中的那些在使用 sparse = TRUE
时从各种对比函数调用。)
CRAN 的提交检查使用类似于
_R_CHECK_CRAN_INCOMING_=TRUE _R_CHECK_CRAN_INCOMING_REMOTE_=TRUE _R_CHECK_VC_DIRS_=TRUE _R_CHECK_TIMINGS_=10 _R_CHECK_INSTALL_DEPENDS_=TRUE _R_CHECK_SUGGESTS_ONLY_=TRUE _R_CHECK_NO_RECOMMENDED_=TRUE _R_CHECK_EXECUTABLES_EXCLUSIONS_=FALSE _R_CHECK_DOC_SIZES2_=TRUE _R_CHECK_CODE_ASSIGN_TO_GLOBALENV_=TRUE _R_CHECK_CODE_ATTACH_=TRUE _R_CHECK_CODE_DATA_INTO_GLOBALENV_=TRUE _R_CHECK_CODE_USAGE_VIA_NAMESPACES_=TRUE _R_CHECK_DOT_FIRSTLIB_=TRUE _R_CHECK_DEPRECATED_DEFUNCT_=TRUE _R_CHECK_REPLACING_IMPORTS_=TRUE _R_CHECK_SCREEN_DEVICE_=stop _R_CHECK_TOPLEVEL_FILES_=TRUE _R_CHECK_OVERWRITE_REGISTERED_S3_METHODS_=TRUE _R_CHECK_PRAGMAS_=TRUE _R_CHECK_COMPILATION_FLAGS_=TRUE _R_CHECK_R_DEPENDS_=warn _R_CHECK_SERIALIZATION_=TRUE _R_CHECK_R_ON_PATH_=TRUE _R_CHECK_PACKAGES_USED_IN_TESTS_USE_SUBDIRS_=TRUE _R_CHECK_SHLIB_OPENMP_FLAGS_=TRUE _R_CHECK_CONNECTIONS_LEFT_OPEN_=TRUE _R_CHECK_FUTURE_FILE_TIMESTAMPS_=TRUE _R_CHECK_AUTOCONF_=true _R_CHECK_DATALIST_=true _R_CHECK_THINGS_IN_CHECK_DIR_=true _R_CHECK_THINGS_IN_TEMP_DIR_=true _R_CHECK_BASHISMS_=true _R_CHECK_ORPHANED_=true _R_CHECK_BOGUS_RETURN_=true _R_CHECK_MATRIX_DATA_=TRUE _R_CHECK_CODE_CLASS_IS_STRING_=true _R_CHECK_RD_VALIDATE_RD2HTML_=true _R_CHECK_RD_MATH_RENDERING_=true _R_CHECK_NEWS_IN_PLAIN_TEXT_=true _R_CHECK_BROWSER_NONINTERACTIVE_=true
这些由 R CMD check --as-cran
启用:传入的检查也使用
_R_CHECK_FORCE_SUGGESTS_=FALSE
因为一些包确实建议了在 CRAN 或其他常用存储库中不可用的其他包。
可以使用多个环境变量来设置“超时”:用于检查部分的子进程所用时间的限制。值为 0
表示没有限制,也是默认值。以“s”、“m”或“h”结尾的字符串分别表示秒数、分钟数或小时数:其他值被解释为秒数(无效输入被视为没有限制)。
_R_CHECK_ELAPSED_TIMEOUT_
¶未另行说明的子进程的默认超时时间,以及除 _R_CHECK_ONE_TEST_ELAPSED_TIMEOUT_
外所有值的默认值。(这也由 tools::check_packages_in_dir
使用。)
_R_CHECK_INSTALL_ELAPSED_TIMEOUT_
¶当 R CMD INSTALL
由 check
运行时的限制。
_R_CHECK_EXAMPLES_ELAPSED_TIMEOUT_
¶运行一个子架构的所有示例的限制。
_R_CHECK_ONE_TEST_ELAPSED_TIMEOUT_
¶运行一个子架构的一个测试的限制。默认值 _R_CHECK_TESTS_ELAPSED_TIMEOUT_
。
_R_CHECK_TESTS_ELAPSED_TIMEOUT_
¶运行一个子架构的所有测试的限制(以及运行一个测试的默认限制)。
_R_CHECK_ONE_VIGNETTE_ELAPSED_TIMEOUT_
¶运行一个小插图中的 R 代码的限制,包括单独重建每个小插图。
_R_CHECK_BUILD_VIGNETTES_ELAPSED_TIMEOUT_
¶重新构建所有小插图的限制。
_R_CHECK_PKGMAN_ELAPSED_TIMEOUT_
¶每次尝试构建 PDF 包手册的限制。
另一个可以启用更严格检查的变量是将 R_CHECK_CONSTANTS
设置为 5
。这将检查 R 代码中没有任何内容23 更改“常量”的值24。最好将此与将 R_JIT_STRATEGY
设置为 3
结合使用,这将在第一次使用时检查代码(默认情况下,大多数代码仅在第二次使用时进行字节编译后才进行检查)。不幸的是,这些检查会减慢示例、测试和文档的检查速度,通常会减慢两倍,但在最坏的情况下至少会减慢一百倍。
以下环境变量可用于自定义 INSTALL
的操作。
_R_INSTALL_LIBS_ONLY_FORCE_DEPENDS_IMPORTS_
¶如果为真,则在仅通过 --libs-only 安装包库并且某些导入或依赖的包不可用时,给出错误。默认值:true(仅对于分析包的本机代码的特殊应用程序为 false)。
R 旨在运行在各种平台上,包括 Linux 和大多数 Unix 变体以及 Windows 和 macOS。因此,在通过添加 R 基础发行版或提供附加包来扩展 R 时,应避免依赖于仅少数支持平台特有的功能。特别是,尽管大多数 R 开发人员使用 GNU 工具,但他们不应使用 GNU 对标准工具的扩展。虽然其他一些软件包明确依赖于例如 GNU make 或 GNU C++ 编译器,但 R 并不依赖。然而,R 是一个 GNU 项目,并且在可能的情况下应遵循 GNU 编码标准 的精神。
以下工具可以“安全地假定”用于 R 扩展。
make
,将 4.2 BSD 系统中 make
的功能视为基线。
GNU 或其他扩展,包括使用“%”的模式规则、自动变量“$^”、用于追加到变量值的“+=”语法、(“安全”)包含没有错误的 makefile、条件执行等等,不能使用(有关更多信息,请参阅 GNU Make 手册 中的“功能”一章)。另一方面,在单独的目录(不包含源代码)中构建 R 应该可以工作,前提是 make
支持 VPATH
机制。
Windows 特定的 makefile 可以假设 GNU make
3.79 或更高版本,因为该平台上没有其他 make
可行。
grep
、sed
和 awk
。
这些工具有 POSIX 标准,但这些标准可能没有得到完全支持。基本功能可以从 Brian W. Kernighan 和 Rob Pike 编写的 The UNIX Programming Environment 等书籍中确定。特别要注意,正则表达式中的 ‘|’ 是扩展正则表达式,并非所有版本的 grep
或 sed
都支持。Open Group Base Specifications,Issue 7,在技术上与 IEEE Std 1003.1 (POSIX),2008 相同,可在 https://pubs.opengroup.org/onlinepubs/9699919799/mindex.html 获取。
在 Windows 下,大多数用户不会安装这些工具,您也不应该要求您的软件包运行时必须存在这些工具。但是,从源代码安装您的软件包的用户将拥有这些工具,因为可以假设他们已按照“R 安装和管理”手册的“Windows 工具集”附录中的说明获取了这些工具。不能假设重定向可以通过 system
使用,因为这不会使用标准 shell(更不用说 Bourne shell 了)。
此外,以下工具对于某些任务是必需的。
texinfo
5.1 或更高版本是构建用 GNU Texinfo 系统编写的 R 手册的 HTML、PDF 和 Info 文件所必需的。这需要 Perl。
代码以允许其他人理解的方式编写也很重要。这对于解决问题特别有用,包括使用自描述的变量名、对代码进行注释以及正确格式化代码。R Core 团队建议对 R 和 C(以及大多数情况下 Perl)代码使用 4 个基本缩进,对 Rd 格式的文档使用 2 个基本缩进。Emacs(21 或更高版本)用户可以通过将以下内容放入其启动文件之一来实现这种缩进风格,并使用自定义将 c-default-style
设置为 "bsd"
,并将 c-basic-offset
设置为 4
。)
;;; ESS (add-hook 'ess-mode-hook (lambda () (ess-set-style 'C++ 'quiet) ;; Because ;; DEF GNU BSD K&R C++ ;; ess-indent-level 2 2 8 5 4 ;; ess-continued-statement-offset 2 2 8 5 4 ;; ess-brace-offset 0 0 -8 -5 -4 ;; ess-arg-function-offset 2 4 0 0 0 ;; ess-expression-offset 4 2 8 5 4 ;; ess-else-offset 0 0 0 0 0 ;; ess-close-brace-offset 0 0 0 0 0 (add-hook 'local-write-file-hooks (lambda () (ess-nuke-trailing-whitespace))))) (setq ess-nuke-trailing-whitespace-p 'ask) ;; or even ;; (setq ess-nuke-trailing-whitespace-p t)
;;; Perl (add-hook 'perl-mode-hook (lambda () (setq perl-indent-level 4)))
(Emacs 的 C 和 R 模式的“GNU”风格使用 2 个基本缩进,当使用窄字体时,这已被确定为无法清晰地显示结构。)
当您(作为 R 开发人员)向 R 基础(所有与 R 一起分发的包)添加新函数时,请务必检查 make test-Specific 或特别是 cd tests; make no-segfault.Rout 是否仍然有效(无需用户交互,并且在独立计算机上)。例如,如果新函数访问互联网或需要 GUI 交互,请将其名称添加到 tests/no-segfault.Rin 中的“停止列表”中。
[待修改:使用 make check-devel
,如果您更改内部结构,请检查写入屏障。]
R 中使用各种 TeX 方言来实现不同的目的。策略是将手册编写为“texinfo”,为了方便起见,主 FAQ 和 Windows FAQ 也是如此。这样做的好处是可以轻松生成 HTML 和纯文本版本以及排版手册。
LaTeX 不直接使用,而是作为排版帮助文档和小插图的中间格式。
需要谨慎考虑对 R 用户系统所做的假设:它可能没有安装“texinfo”或 TeX 系统。我们已尝试抽象出跨平台差异,几乎所有排版文档的设置都是由 tools::texi2dvi
完成的。它用于离线打印帮助文档、准备小插图以及通过 R CMD Rd2pdf
创建包手册。它目前不用于在 doc/manual 目录中创建的 R 手册。
tools::texi2dvi
使用系统命令 texi2dvi
(如果可用)。在类 Unix 系统上,这通常是“texinfo”的一部分,而在 Windows 上,如果它存在,它将是一个可执行文件,是 MiKTeX 的一部分。如果不可用,R 代码将运行一系列 (pdf)latex
、bibtex
和 makeindex
命令。
此过程一直容易受到所用外部软件版本的攻击:特别的问题是 texi2dvi
和 texinfo.tex 更新、两者之间的不匹配25、LaTeX 包 ‘hyperref’ 的版本以及索引生成中的怪癖。用于 LaTeX 和后来 ‘texinfo’ 的许可证禁止我们在 R 发行版中包含“已知良好”版本。
在类 Unix 系统上,configure
会查找 TeX 及其相关程序的可执行文件,如果找到,则将绝对路径记录在系统 Renviron 文件中。以前,如果找不到命令,则会记录 ‘false’,但现在会记录名称以便在运行时通过路径查找。后者对于二进制发行版可能很重要:我们不希望绑定到例如 TeX Live 2007。
本章用于记录 R 可能正在进行和未来的更改:我们没有承诺发布此类更改,更不用说时间表了。
R 2.x.y 中的向量长度限制为 2^31 - 1 个元素(约 20 亿),因为长度存储在 SEXPREC
中,作为 C int
,并且该类型被广泛用于记录长度和元素编号,包括在包中。
请注意,在 32 位平台下,更长的向量实际上是不可能的,因为它们有地址限制,因此本节仅适用于 64 位平台。在 32 位版本的 R 中,内部结构保持不变。
如果单个对象具有 2^31 个或更多元素,则如果为整数或逻辑类型,则至少占用 8GB 内存,如果为数值或字符类型,则占用 16GB 内存,因此常规使用此类对象还需要一段时间。
现在对长向量有一些支持。这适用于原始、逻辑、整数、数值和字符向量,以及列表和表达式向量。(字符向量 (CHARSXP
) 的元素仍然限制为 2^31 - 1 字节。)一些注意事项
-1
并将实际长度记录为标题开头的 64 位字段来实现的。由于 R 中相当多的代码使用带符号类型来表示长度,因此“长长度”使用带符号的 C99 类型 ptrdiff_t
记录,该类型被 typedef 为 R_xlen_t
。
-1
,后面跟着两个 32 位字段,分别给出实际长度的较高 32 位和较低 32 位。目前有一个健全性检查,在反序列化时将长度限制为 2^48。
R_xlen_t
在 C 头文件 Rinternals.h 中提供给包:这在 C 代码中应该没问题,因为需要 C99。人们确实尝试在 C++ 中使用 R 内部函数,但 C++98 编译器不需要支持这些类型。
INTSXP
或 REALSXP
索引。
length
将返回一个双精度浮点值。在传递给 .C
/.Fortran
之前调用 as.integer(length(x))
的代码应该检查 NA
结果。
也有一些愿望能够在 R 中存储更大的整数,尽管将这些整数存储为 double
的可能性经常被忽视(例如,由 seek
返回的文件指针已经存储为 double
)。
已经提出了不同的路线
longint
。R 的通常隐式强制规则将确保为索引或 length<-
提供一个 integer
向量可以正常工作。
integer
类型在 64 位平台上改为 64 位(S-PLUS 对 DEC/Compaq Alpha 系统采用的就是这种方法)。或者在所有平台上都这样做。
integer
或 double
值,并在必要时返回 double
。
第三种方案的优点是,对现有代码的干扰最小,并且不会增加内存需求。在第一种和第三种方案中,R 的代码和用户代码都需要适应非 integer
类型的长度,而在第三种方案中,长向量的代码分支很少被测试。
大多数 .C
和 .Fortran
接口的用户使用 as.integer
来表示长度和元素编号,但少数用户会省略这些,因为他们知道这些是 integer
类型的。可以合理地假设这些永远不会用于长向量。
其余的接口需要处理更改后的 VECTOR_SEXPREC
类型。似乎在大多数情况下,长度是通过 length
和 LENGTH
函数26 访问的。当前的方法是保持这些函数返回 32 位长度,并引入“长”版本 xlength
和 XLENGTH
,它们返回 R_xlen_t
值。
另请参见 https://homepage.cs.uiowa.edu/~luke/talks/useR10.pdf.
矩阵存储为向量,因此也限制为 2^31-1 个元素。现在,在 64 位平台上允许更长的向量,支持具有更多元素的矩阵,只要每个维度不超过 2^31-1。但是,并非所有应用程序都支持。
主要问题是使用 32 位 INTEGER
编译的 Fortran 代码执行的线性代数。虽然没有保证,但似乎目前在 64 位平台上与 R 一起使用的所有编译器都允许每个维度小于 2^31 但具有 2^31 个或更多元素的矩阵,并正确地对其进行索引,并且大部分支持软件(例如 BLAS 和 LAPACK)也能正常工作。
存在例外情况:例如,一些复杂的 LAPACK 辅助例程确实使用单个 INTEGER
索引,因此会静默溢出并导致段错误或给出错误的结果。一个已知的例子是在复数矩阵上使用 svd()
函数。
由于这是实现相关的,因此优化的 BLAS 和 LAPACK 可能存在进一步的限制:在使用 ATLAS 在 ‘x86_64’ Linux 上运行 svd()
函数时,已报告出现段错误。
对于大型矩阵的矩阵代数运算,几乎肯定需要一台具有大量内存(数百 GB)、多个核心和多线程 BLAS 的机器。
跳转到: | _
.
A C D E G I L M N P R S T U V W |
---|
跳转到: | _
.
A C D E G I L M N P R S T U V W |
---|
跳转到: | .
A B C E F G L M N P S U V W |
---|
跳转到: | .
A B C E F G L M N P S U V W |
---|
严格来说,一个 SEXPREC
节点;VECTOR_SEXPREC
节点略小,但后面跟着节点中的数据。
指向一个函数的指针,或一个用于按名称查找函数的符号,或一个要计算以得到函数的语言对象。
目前唯一的用途是用于环境的哈希表(VECSXP
),其中length
是表的尺寸,truelength
是使用的主要槽位数,用于序列化中的引用哈希表(VECSXP
),以及用于“可增长”向量(原子向量,VECSXP
和EXPRSXP
),这些向量是在子赋值时通过略微过度分配创建的,以便在子赋值期间可以就地执行一些后续的扩展),其中truelength
是使用的槽位数。
请记住,附加一个列表或一个保存的镜像实际上会创建一个环境并填充它,然后附加它。
目前还有一个区别:在分析时,内置函数被计为函数调用,而特殊函数则不计。
另一个当前的例子是左大括号,它被实现为一个原语。
目前仅使用位 0:4 用于SEXPTYPE
,但值 241:255 用于伪SEXPTYPE
。
目前唯一相关的位是 0:1、4、14:15。
请参阅文件 src/main/gram.c 中旧版本中的定义 USE_UTF8_IF_POSSIBLE
。
或者 UTF-16,如果操作系统启用了对代理的支持,而这在将编码支持添加到 R 时并不支持。
但不包括 GraphApp 工具包。
这也可以创建非 S4 对象,例如 new("integer")
。
虽然不推荐这样做,因为它不太面向未来。
但显然在 Windows 上不行。
C 代码位于目录 src/main 中的文件 base.c、graphics.c、par.c、plot.c 和 plot3d.c 中。
虽然这需要谨慎处理,例如 circle
回调会得到一个半径(并且应该按 x 单位解释)。
设备可以找到指向其 DevDesc
的 GEDevDesc
,并且这经常发生,因此有一个方便的函数 desc2GEDesc
可以做到这一点。
调用 R_CheckDeviceAvailable()
可确保存在一个空闲插槽,否则会抛出错误。
在设备坐标中
在诸如打印机之类的元文件设备上使用 Alpha 混合在技术上是可行的,但似乎很少有驱动程序支持此功能。
一个 Xcode 项目,位于 SVN 中 https://svn.r-project.org/R-packages/trunk/Mac-GUI/。
在 Windows 下,如果环境变量 R_WIN_NO_JUNCTIONS
的值不为空,则使用连接点或副本。
通常的罪魁祸首是通过 .Call
或 .External
调用编译代码 via,这些代码会更改其参数。
字节编译器假设不会改变的东西,例如函数体。
Linux 发行版倾向于将 texinfo.tex 从 ‘texinfo’ 中解包。
但 LENGTH
在某些内部使用中是宏。