2

我正在查看一个稍旧的 Fortran 程序,它带有适用于 32 位系统的 C 调用,但会引发对 64 位编译器的警告和担忧。该程序将指向动态分配内存的 C 指针的地址存储为int,在 Fortran 端共享为INTEGER。我担心的是,在 64 位整数系统上,来自 C 指针的转换可能大于可以存储为int/的值INTEGER。我已经使用两个文件将现有程序简化为这个示例。

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;
}

编译

-m32该程序可以使用32 位(这里没有问题)和-m6464 位的 GNU 编译器进行编译。编译 C 代码会引发一些警告:

$ 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

而其余的编译和链接都很好,并且程序可以正常工作:

$ 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

问题

虽然我看到calloc返回的地址可以满足 4 字节整数的数据限制,但是否存在calloc返回更大整数地址的风险?强制转换(intptr_t)将使编译警告静音,但我怀疑如果尝试将指针强制转换为截断地址,它将截断任何更高位和“分段错误”。它是否正确?

我该怎么办?修复需要在 Fortran 代码上进行吗?

4

1 回答 1

6

是的,存在潜在的位数问题。如果您希望您的代码在面对编译器和平台更改时保持稳健,那么您应该做很多事情,其中​​大部分依赖于 Fortran 2003 的 C 互操作性特性。这些语言特性由最近的 gfortran 支持和最积极维护的 Fortran 编译器。

从您的示例中不清楚 Fortran 代码是否真的需要知道指向结构的指针的值data作为整数(在您的示例中您打印此值,但我怀疑这只是为了调试)。如果 Fortran 代码只需将指针视为不透明句柄,则 ISO_C_BINDING 内部模块中的 C_PTR 类型与 C 指针等效。如果由于某种原因 Fortran 代码确实需要将指针的值作为整数知道,那么 C_INTPTR_T 类型的整数(同样来自 ISO_C_BINDING 内部模块)是合适的。更进一步,如果您希望 fortran 代码能够使用实际结构本身,那么您可以定义一个 BIND(C) 派生类型并以各种方式使用它。

此外,您的 C 代码假定 Fortran 编译器使用特定的调用约定,包括过程名称被修改以形成链接器名称的方式。您可以在 Fortran 端的接口块中使用 BIND(C,NAME='xxx') 属性来指定 Fortran 编译器使用与其配套 C 编译器兼容的调用约定,并指定过程的 C 名称.

请注意,鉴于您的示例的其余部分,整数及其后续分配的 POINTER 声明不相关。

全部完成(在自由形式中,固定形式已经有一段时间了):

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

并在 C 端进行简化:

#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;
}
于 2013-03-18T10:11:03.997 回答