Выделение памяти в C для выделяемого Fortran

Мы пытаемся взять на себя выделение памяти устаревшего кода Fortran (+100 000 строк кода) в C++, потому что мы используем библиотеку C для разделения и выделения распределенной памяти в кластере. Размещаемые переменные определяются в модулях. Когда мы вызываем подпрограммы, использующие эти модули, кажется, что индекс неправильный (смещен на единицу). Однако если мы передадим тот же аргумент другой подпрограмме, мы получим то, что ожидаем. Следующий простой пример иллюстрирует проблему:

привет.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:

 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. Любые обходные пути?


person user3680060    schedule 27.05.2014    source источник
comment
Предполагаемые массивы фигур, определенные как ваши INTEGER A(*), всегда начинаются с 1, это не имеет ничего общего с их размещением. Здесь применяются правила передачи аргументов.   -  person Vladimir F    schedule 27.05.2014
comment
Однако поймите, что вы играете с огнем здесь, я бы использовал указатели вместо выделяемых.   -  person Vladimir F    schedule 27.05.2014
comment
Играть с огнем - правильно :) Кроме того, член типа с обозначением указателя также является дескриптором фортрана, встроенным в тип, но с другими флагами и без записей измерений в конце (поскольку он будет указывать к чему-то, что полностью определит значение.   -  person DNT    schedule 27.05.2014
comment
Я так не думаю. Будет смешно, когда вы столкнетесь с компилятором с автоматическим перераспределением левой части в одной из ваших операций с целым массивом. Удачной отладки.   -  person Vladimir F    schedule 27.05.2014
comment
Это в любом случае происходит со строками, и это совсем не смешно :) Я просмотрел тонны строк ассемблера запуска подпрограммы на Фортране, чтобы понять, почему происходит много очень странных вещей.   -  person DNT    schedule 27.05.2014
comment
Используйте файл iso_c_binding.   -  person M. S. B.    schedule 27.05.2014


Ответы (3)


Фиктивные аргументы массива 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.

---Edit--- Когда я вижу, что @Max la Cour Christensen реализовал то, что я набросал выше, я понимаю, что неправильно понял вывод вашего кода. Дескриптор действительно был неправильным, хотя я не написал ничего явно неправильного. Приведенное выше решение по-прежнему применимо.

person Vladimir F    schedule 27.05.2014
comment
@Vladirmir F: просто академический вопрос - есть ли у вас надежный способ назначить внешне созданные дескрипторы параметрам функции / подпрограммы fortran, особенно для многомерных массивов? Это решило бы проблему знания размерности массива в коде Fortran. - person DNT; 27.05.2014
comment
Я не понимаю. Если вы приняли размер массива, как указано выше, дескриптор не используется, только указатель. Если вы приняли массив формы, вы передаете дескриптор. Если вы хотите переписать дескриптор указателя или выделяемого массива, я даже не знаю, можно ли стандартным способом получить адрес дескриптора, но как только он у вас есть, вы можете его отредактировать, но тогда все ставки сняты . - person Vladimir F; 27.05.2014
comment
Я имел в виду последний случай и без iso_c_binding, когда C-сторона создает дескриптор, инициализирует, распределяет память данных, тогда сторона фортрана «видит» это как обычный массив фортрана с несколькими измерениями без указания каких-либо размеров на фортране. объявление аргумента функции. Мне удалось сделать это с помощью ifort, но пришлось использовать хак, чтобы скопировать дескриптор в дескриптор fortran в стеке. Цель состояла в том, чтобы запустить код на Фортране через интерфейсы полиморфных функций, которые принимают разные формы и размеры массивов, но все с одним и тем же типом данных. Это официально не поддерживается. - person DNT; 28.05.2014
comment
Гораздо лучше использовать препроцессор и общие интерфейсы. Если вы не используете новый TS и официальный заголовок для массива предполагаемой формы, вы сами. Не вижу особого смысла в этих экспериментах, портативность, которая является сильной стороной Фортрана, пропала. - person Vladimir F; 28.05.2014

«Эквивалентный» код ISO_C_BINDING:

код С++:

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
person Max la Cour Christensen    schedule 27.05.2014

Внутреннее представление массивов фортрана сильно отличается от того, что используется в C/C++.

Fortran использует дескрипторы, которые начинаются с указателя на данные массива, за которыми следует размер типа элемента, количество измерений, некоторые байты заполнения, внутренняя последовательность байтов 32/64 бит, указывающая различные флаги, такие как указатель, цель, выделяемый, могут быть освобождены и т. д. Большинство этих флагов не документированы (по крайней мере, в ifort, с которым я работал), а в конце — последовательность записей, каждая из которых описывает количество элементов в соответствующем измерении, расстояние между элементами и т. д.

Чтобы «увидеть» внешне созданный массив из фортрана, вам нужно создать такие дескрипторы в C/C++, но на этом это не заканчивается, потому что фортран также делает их копии в коде запуска каждой подпрограммы, прежде чем она попадет в сначала одно из ваших утверждений, в зависимости от индикаторов, таких как «in», «out», «inout» и других индикаторов, используемых в объявлении массива fortran.

Массивы внутри типа, объявленного с определенными размерами, хорошо сопоставляются (опять же в ifort) с соответствующими членами структуры C того же типа и количества элементов, но указатели и члены выделяемого типа на самом деле являются дескрипторами в типе, которые необходимо инициализировать правильными значениями. во всех своих полях, чтобы fortran мог «видеть» выделяемое значение. Это в лучшем случае сложно и опасно, поскольку компилятор фортрана может генерировать код копирования для массивов недокументированными способами в целях оптимизации, но для этого ему необходимо «увидеть» весь задействованный код фортрана. Все, что выходит за пределы домена fortran, неизвестно и может привести к неожиданному поведению.

Лучше всего посмотреть, поддерживает ли gfortran что-то вроде iso_c_binding, и определить такие интерфейсы для вашего кода fortran, а затем использовать встроенные функции iso_c_binding для сопоставления указателей C_PTR с указателями fortran на типы, массивы и т. д.

Вы также можете передать указатель на одномерный массив символов и его размер, и это работает для строк в основном до тех пор, пока размер передается по значению в качестве последнего аргумента (опять же, зависит от компилятора и флага компилятора).

Надеюсь это поможет.

РЕДАКТИРОВАТЬ: изменено «iso_c_binding ifort» на «iso_c_binding» после комментария Владимира - спасибо!

person DNT    schedule 27.05.2014
comment
Да, но некоторые флаги помечены как «внутренние», а это не так. Кроме того, нет простого способа присвоить переменную фортрана дескриптору, переданному извне. Intel рекомендует вместо этого использовать iso_c_binding. - person DNT; 27.05.2014
comment
Это не iso_c_binding от ifort, это стандарт Fortran! Увы, здесь есть целый тег об этом stackoverflow.com/questions/tagged/fortran-iso-c- привязка - person Vladimir F; 27.05.2014
comment
Что касается первого комментария, вы правы, поэтому я удалил его через несколько секунд после публикации. - person Vladimir F; 27.05.2014
comment
Хорошо, согласен, я работал только над интерфейсом ifort, мне никогда не приходилось использовать fortran по какой-либо другой причине, так что, если это общий стандарт, тем лучше! - person DNT; 27.05.2014