Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do I get a segfault when calling my C++ function with .Call rather than .C?

Tags:

c++

r

My end goal is to call some C++ functions from within R, here is a MNWE of where I'm hitting a roadblock. If I'm reading the room correctly, I seem to get a segfault when I call my function with .Call but when I call it with .C everything works fine.

Here is my short C++ function

// test.cpp
#include <iostream>

extern "C" void fnTest() {
  std::cout << "Hello" << std::endl;
}

Which I then compiled with

R CMD SHLIB -o test.so test.cpp

Which gave the following output:

g++ -std=gnu++11 -shared -L/usr/lib64/R/lib -Wl,-O1,--sort-common,--as-needed,-z,relro,-z,now -o te
st.so test.o -L/usr/lib64/R/lib -lR

Now within R I did

> dyn.load("test.so")
> .C("fnTest")
Hello
list()
> .Call("fnTest")
Hello

 *** caught segfault ***
address 0x30, cause 'memory not mapped'

Possible actions:
1: abort (with core dump, if enabled)
2: normal R exit
3: exit R without saving workspace
4: exit R saving workspace
Selection:

The documentation that I read for these two functions is here and didn't seem to indicate much of a difference in the calling format of the two functions. I tried several other variations (e.g. I was was able to pass arguments successfully to .C but not .Call) and didn't have any success.

What is the proper way to .Call a C++ function from within R?

Some notes on my eventual use case beyond this minimal example, hopefully this is not an XY problem: I have a project with many complicated dependencies which I know how to build with CMake but not directly from g++. I was able to build a shared library from this project that I could then link into an "R compatible" shared library (R CMD SHLIB -o test.so test.cpp -L/path/to/my/lib/ -l my_lib_name) which I was able to dyn.load() into my R environment. At that point I then ran into the above .C vs. .Call issue.

like image 541
Jay Avatar asked Apr 06 '20 21:04

Jay


People also ask

What causes a segfault in C?

In practice, segfaults are almost always due to trying to read or write a non-existent array element, not properly defining a pointer before using it, or (in C programs) accidentally using a variable's value as an address (see the scanf example below).

How do you resolve a segfault?

It can be resolved by having a base condition to return from the recursive function. A pointer must point to valid memory before accessing it.

Is segfault a runtime error?

The segmentation error is one of the runtime error, that is caused because of the memory access violation, like accessing invalid array index, pointing some restricted address etc. In this article, we will see how to detect this type of error using the GDB tool.

What causes segmentation fault with pointers?

A segmentation fault usually occurs when you try to access data via pointers for which no memory has been allocated. It is thus good practice to initialize pointers with the value NULL, and set it back to NULL after the memory has been released.


1 Answers

From reading some additional documentation (that I should have found on the first pass), I believe that you cannot .Call a function that has a return type of void. I could not find an explicit mention of this, but no example in the documentation (e.g. this section) listed a return type other than SEXP and at one point the documentation states that:

All the R objects you will deal with will be handled with the type SEXP

On the other hand, as documented in the Interface functions .C and .Fortran section, any function that you .C must have a return type of void:

Note that the compiled code should not return anything except through its arguments: C functions should be of type void and Fortran subprograms should be subroutines.

Here are some examples that can be compiled as in the OP. It didn't seem like there was a default "null" return type for .Call'ed functions, but allocVector(REALSXP, 0) R_NilValue seemed to work well.

// test.cpp

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

extern "C" void fnPrintC() {
  Rprintf("Hello world!\n");
}

extern "C" SEXP fnPrintCall() {
  Rprintf("Hello world!\n");

  // return allocVector(REALSXP, 0);
  return R_NilValue;
}

extern "C" SEXP fnAddCall(SEXP a, SEXP b) {
  double* xa = REAL(a);
  double* xb = REAL(b);

  SEXP ans = allocVector(REALSXP, 2);
  REAL(ans)[0] = *xa + *xb;
  REAL(ans)[1] = *xa - *xb;

  return ans;
} 

Here they are called from R. Note we can send the (void) output to the dummy variable x if we don't want to see it.

> dyn.load("test.so")

> x <- .C("fnPrintC")
> Hello world!

> x <- .Call("fnPrintCall")
> Hello world!

> .Call("fnAddCall", 4, 3)
> [1] 7 1

In general, the documentation linked above was pretty helpful, I recommend starting there for anyone with a similar question, I certainly wish I'd read it more thoroughly earlier on.

like image 184
Jay Avatar answered Sep 30 '22 08:09

Jay