سؤال

I'm looking at a slightly old Fortran program with C calls that works for 32-bit systems, but raises warnings and concerns with 64-bit compilers. The program stores the address of a C pointer to dynamically allocated memory as int, which is shared on the Fortran-side as INTEGER. My concern is that on 64-bit integer systems, the cast from C pointer could be larger than what can be stored as int/INTEGER. I've simplified the existing program to this example with two files.

Fortran: this.f

        program this
        integer,pointer :: iptr
        allocate(iptr)
        call that_allocate(iptr)
        write(*,'(A, Z12)') 'Fortran: iptr address', iptr
        call that_assemble(iptr)
        call that_free(iptr)
        end program this

C: that.c

#include <stdlib.h>
#include <stdio.h>

typedef struct data {
    int a;
    float b;
} data;

void that_allocate_(int* iptr)
{
    data *pData = calloc(1, sizeof(data));
    *iptr = (int)pData;
    printf("C: Allocated address %p (or %d)\n", pData, pData);
    return;
}

void that_assemble_(int* iptr)
{
    data *pData = (data *) *iptr;
    pData->a = 42;
    pData->b = 3.1415926;
    return;
}

void that_free_(int* iptr)
{
    data *pData = (data *) *iptr;
    printf("C: Freeing data %d and %g at %p\n", pData->a, pData->b, pData);
    free(pData);
    return;
}

Compile

The program can be compiled with the GNU compilers with -m32 for 32-bit (no issue here), and -m64 for 64-bit. Compiling the C code raises some warnings:

$ gcc -m64 -c that.c
that.c: In function ‘that_allocate_’:
that.c:12: warning: cast from pointer to integer of different size
that.c: In function ‘that_assemble_’:
that.c:19: warning: cast to pointer from integer of different size
that.c: In function ‘that_free_’:
that.c:27: warning: cast to pointer from integer of different size

while the remaining compilation and linking is fine, and the program works:

$ gfortran -m64 -o prog this.f that.o
$ ./prog 
C: Allocated address 0x1130b40 (or 18025280)
Fortran: iptr address     1130B40
C: Freeing data 42 and 3.14159 at 0x1130b40

Questions

While I see calloc returned an address that can fit within the data limits of a 4-byte integer, is there a risk of calloc returning an address with a larger integer? Casting with (intptr_t) will silence the the compile warnings, but I suspect it will truncate any higher bits and "Segmentation fault" if a pointer is attempted to be cast to a truncated address. Is this correct?

What should I do? Does the fix need to go on the Fortran code?

هل كانت مفيدة؟

المحلول

Yes, there is a potential bitness problem. If you want your code to be robust in the face of compiler and platform changes, then there are a number of things that you should do, most of which rely on the C interoperability features of Fortran 2003. These language features are supported by recent gfortran and most actively maintained Fortran compilers.

It is not clear from your example whether the Fortran code really needs to know the value of the pointer to the data struct as an integer (in your example you print this value, but I suspect that is just for debugging). If the Fortran code just needs to regard the pointer as an opaque handle, then the C_PTR type in the ISO_C_BINDING intrinsic module is the appropriate equivalent to a C pointer. If for some reason the Fortran code does need to know the value of the pointer as an integer, then an integer of kind C_INTPTR_T (again from the ISO_C_BINDING intrinsic module) is appropriate. Going further still, if you want the fortran code to be able to play with the actual structure itself, then you can define a BIND(C) derived type and use that in various ways.

Further, your C code assumes that the Fortran compiler uses a certain calling convention, including the way that procedure names are mangled to form a linker name. You can use the BIND(C,NAME='xxx') attribute in an interface block on the Fortran side to specify that the Fortran compiler use a calling convention that is compatible with its companion C compiler and to specify the C name of a procedure.

Note that the POINTER declaration on the integer and its subsequent allocation is not relevant, given the rest of your example.

All up (in free form, it has been a while since fixed form was appropriate):

PROGRAM this
  USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR, C_INTPTR_T
  IMPLICIT NONE
  ! Interfaces required due to BIND(C).  Also allows the Fortran 
  ! compiler to do better error checking.  Note that the default 
  ! binding label is a lowercase variant of the Fortran name, but
  ! we specify it explicitly here anyway for clarity.
  INTERFACE
    SUBROUTINE that_allocate(the_c_ptr)  &
        BIND(C,NAME='that_allocate')
      USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
      IMPLICIT NONE
      ! Note passing a pointer by reference.
      TYPE(C_PTR), INTENT(OUT) :: the_c_ptr
    END SUBROUTINE that_allocate

    SUBROUTINE that_assemble(the_c_ptr)  &
        BIND(C,NAME='that_assemble')
      USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
      IMPLICIT NONE
      ! Note passing a pointer by value.
      TYPE(C_PTR), INTENT(IN), VALUE :: the_c_ptr
    END SUBROUTINE that_assemble

    SUBROUTINE that_free(the_c_ptr)  &
        BIND(C,NAME='that_free')
      USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
      IMPLICIT NONE
      ! Note passing a pointer by value.
      TYPE(C_PTR), INTENT(IN), VALUE :: the_c_ptr
    END SUBROUTINE that_free
  END INTERFACE

  TYPE(C_PTR) :: the_c_ptr
  CALL that_allocate(the_c_ptr)
  ! Use transfer to convert the C address to an integer value.
  PRINT "('Fortran: ptr address',Z12)",  &
      TRANSFER(the_c_ptr, 0_C_INTPTR_T)
  CALL that_assemble(the_c_ptr)
  CALL that_free(the_c_ptr)
END PROGRAM this

and simplifying on the C side:

#include <stdlib.h>
#include <stdio.h>

typedef struct data {
  int a;
  float b;
} data;

void that_allocate(data** pData)
{
    *pData = (data*) calloc(1, sizeof(data));
    printf("C: Allocated address %p\n", *pData);
    return;
}

void that_assemble(data* pData)
{
    pData->a = 42;
    pData->b = 3.1415926;
    return;
}

void that_free(data* pData)
{
    printf("C: Freeing data %d and %g at %p\n", pData->a, pData->b, pData);
    free(pData);
    return;
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top