arreglos - Al pasar asignado C_PTR a Fortran matriz a C
arreglos en fortran (1)
Tengo problemas con segfaults para acceder a una matriz en C, que se asigna en el archivo Fortran a continuación. Hay algunos artefactos de depuración, como el hecho de que el archivo no escribe nada significativo e inicializo una variable que nunca uso.
Sin embargo, he encontrado lo siguiente:
- No inicializar
i
(pero todavía lo declaro): no segfault - No abrir el archivo en C: sin segfault
- No imprime
HESS
(noHESS_COPY
) en otro lugar del código: no segfault - Declarar e inicializar
i
con un nombre diferente: segfault
¿Alguien sabe lo que podría dar lugar a este comportamiento? La segfault se produce en la línea ARRAY_PTR = C_LOC(HESS_COPY(1, 1))
. Estoy compilando usando gfortran
y gcc
con indicadores de depuración (sin optimización).
valgrind
dice que hay una escritura inválida (los dos mejores son los que muestro a continuación):
Invalid write of size 8
at 0xBEEA3E: get_pointer (modsparsehess.f90:34)
by 0xA75D7A: print_hess (sparse_hessian_c.c:108)
by 0x866C95: quench_ (quench.f:316)
by 0x7F2DBE: mc_ (mc.F:368)
by 0x4B65E2: mcruns_ (mcruns.f:62)
by 0x459245: MAIN__ (main.F:430)
by 0x45A33F: main (main.F:21)
Address 0x87 is not stack''d, malloc''d or (recently) free''d
Archivo C
#include <stdio.h>
void get_pointer(double ** hessian);
void print_hess(int *arr_size) {
// Create a pointer to handle the hessian
double **hessian;
int i;
i = 0;
get_pointer(hessian);
printf("%8.3f", **hessian);
// Open a file for writing
FILE *fptr = fopen("hessian_out", "w");
// Print the hessian
fprintf(fptr, "/n");
fclose(fptr);
}
Archivo Fortran
MODULE MODSPARSEHESS
USE, INTRINSIC :: ISO_C_BINDING
USE MODHESS, ONLY: HESS
INTERFACE
SUBROUTINE PRINT_HESSIAN(DIMENSIONS) BIND(C, NAME=''print_hess'')
USE, INTRINSIC :: ISO_C_BINDING
INTEGER(C_INT) :: DIMENSIONS
END SUBROUTINE PRINT_HESSIAN
END INTERFACE
CONTAINS
SUBROUTINE GET_POINTER_IN_C(ARRAY_PTR) BIND(C, NAME=''get_pointer'')
! C signature: void get_pointer(double ** hessian);
USE, INTRINSIC :: ISO_C_BINDING
IMPLICIT NONE
! Arguments
TYPE(C_PTR), INTENT(OUT) :: ARRAY_PTR
! Local variables
REAL(C_DOUBLE), DIMENSION(:,:), &
ALLOCATABLE, TARGET :: HESS_COPY
! Copy the hessian into HESS_COPY
IF (.NOT. ALLOCATED(HESS_COPY)) THEN
ALLOCATE(HESS_COPY(SIZE(HESS, 1), SIZE(HESS, 2)))
END IF
HESS_COPY(:, :) = HESS(:, :)
! Now get the pointer
ARRAY_PTR = C_LOC(HESS_COPY(1, 1))
END SUBROUTINE GET_POINTER_IN_C
END MODULE MODSPARSEHESS
La variable HESS_COPY
es una variable local, no guardada y asignable del procedimiento de Fortran GET_POINTER_IN_C
.
En consecuencia, cada vez que el procedimiento comienza la ejecución, siempre está sin asignar. La prueba de su estado de asignación en la primera declaración ejecutable de ese procedimiento es, por lo tanto, superflua.
En consecuencia, también, al final de la ejecución del procedimiento, la variable local no guardada se desasigna automáticamente. La referencia C_LOC hacia el final del procedimiento, por lo tanto, obtiene la dirección de un objeto que está a punto de dejar de existir.
El código C funciona entonces con la dirección de un objeto que no existe y el programa falla.
Si se HESS_COPY
variable HESS_COPY
local o la variable de módulo guardada, continuaría existiendo entre las invocaciones de procedimiento.
(Todas las variables del módulo se guardan a partir de Fortran 2008, las revisiones previas del lenguaje formalmente requeridas explícitamente específicas de SAVE para las variables del módulo relevante si el módulo no se referenciaba continuamente en un ámbito activo).
(Como un aparte, las reglas del lenguaje, a partir de Fortran 2003, también significan que la prueba asignada, la instrucción de asignación y la asignación posterior pueden simplemente reemplazarse por la declaración única HESS_COPY = HESS
).
Además, en el código C, se está intentando devolver información en un puntero que no existe. En el código original, hessian se declara como un puntero a un puntero para hacer doble nota en los dos niveles de indirección. Sin algún tipo de inicialización, el primer nivel de indirección señalará "aleatoriamente" en la memoria, el código Fortran intentará almacenar su resultado en esa ubicación aleatoria.
Como alternativa, considere:
#include <stdio.h>
void get_pointer(double ** hessian);
void print_hess(int *arr_size) {
// A pointer to double (one level of indirection).
double *hessian;
// Pass the address of that pointer.
get_pointer(&hessian);
// print the value of the double being pointed at.
printf("%8.3f/n", *hessian);
// print the value of the next double in the array
// (assuming that there is more than one).
printf("%8.3f/n", *(hessian+1));
// (or equivalently, `hessian[1]`)
}
El método de puntero Fortran al que se refiere Vladimir F en los comentarios requiere dos procedimientos Fortran, uno similar al que tiene que asigna un puntero Fortran y copia los datos, el segundo que desasigna ese puntero. Cada llamada al procedimiento de asignación debe coincidir con una llamada correspondiente al procedimiento de desasignación, pasando el mismo puntero. Algo como:
SUBROUTINE GET_POINTER(ARRAY_PTR) BIND(C, NAME=''get_pointer'')
! C signature: void get_pointer(double **);
USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_LOC, C_PTR, C_DOUBLE
TYPE(C_PTR), INTENT(OUT) :: ARRAY_PTR
REAL(C_DOUBLE), POINTER :: HESS_COPY(:,:)
! See also the SOURCE= specifier.
ALLOCATE(HESS_COPY(SIZE(HESS,1), SIZE(HESS,2))
HESS_COPY = HESS
ARRAY_PTR = C_LOC(HESS_COPY)
END SUBROUTINE GET_POINTER
SUBROUTINE RELEASE_POINTER(ARRAY_PTR) BIND(C, NAME=''release_pointer'')
! C signature: void release_pointer(double*);
USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR, C_F_POINTER, C_DOUBLE
TYPE(C_PTR), INTENT(IN), VALUE :: ARRAY_PTR
REAL(C_DOUBLE), POINTER :: HESS_COPY(:,:)
CALL C_F_POINTER(ARRAY_PTR, HESS_COPY, SHAPE(HESS))
DEALLOCATE(HESS_COPY)
END SUBROUTINE RELEASE_POINTER