2013-10-29 20 views
38

中处理我不知道是否有人可以向我说明R如何通过在控制台提示符下键入的R命令执行C调用。我特别困惑于R对a)函数参数的处理和b)函数调用本身。了解如何。内部的C函数在R

我们举一个例子,在这种情况下为set.seed()。想知道它是如何工作的我在提示符下键入名称,获取源代码(look here for more on that),看看最终有一个.Internal(set.seed(seed, i.knd, normal.kind),请尽快查找/src/names.c.Internals部分中的相关函数名称,找到它的名称为do_setseed,并且位于RNG.c这使我...

SEXP attribute_hidden do_setseed (SEXP call, SEXP op, SEXP args, SEXP env) 
{ 
    SEXP skind, nkind; 
    int seed; 

    checkArity(op, args); 
    if(!isNull(CAR(args))) { 
    seed = asInteger(CAR(args)); 
    if (seed == NA_INTEGER) 
     error(_("supplied seed is not a valid integer")); 
    } else seed = TimeToSeed(); 
    skind = CADR(args); 
    nkind = CADDR(args); 
    //... 
     //DO RNG here 
    //... 
    return R_NilValue; 
} 
  • 什么是CARCADRCADDR?我的研究让我相信他们是一个Lisp受影响的结构关于名单,但除此之外,我不明白这些功能或为什么需要
  • checkArity()是做什么用的?
  • SEXP args似乎不言自明,但是这是在函数调用中传递的参数 的列表吗?
  • SEXP op代表什么?我认为这意味着运营商(如在二进制功能,如+),但那么什么是SEXP call

是任何人都能够通过,当我在R控制台提示符下键入

set.seed(1) 

,高达skindnkind被定义在这点会发生什么流?我发现我无法很好地理解这个级别的源代码以及从解释器到C函数的路径。

+6

您可能想从http://adv-r.had.co.nz/C-interface.html开始 - 我将在稍后为此特定示例撰写一些更多详细信息。 – hadley

+0

@hadley谢谢,我今晚会经历这个。我真的很期待你的书被释放!很快,我希望。 :-) –

+0

@ SimonO101:我会在你的问题标题 –

回答

23

CARCDR是您如何访问pairlist对象,如section 2.1.11 of R Language Definition中所述。 CAR包含第一个元素,而CDR包含其余元素。一个例子section 5.10.2 of Writing R Extensions给出:

#include <R.h> 
#include <Rinternals.h> 

SEXP convolveE(SEXP args) 
{ 
    int i, j, na, nb, nab; 
    double *xa, *xb, *xab; 
    SEXP a, b, ab; 

    a = PROTECT(coerceVector(CADR(args), REALSXP)); 
    b = PROTECT(coerceVector(CADDR(args), REALSXP)); 
    ... 
} 
/* The macros: */ 
first = CADR(args); 
second = CADDR(args); 
third = CADDDR(args); 
fourth = CAD4R(args); 
/* provide convenient ways to access the first four arguments. 
* More generally we can use the CDR and CAR macros as in: */ 
args = CDR(args); a = CAR(args); 
args = CDR(args); b = CAR(args); 

还有一个TAG宏访问给予实际的参数的名字。

checkArity确保传递给函数的参数数量是正确的。 args是传递给函数的实际参数。 op是用于处理多个R函数的C函数的偏移指针(引自src/main/names.c,其中还包含显示每个函数的偏移量和参数的表)。例如,do_colsum句柄col/rowSumscol/rowMeans

/* Table of .Internal(.) and .Primitive(.) R functions 
* =====  =========  ========== 
* Each entry is a line with 
* 
* printname c-entry  offset eval arity pp-kind precedence rightassoc 
* --------- -------  ------ ---- ----- ------- ---------- ---------- 
{"colSums", do_colsum, 0,  11, 4,  {PP_FUNCALL, PREC_FN, 0}}, 
{"colMeans", do_colsum, 1,  11, 4,  {PP_FUNCALL, PREC_FN, 0}}, 
{"rowSums", do_colsum, 2,  11, 4,  {PP_FUNCALL, PREC_FN, 0}}, 
{"rowMeans", do_colsum, 3,  11, 4,  {PP_FUNCALL, PREC_FN, 0}}, 

注意arity在上表是4,因为(即使rowSums等人只能有3个参数)do_colsum有4个,你可以从.Internal呼叫rowSums看到:

> rowSums 
function (x, na.rm = FALSE, dims = 1L) 
{ 
    if (is.data.frame(x)) 
     x <- as.matrix(x) 
    if (!is.array(x) || length(dn <- dim(x)) < 2L) 
     stop("'x' must be an array of at least two dimensions") 
    if (dims < 1L || dims > length(dn) - 1L) 
     stop("invalid 'dims'") 
    p <- prod(dn[-(1L:dims)]) 
    dn <- dn[1L:dims] 
    z <- if (is.complex(x)) 
     .Internal(rowSums(Re(x), prod(dn), p, na.rm)) + (0+1i) * 
      .Internal(rowSums(Im(x), prod(dn), p, na.rm)) 
    else .Internal(rowSums(x, prod(dn), p, na.rm)) 
    if (length(dn) > 1L) { 
     dim(z) <- dn 
     dimnames(z) <- dimnames(x)[1L:dims] 
    } 
    else names(z) <- dimnames(x)[[1L]] 
    z 
} 
+0

+1谢谢!这很棒。因此'checkArity'可以有效地计算函数调用中参数的数量,查找函数并计算函数期望的参数数量,如果它们不匹配,则抛出受控错误以优雅地退出函数而不是调用函数错误的参数数量和可能的分段错误? –

+2

@ SimonO101:是的,它从'names.c'中的表中查找函数,以查看需要或允许的参数数量。你可以通过调用类似于'x < - 1; 。内部(rowSums(X))'。 –

18

基本的C级水平提升功能是CARCDR。 (Pairlists与列表非常相似,但实现为链接列表并在内部用于参数列表)。他们有简单的R等价物:x[[1]]x[-1]。 R还提供了很多在两个的组合:

  • CAAR(x) = CAR(CAR(x))这相当于x[[1]][[1]]
  • CADR(x) = CAR(CDR(x))这相当于x[-1][[1]],即x[[2]]
  • CADDR(x) = CAR(CDR(CDR(x))相当于x[-1][-1][[1]],即x[[3]]
  • 等on

访问第n个元素的第pairlist是一个O(n)操作,不像访问列表中的第n个元素是O(1)。这就是为什么访问pairlist的第n个元素没有更好的功能。

内部/原始函数不会按名称进行匹配,它们只使用位置匹配,这就是为什么他们可以使用这个简单的系统来提取参数。

接下来,您需要了解C函数的参数是什么。我不知道在哪里,这些都记录在案,所以我可能不完全正确有关的结构,但我应该是一般件:

  • call:完整的调用,如可能通过match.call()

  • 被捕获
  • op:从R中调用的.Internal函数的索引。这是必需的,因为从.Internal函数到C函数存在多对1映射。 (例如do_summary实施总和,平均值,最小值,最大值和产品)。数量是names.c第三项 - 它始终为0 do_setseed,因此从未使用过

  • args:提供给函数的参数成对列表。

  • env:函数被调用的环境。

checkArity是调用Rf_checkArityCall,基本上查找(在names.c第五列是元数)的参数的数量,并确保所提供的数字相匹配的宏。你必须遵循C中的一些宏和函数来看看发生了什么 - 有一个你可以通过grep通过的R源的本地副本是非常有帮助的。

+0

+1谢谢!因此,使用'CADDR(x)''有效地将访问时间保持为'O(1)'(我一直认为这样的操作的时间损失会很小,因为我无法想象函数,其中参数列表将得到* that * long?现在让我更加了解*阅读上面的示例(结合我正在阅读的书本章节) –

+0

@ SimonO101不,'CADDR(x )'会比'CDR(x)'慢,但可以像Joshua的例子一样使用CAR和CDR,以避免开销。 – hadley