Мы пытаемся взять на себя выделение памяти унаследованного кода Фортрана (+100 000 строк кода) в C ++, потому что мы используем библиотеку C для разделения и распределения распределенной памяти в кластере. Распределяемые переменные определены в модулях. Когда мы вызываем подпрограммы, которые используют эти модули, индекс кажется неправильным (смещенным на единицу). Однако, если мы передадим тот же аргумент другой подпрограмме, мы получим то, что ожидаем. Следующий простой пример иллюстрирует проблему:
hello.f95:
MODULE MYMOD
IMPLICIT NONE
INTEGER, ALLOCATABLE, DIMENSION(:) :: A
SAVE
END MODULE
SUBROUTINE TEST(A)
IMPLICIT NONE
INTEGER A(*)
PRINT *,"A(1): ",A(1)
PRINT *,"A(2): ",A(2)
END
SUBROUTINE HELLO()
USE MYMOD
IMPLICIT NONE
PRINT *,"A(1): ",A(1)
PRINT *,"A(2): ",A(2)
CALL TEST(A)
end SUBROUTINE HELLO
main.cpp
extern "C" int* __mymod_MOD_a; // Name depends on compiler
extern "C" void hello_(); // Name depends on compiler
int main(int args, char** argv)
{
__mymod_MOD_a = new int[10];
for(int i=0; i<10; ++i) __mymod_MOD_a[i] = i;
hello_();
return 0;
}
Мы компилируем с:
gfortran -c hello.f95; c++ -c main.cpp; c++ main.o hello.o -o main -lgfortran;
Выход из работы ./main is
A(1): 1
A(2): 2
A(1): 0
A(2): 1
Как видите, вывод A отличается, хотя в обеих подпрограммах напечатаны A (1) и A (2). Таким образом, кажется, что HELLO начинается с A (0), а не с A (1). Вероятно, это связано с тем, что ALLOCATE никогда не вызывался непосредственно в Фортране, чтобы он не знал о границах А. Есть ли обходные пути?
Пустые аргументы массива Fortran всегда начинаются с нижней границы, определенной в подпрограмме. Их нижняя граница не сохраняется во время разговора. Поэтому аргумент A
в TEST()
всегда будет начинаться с одного. Если вы хотите, чтобы это началось с 42
, ты должен сделать:
INTEGER A(42:*)
Что касается распределения, вы играете с огнем. Для этого гораздо лучше использовать указатели Фортрана.
integer, pointer :: A(:)
Затем вы можете установить массив так, чтобы он указывал на буфер C:
use iso_c_binding
call c_f_pointer(c_ptr, a, [the dimensions of the array])
где c_ptr
имеет type(c_ptr)
совместим с void *
который также исходит от iso_c_binding
,
—Редактировать—
Как только я вижу, что @Max la Cour Christensen реализовал то, что я набросал выше, я вижу, что неправильно понял вывод вашего кода. Дескриптор был действительно неправильным, хотя я не написал ничего плохого. Решение выше все еще применяется.
«Эквивалентный» код ISO_C_BINDING:
код C ++:
extern "C" int size;
extern "C" int* c_a;
extern "C" void hello();
int main(int args, char** argv)
{
size = 10;
c_a = new int[size];
for(int i=0; i<size; ++i) c_a[i] = i;
hello();
return 0;
}
фортран код:
MODULE MYMOD
USE, INTRINSIC :: ISO_C_BINDING
IMPLICIT NONE
INTEGER, BIND(C) :: SIZE
TYPE (C_PTR), BIND(C) :: C_A
INTEGER(C_INT), POINTER :: A(:)
SAVE
END MODULE
SUBROUTINE TEST(A)
IMPLICIT NONE
INTEGER A(*)
PRINT *,"A(1): ",A(1)
PRINT *,"A(2): ",A(2)
END
SUBROUTINE HELLO() BIND(C)
USE, INTRINSIC :: ISO_C_BINDING
USE MYMOD
IMPLICIT NONE
CALL C_F_POINTER(C_A,A,(/SIZE/))
PRINT *,"A(1): ",A(1)
PRINT *,"A(2): ",A(2)
CALL TEST(A)
END SUBROUTINE
Выход:
A(1): 0
A(2): 1
A(1): 0
A(2): 1
Внутреннее представление массивов fortran сильно отличается от того, которое используется в C / C ++.
Fortran использует дескрипторы, которые начинаются с указателя на данные массива, за которым следуют размер типа элемента, число измерений, некоторые байты заполнения, внутренняя 32 -64-битная последовательность байтов, указывающая различные флаги, такие как указатель, цель, выделяемые, могут быть освобождены и т. д. Большинство этих флагов не задокументированы (по крайней мере, в ifort, с которым я работал), и в конце — последовательность записей, каждая из которых описывает количество элементов в соответствующем измерении, расстояние между элементами и т. д.
Чтобы «увидеть» внешне созданный массив из Fortran, вам нужно создать такие дескрипторы в C / C ++, но это не заканчивается, потому что Fortran также делает их копии в коде запуска каждой подпрограммы, прежде чем он доберется до первое из ваших утверждений, в зависимости от таких индикаторов, как «in», «out», «inout» и других индикаторов, используемых в объявлении массива fortran.
Массивы в типе, объявленном с определенными размерами, хорошо отображаются (опять же в ifort) на соответствующие элементы структуры C того же типа и числа элементов, но указатель и размещаемые члены типа действительно являются дескрипторами в типе, которые необходимо инициализировать для правильных значений. во всех их полях, чтобы Фортран мог «видеть» выделяемое значение. В лучшем случае это сложно и опасно, поскольку компилятор фортрана может генерировать код для копирования массивов недокументированными способами в целях оптимизации, но для этого ему необходимо «увидеть» весь задействованный код фортрана. Что-либо, выходящее за пределы области fortran, неизвестно и может привести к неожиданному поведению.
Лучше всего посмотреть, поддерживает ли gfortran что-то вроде iso_c_binding, и определить такие интерфейсы для вашего кода на языке fortran, а затем использовать встроенные функции iso_c_binding для сопоставления указателей C_PTR с указателями fortran на типы, массивы и т. Д.
Вы также можете передать указатель на одномерный массив char и его размер, и это работает для строк в основном до тех пор, пока размер передается по значению как последний аргумент (опять же, зависит от флага компилятора и флага компилятора).
Надеюсь это поможет.
РЕДАКТИРОВАТЬ: после комментария Владимира изменилось ‘iso_c_binding’ ifort на ‘iso_c_binding’ — спасибо!