Writing R Extensions says to null-terminate external interface arrays. Examples and CRAN packages (dplyr, stringi + comment, data.table) all include {NULL, NULL, 0}
.
The following foo
package passes R CMD check, no notes.
bar = \(x) .Call(c_bar, x)
#include <Rinternals.h>
int bar(SEXP baz) {return TYPEOF(baz);}
static const R_CallMethodDef entries[] = {
{"c_bar", (DL_FUNC) &bar, 1} // No {NULL, NULL, 0}
};
void R_init_foo(DllInfo* dll) {
R_registerRoutines(dll, NULL, entries, NULL, NULL);
R_useDynamicSymbols(dll, FALSE);
}
Question: Why is {NULL, NULL, 0}
required / recommended?
The source code of R_registerRoutines
in src/main/Rdynload.c
(link) is helpful:
if(callRoutines) {
for(num = 0; callRoutines[num].name != NULL; num++) {;}
info->CallSymbols =
(Rf_DotCallSymbol*)calloc((size_t) num, sizeof(Rf_DotCallSymbol));
info->numCallSymbols = num;
for(i = 0; i < num; i++)
R_addCallRoutine(info, callRoutines+i, info->CallSymbols + i);
}
Here, callRoutines
is a pointer to the first element of an array of R_CallMethodDef
structs. By not terminating the array with a "null struct", you are relying on undefined behaviour in the second loop iteration at callRoutines[num].name != NULL
, where the array is indexed out of bounds.