In short: I have a fortran subroutine that accepts a c_ptr
, and checks if it is null
by using c_associated
. Unless I give the pointer the VALUE
attribute, the check triggers a segfault.
I have found a way to work around this, but I am interested in what is causing this behaviour. To me it smells like it has something to do with Fortran implicitly passing by reference. With my main library (not shown) this behaviour only shows up when I compile with optimizations (not when using -O0 -g
), but I have not been able to create a minimal example reproducing that behaviour.
Example:
In main.cpp
#include <iostream>
extern "C" {
void test_ptr(double* ptr);
}
int main(){
double* ptr{nullptr};
std::cout << "In C++, pointer is " << ptr << std::endl;
test_ptr(ptr);
std::cout << "Fortran exited without fault." << std::endl;
return 0;
}
In test.f90
module test
use, intrinsic :: iso_c_binding
public test_ptr
contains
subroutine test_ptr(ptr) BIND(C)
use, intrinsic :: iso_c_binding
type(c_ptr), target, intent(in) :: ptr
print*, "In Fortran, pointer is", ptr
if (c_associated(ptr)) then
print*, 'Pointer is not null'
else
print*, 'Pointer is null'
endif
end subroutine test_ptr
end module test
If I compile and link this with
gfortran -c test.f90 test.o -O3
g++ -c main.cpp main.o -O3
g++ -o main main.o test.o -lgfortran
running ./main
gives
In C++, pointer is 0
In Fortran, pointer is 0
Segmentation fault: 11
I have found two ways of fixing this:
Either by declaring ptr
as
type(c_ptr), target, intent(in), value :: ptr
or by changing the C++-side to pass &ptr
, as.
extern "C" {
void test_ptr(double** ptr);
}
int main(){
double* ptr = nullptr;
std::cout << "In C++, pointer is " << ptr << std::endl;
test_ptr(&ptr);
std::cout << "Fortran exited without fault." << std::endl;
return 0;
}
Both these changes (individually) lead to the expected output
In C++, pointer is 0
In Fortran, pointer is 0
Pointer is null
Fortran exited without fault.
To me it almost seems like c_associated
is at some point trying to dereference ptr
, without first checking if it is null
, is this expected behaviour? Is there another (preferred) way of checking if a c_ptr
is null
?
The value
attribute is a Fortran 2003 feature (some compiler support it prior to that as extension). Without it it assumes that a pointer ptr
is passed by address (in C), constness defined by intent(in)
notwithstanding, which would be an equivalent of reference to a pointer in C++. And you supplied a null pointer as that address while Fortran would dereference it implicitly on any use, e.g. ptr = something
is equivalent of C code *ptr = something
.
The void test_ptr(double** ptr);
would be the correct equivalent signature for function without value
attribute.
PS. If you're using a legacy Fortran 90 compiler, there might be some quirks or different behaviour that do not match modern standard behavior, as value
wasn't in standard