c++pointersfortranfortran-iso-c-binding

Checking `c_associated` on a `nullptr` causes segfault unless pointer has `value` attribute


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?


Solution

  • 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