c++ c memory-management fortran fortran-iso-c-binding

c++ - Llamando al código C con datos en memoria de Fortran



memory-management fortran-iso-c-binding (1)

Fortran 2003 introdujo la interoperabilidad C en el idioma Fortran. Esta función de lenguaje hace que sea mucho más fácil escribir fuentes Fortran y C (y por lo tanto C ++) que pueden trabajar juntas de una manera portátil y robusta. A menos que se le impida usar este nivel del idioma por otros motivos, debería utilizar esta característica.

Tiene un problema con la indirección del puntero, ya sea que el puntero al objeto C ++ se esté almacenando en un punto largo o un puntero en largo (el operando de los moldes en doSth_ y teardown_A_ debe tener un * delante de ellos). Depende de los compiladores C ++ y Fortran que esté utilizando, pero es posible que tenga una discrepancia de tamaño entre un C largo, un puntero C y un entero tipo Fortran predeterminado.

Un ejemplo modificado que muestra el enfoque utilizando la función de interoperabilidad C de Fortran 2003 a continuación.

// C++ struct A { public: void do_something() { // ... } private: // ... }; // Note no need for trailing underscore. extern "C" { // Note pointer to pointer to void. void init_A(void** ptr_ptr_to_A) { A* a = new A; *ptr_ptr_to_A = reinterpret_cast<void*>(a); } void doSth(void* ptr_to_A) { A* a = reinterpret_cast<A*>(ptr_to_A); a->do_something(); } void teardown_A(void* ptr_to_A) { A* a = reinterpret_cast<A*>(ptr_to_A); delete a; } } ! Fortran 2003 USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR IMPLICIT NONE INTERFACE SUBROUTINE init_A(ptr_to_A) BIND(C, NAME=''init_A'') USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR IMPLICIT NONE ! This argument is a pointer passed by reference. TYPE(C_PTR), INTENT(OUT) :: ptr_to_A END SUBROUTINE init_A SUBROUTINE doSth(ptr_to_A) BIND(C, NAME=''doSth'') USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR IMPLICIT NONE ! This argument is a pointer passed by value. TYPE(C_PTR), INTENT(IN), VALUE :: ptr_to_A END SUBROUTINE doSth SUBROUTINE teardown_A(ptr_to_A) BIND(C, NAME=''teardown_A'') USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR IMPLICIT NONE ! This argument is a pointer passed by value. TYPE(C_PTR), INTENT(IN), VALUE :: ptr_to_A END SUBROUTINE teardown_A END INTERFACE TYPE(C_PTR) :: ptr_to_A INTEGER :: i !**** CALL init_A(ptr_to_A) DO i = 1, 100 CALL doSth(ptr_to_A) END DO CALL teardown_A(ptr_to_A) END

Tengo un objeto complicado de C ++ que me gustaría usar en mi código Fortran. En general, no hay problema para llamar al código C ++ de Fortran (solo es necesario proporcionar una interfaz adecuada con enlace C, por ejemplo).

Sin embargo, mi problema aquí es que quiero que mis llamadas de Fortran a C ++ operen en lo que yo llamaría un objeto persistente: un objeto de C ++ creado por la primera función de inicio, y operado por otras funciones de C ++.

Para ser más específico, supongamos que tengo el siguiente código C ++

struct A { public: void do() { // do something on complicated stuff private: ... // complicated stuff }; extern "C" { void* init_A() { A* a = new A(); return reinterpret_cast<void*>(a); } void doSth(void* ptr_to_A) { A* a = reinterpret_cast<A*>(ptr_to_A); a.do(); } void teardown_A(void* ptr_to_A) { A* a = reinterpret_cast<A*>(ptr_to_A); delete a; } }

Y el siguiente código fortran (supongamos que es main ()):

USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR INTERFACE TYPE(C_PTR) FUNCTION init_A() BIND(C, NAME=''init_A'') USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR IMPLICIT NONE END FUNCTION init_A SUBROUTINE doSth(ptr_to_A) BIND(C, NAME=''doSth'') USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR IMPLICIT NONE TYPE(C_PTR), INTENT(IN), VALUE :: ptr_to_A END SUBROUTINE doSth SUBROUTINE teardown_A(ptr_to_A) BIND(C, NAME=''teardown_A'') USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR IMPLICIT NONE TYPE(C_PTR), INTENT(IN), VALUE :: ptr_to_A END SUBROUTINE teardown_A END INTERFACE

Ahora en mi código real, esto compila, enlaza, y algunas veces funciona, pero a veces no: parece que la memoria asignada en init_A () no está garantizada para ser modificada por el código Fortran.

No pude encontrar nada al respecto en Internet:

  • ¿Sabes si hay algún mecanismo estándar para garantizar que la memoria asignada por init_A_ () no se modifique y siga asignándose por el código Fortran?
  • ¿Conoces algún otro mecanismo que se adapte a mi problema?

Además, ¿alguien puede explicarme por qué la memoria no se gestiona correctamente? Hasta ahora, pensé que

  • Fortran le pediría memoria al sistema operativo, C ++ también,

  • Los segmentos de memoria dados por el SO a Fortan y C ++ no estaban relacionados y se garantiza que no se superponen,

  • Si se solicitara una nueva memoria, el sistema operativo no permitiría que Fortran usara la memoria C ++ hasta que C ++ la liberara

  • La memoria C ++ se libera mediante una llamada a teardown_A () o cuando finaliza el programa (es decir, Fortran main)

Editar: actualicé mi código con la respuesta de IanH, pero esto todavía no funciona (segfaults, porciones de memoria son desasignadas al llamar a doSth () desde Fortran

El código original que publiqué es el siguiente (para comentarios referidos a él)

struct A { public: void do() { // do something on complicated stuff private: ... // complicated stuff }; extern "C" { void init_A_(long* ptr_to_A) { // ptr_to_A is an output parameter A* a = new A(); *ptr_to_A = reinterpret_cast<long>(a); } void doSth_(long* ptr_to_A) { A* a = reinterpret_cast<A*>(*ptr_to_A); a.do(); } void teardown_A_(long* ptr_to_A) { A* a = reinterpret_cast<A*>(*ptr_to_A); delete a; } }

Y el código Fortran:

integer :: ptr_to_A call init_A(ptr_to_A) do i=1,10000 call doSth(ptr_to_A) enddo call teardown_A(ptr_to_A)