Передача выделенного C_PTR в массив Fortran на C

У меня проблемы с segfaults при доступе к массиву в C, который выделен в файле Fortran ниже. Есть несколько артефактов отладки, например, тот факт, что записи в файл не пишут ничего значимого, и я инициализирую переменную i, которую никогда не использую.

Однако я обнаружил следующее:

  • Не инициализируется i (но все еще объявляет): нет segfault
  • Не открывать файл в C: нет segfault
  • Отсутствие печати HESS (не HESS_COPY) в другом месте кода: нет segfault
  • Объявление и инициализация i с другим именем: segfault

Кто-нибудь знает, что может вызвать такое поведение? Сам segfault происходит в строке ARRAY_PTR = C_LOC(HESS_COPY(1, 1)). Я компилирую с использованием gfortran и gcc с флагами отладки (без оптимизации).

valgrind говорит о недопустимой записи (два верхних файла показаны ниже):

 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

Файл 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);
}

Файл 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

person Kyle_S-C    schedule 05.02.2015    source источник


Ответы (1)


Переменная HESS_COPY - это локальная несохраненная переменная, доступная для размещения в процедуре Fortran GET_POINTER_IN_C.

Следовательно, всякий раз, когда процедура начинает выполнение, она всегда не распределяется. Таким образом, проверка его статуса распределения в первом исполняемом операторе этой процедуры является излишней.

Следовательно, также в конце выполнения процедуры эта несохраненная локальная переменная автоматически освобождается. Таким образом, ссылка C_LOC ближе к концу процедуры получает адрес объекта, который вот-вот прекратит свое существование.

Затем код C работает с адресом несуществующего объекта, и программа терпит неудачу.

Если переменная HESS_COPY была сохранена как локальная или сохраненная переменная модуля, она продолжала бы существовать между вызовами процедур.

(Все переменные модуля сохраняются, начиная с Fortran 2008, предыдущие языковые версии формально требовали явной специфики SAVE для соответствующих переменных модуля, если на модуль не постоянно ссылались в активной области.)

(Кстати, правила языка, начиная с Fortran 2003, также означают, что выделенный тест, оператор выделения и последующее присваивание можно просто заменить одним оператором HESS_COPY = HESS.)


Кроме того, в коде C делается попытка вернуть информацию в указателе, который не существует. В исходном коде hessian объявлен как указатель на указатель на double - обратите внимание на два уровня косвенности. Без какой-либо инициализации первый уровень косвенного обращения будет указывать «случайным образом» в памяти, а затем код Fortran будет пытаться сохранить свой результат в этом случайном месте.

В качестве альтернативы рассмотрите:

#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]`)
}

Метод указателя Fortran, на который ссылается Владимир Ф в комментариях, требует двух процедур Fortran: одна аналогична той, что у вас есть, которая выделяет указатель Fortran и копирует данные, а вторая освобождает этот указатель. Каждый вызов процедуры выделения должен соответствовать соответствующему вызову процедуры освобождения, передавая тот же указатель. Что-то вроде:

   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
person IanH    schedule 05.02.2015
comment
Хорошо, я бы просто взял указатель на Фортран. Он может оставаться локальным, и впоследствии анонимная цель будет жить под наблюдением С. - person Vladimir F; 05.02.2015
comment
Абсолютно согласен. Мне не нравится скрытое состояние, связанное с использованием сохраненных размещаемых объектов (или действительно сохраненных чего-либо), но использование указателя приводит к необходимости в подпрограмме освобождения компаньона на стороне Fortran. Я подумал, что это может скрыть в ответе аспекты, связанные с основным недоразумением в вопросе о поведении несохраненных локальных выделяемых переменных. - person IanH; 06.02.2015
comment
Так работает ли он (неправильно), когда я не выполняю другие операции с памятью, потому что он просто оставил соответствующие значения в памяти? - person Kyle_S-C; 06.02.2015
comment
Кроме того, как мне использовать указатель Fortran? Могу ли я выделить для этого место, а затем выполнить освобождение памяти в процедурах C? Как вызов free узнает, сколько памяти нужно сделать доступной? - person Kyle_S-C; 06.02.2015
comment
На самом деле - у вас есть две проблемы - указатель вашего косвенного адреса на стороне C неверен. Следующие правки. - person IanH; 06.02.2015