Fortran освобождение выделенной памяти C++

Чтобы добавить некоторый фоновый контекст, мне нужен потокобезопасный генератор случайных чисел для использования в многопоточном коде Fortran, который должен быть кросс-компилятором и совместим с кросс-платформой. Лучший способ добиться этого — придерживаться языковых стандартов. Поэтому я хотел обернуть генераторы случайных чисел C++11 в функцию, которую я могу вызывать из фортрана, поэтому у меня есть один генератор случайных чисел на поток, каждый со своим собственным объектом состояния.

Поэтому я создал небольшой класс C++ с привязками к 3 функциям C.

#include <random>
#include <iostream>

class random {
    std::mt19937_64 engine;
    std::uniform_real_distribution<double> dist;

    public:
    random(uint64_t seed) : engine(seed), dist(0.0, 1.0) {};
    double get_number() {
        return dist(engine);
    }
};

extern "C" {
    void *random_construct(int seed) {
        return new class random(static_cast<uint64_t> (seed));
    }

    double random_get_number(void *r) {
        return static_cast<class random *> (r)->get_number();
    }

    void random_destroy(void *r) {
        delete static_cast<class random *> (r);
    }
}

Интерфейс Фортран

  MODULE random
  USE, INTRINSIC :: iso_c_binding

  IMPLICIT NONE

  INTERFACE
     TYPE (C_PTR) FUNCTION random_construct(seed)                          &
 &   BIND(C, NAME='random_construct')
     USE, INTRINSIC :: iso_c_binding

     IMPLICIT NONE

     INTEGER (C_INT), VALUE :: seed

     END FUNCTION
  END INTERFACE

  INTERFACE
     REAL (C_DOUBLE) FUNCTION random_get_number(r)                         &
 &   BIND(C, NAME='random_get_number')
     USE, INTRINSIC :: iso_c_binding

     IMPLICIT NONE

     TYPE (C_PTR), VALUE :: r

     END FUNCTION
  END INTERFACE

  INTERFACE
     SUBROUTINE random_destroy(r)                                          &
 &   BIND(C, NAME='random_destroy')
     USE, INTRINSIC :: iso_c_binding

     IMPLICIT NONE

     TYPE (C_PTR), VALUE :: r

     END SUBROUTINE
  END INTERFACE

  END MODULE

И небольшая программа для проверки этого.

   PROGRAM random_test
   USE random

   IMPLICIT NONE

   TYPE (C_PTR) :: rand_object
   INTEGER      :: count

   CALL SYSTEM_CLOCK(count)
   rand_object = random_construct(count)

   WRITE(*,*) random_get_number(rand_object)

   CALL random_destroy(rand_object)

   WRITE(*,*) random_get_number(rand_object)  ! Expected to segfault.

   END PROGRAM

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

Если я изменю свою тестовую программу на

   PROGRAM random_test
   USE random

   IMPLICIT NONE

   TYPE (C_PTR), ALLOCATABLE :: rand_object
   INTEGER                   :: count

   CALL SYSTEM_CLOCK(count)
   rand_object = random_construct(count)

   WRITE(*,*) random_get_number(rand_object)

   DEALLOCATE (rand_object)

   WRITE(*,*) random_get_number(rand_object)

   END PROGRAM

Теперь он производит поведение, которого я ожидал, и segfaults после DEALLOCATE. Теперь моя интуиция говорит, что это не должно работать, поскольку я никогда не буду пытаться выделить память на одном языке и освободить ее на другом. Но есть ли причина, по которой этого не должно быть? Объект C++ относится к типу POD, поэтому его память должна быть непрерывной. Затем, пока у Фортрана есть правильный адрес памяти, он должен иметь возможность тривиально освободить его. Есть ли что-то, что мне здесь не хватает?


person user1139069    schedule 18.09.2018    source источник
comment
Доступ к удаленному объекту является поведением undefined. Не обязательно сбой.   -  person François Andrieux    schedule 18.09.2018
comment
За исключением случаев, когда я явно обнуляю объект после вызова удаления, 'void random_destroy(void *r) {delete static_cast‹class random *› (r); r = nullptr;}' Он по-прежнему не генерирует случайные числа.   -  person user1139069    schedule 18.09.2018
comment
Это ничего не изменит. r является копией указателя, и присвоение ему другого адреса никак не повлияет на исходный указатель.   -  person François Andrieux    schedule 18.09.2018
comment
Это не имеет никакого смысла. Вы не можете вызвать метод по адресу памяти 0.   -  person user1139069    schedule 18.09.2018
comment
Добавление r = nullptr; к random_destroy не присвоит null rand_object. r является копией rand_object, поэтому присвоение r не меняет rand_object.   -  person François Andrieux    schedule 18.09.2018
comment
Fortran передается по ссылке. Почему бы и нет?   -  person user1139069    schedule 18.09.2018
comment
r является ссылкой на объект, на который rand_object ссылается yes, в том смысле, что они оба относятся к одному и тому же объекту. Но r не является ссылкой на саму ссылку rand_object. Редактировать: я не эксперт в Фортране, но это мое понимание, и оно согласуется с тем, что вы описываете. Если rand_object было обнулено, то random_get_number, по всей вероятности, должен дать сбой.   -  person François Andrieux    schedule 18.09.2018
comment
TYPE (C_PTR), ALLOCATABLE :: rand_object объявляет размещаемый скалярный указатель C. Это не имеет ничего общего с объектом/контейнером массива.   -  person francescalus    schedule 18.09.2018
comment
@user1139069 user1139069 Я думаю, это потому, что вы прикрепляете атрибут VALUE в своем интерфейсе для внешних подпрограмм. Если вы удалите ЗНАЧЕНИЕ и измените определение подпрограмм C++, чтобы они получали void **r (и установка *r = nullptr в подпрограмме уничтожения), это может сработать... (Я пробовал это на своем Mac, и это вроде работает)   -  person roygvib    schedule 18.09.2018
comment
@user1139069 user1139069 [Но я помню, что где-то в сети есть хороший потокобезопасный MT PRNG на Фортране, поэтому его может быть проще использовать (если он доступен...)]   -  person roygvib    schedule 18.09.2018
comment
@roygvib Это то, чего мне не хватало. Я забыл, что аргументы на стороне C будут отображаться как указатель на аргумент. TYPE(C_PTR) является эквивалентом struct {void *}, объясняющим, почему мои адреса fortran и C постоянно появлялись разными. К сожалению, кто-то больше не может добавить правильный ответ, так как он был ошибочно помечен как дубликат.   -  person user1139069    schedule 18.09.2018
comment
Я еще не буду голосовать, чтобы снова открыть этот вопрос. Несмотря на заголовок, в вопросе нет ничего, что указывало бы на то, что вы освобождаете память в Фортране, которая была выделена в С++. Что касается атрибута value: процедура C++, которая выполняет уничтожение, явно не может обновить аргумент Fortran, который был передан по значению. Этот аспект рассматривается в новом вопросе, который я добавил в список дубликатов.   -  person francescalus    schedule 19.09.2018
comment
В этом-то и дело. Вы пропустили Есть ли что-то, что мне здесь не хватает?   -  person user1139069    schedule 19.09.2018
comment
Предполагая, что последний комментарий был адресован мне: вы что-то пропустили, но это что-то описано в другом вопросе. Уверяем вас, что пометка вопроса как повторяющегося не означает, что вопрос в каком-то смысле бесполезен. Это все еще актуальный вопрос у вас есть. Но если вы считаете, что другой вопрос не подходит, вы можете так и сказать.   -  person francescalus    schedule 19.09.2018