I'm trying to write a mixed-language (C++/Fortran) code in which memory passed from C++ to the Fortran subroutine may or may not be used and so in fact may or may not be allocated. The behavior inside the Fortran subroutine should be determined by an additional integer flag that is passed from C++ to Fortran. The problem that I am encountering seems to occur primarily with the Intel compiler (2022.1.2, 2022.1.0, 2021.4.0, 2021.1) combined with optimization (-O2). In that case, the compiler ignores my conditional statement and chooses to make an assignment from the unallocated memory, triggering a segmentation fault.
This example, consisting of one .cpp file, one .F90 file, and one Makefile, should demonstrate the problem: The line in the Fortran code that says "my_number = numbers" should never be executed based on the surrounding conditional, but with optimization, the line triggers a segmentation fault anyway.
Is there a way to prevent the optimization from ignoring my conditional or a better way to handle this overall (while maintaining the mixed-language paradigm)?
main.cpp:
extern "C" {
void test_module_mp_test_routine_(int* numbers,int* allocated);
}
int main(void)
{
int* numbers(0);
bool allocated(numbers);
int allocated_int(allocated);
test_module_mp_test_routine_(numbers,&allocated_int);
return 0;
}
test_module.F90:
! ============================================================
module test_module
! ============================================================
implicit none
private
save
public :: test_routine
contains
! ============================================================
subroutine test_routine(numbers,allocated_int)
! ============================================================
implicit none
integer :: numbers,allocated_int
logical :: have_numbers
integer :: my_number
continue
have_numbers = (allocated_int == 1)
my_number = 0
if (have_numbers) then
my_number = numbers
end if
write(*,*)"allocated_int = ",allocated_int
if (have_numbers) then
write(*,*)"my_number = ",my_number
end if
return
end subroutine test_routine
end module test_module`
Makefile:
CXX=icpc
FORT=ifort
main: main.cpp test_module.o
$(CXX) -O2 -o $@ $^ -lifcore
test_module.o: test_module.F90
$(FORT) -O2 -c $<
clean:
rm -f test_module.{o,mod} main
Within a Fortran procedure like
subroutine sub(x)
real :: x
end subroutine
the argument x
is an ordinary (data) dummy variable. Within Fortran, if such a dummy variable is argument associated with an allocatable actual argument, that actual argument must be allocated (Fortran 2018, 15.5.2.4 p7); if argument associated with a pointer argument, that pointer argument must be pointer associated (F2018, 15.5.2.3 p1).
If we have something like
program bad
implicit none
real, allocatable :: a
real, pointer :: b => null
call sub(a)
call sub(b)
contains
subroutine sub(x)
real :: x
end subroutine
end program bad
we have problems in each call to sub
. You can ask your friendly compiler to tell you about these problems, instead of presenting a segmentation fault, or whatever other nastiness it chooses to perform. With ifort -check all
is the way to ask.
Now, when calling a Fortran subroutine like this by means other than Fortran (using C interoperation, for example, or C++ hacky stuff) our compiler-generated runtime test may not work, and Fortran's rules about allocation/pointer association could be seen as a little fuzzy. Still, we can work fully within the meaning of the code and Fortran's requirements by stating the goal here as "optional arguments". If we don't want to use a variable's value within the procedure, we avoid providing one.
Going back to our Fortran program with a change:
program lessbad
implicit none
real, allocatable :: a
real, pointer :: b => null
call sub(a)
call sub(b)
contains
subroutine sub(x)
real, optional :: x ! A difference
end subroutine
end program lessbad
For the optional x
we are allowed to argument associate an unallocated actual argument or a disassociated pointer actual argument. Simply, in these cases x
is taken as not present (F2018, 15.5.2.12).
Optional arguments are interoperable with C in Fortran 2018, as implemented by recent Intel compilers:
subroutine test_routine(numbers) bind(c)
use, intrinsic :: iso_c_binding, only : c_int
integer(c_int), optional :: numbers
logical :: have_numbers
integer :: my_number
have_numbers = PRESENT(numbers)
my_number = 0
if (have_numbers) then
my_number = numbers
end if
if (have_numbers) then
write(*,*)"my_number = ",my_number
end if
end subroutine test_routine
An optional dummy argument is absent if (and only if) it is associated with a (C) null pointer (F2018, 18.3.6 p7).
In the example of the question, the pointer nature of the dummy argument is not used: it's not necessary for the dummy to be a pointer. In more general cases it may be necessary to have the dummy be a pointer: here this other answer shows how to use c_associated
to test whether a type(c_ptr)
is associated. In even more general cases it's possible to have a dummy a Fortran pointer (instead of a C pointer), and use associated
, but that's somewhat more advanced.
As also stated in that other answer, small changes to the invocation and C prototype definition are required, to support these alternative approaches.