Изменение переменных, которые не должны быть затронуты (переполнение памяти?)

Я боролся с проблемой в течение нескольких часов, и я в своем уме.

По сути, у меня есть функции, которые принимают несколько переменных и выводят другие. Вы знаете, довольно стандартные вещи.

Проблема в том, что одна из моих переменных не изменяется, но становится перепутанной. Я могу только предположить, что где-то происходит перелив памяти в ее пространство, но не могу понять, где, почему и как.

Ниже приведен очень упрощенный код (полная версия составляет около 300 строк и должна вычислять точки и производные сплайновой поверхности «NURBS»). Приносим извинения за количество неиспользуемых входных данных в функциях (я отключил и удалил большую часть кода, чтобы помочь вам, ребята, понять, что не так).

Когда он скомпилирован и запущен, вывод на экран:

Start Mex 40, 30
Outer Function Part 1 40, 30
Inner Function Part 1 40, 30
Inner Function Part 2 40, 30
Outer Function Part 2 817645044, 1069852220
Outer Function Part 3 817645044, 1069852220
End Mex 817645044, 1069852220

Однако вывод, очевидно, должен быть последовательным (40, 30).

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

void Simple_Separate_Parametrics(double* t_frac, double* t_crd_dbl, double t_full) {
    *t_crd_dbl = floor((double)t_full);
    *t_frac = t_full - *t_crd_dbl;
}



void Calc_Point_and_XJacobian_On_SplurfaceMap(double* P, double* J, bool* Valid, unsigned long* RelCrds, double* s, double* t, double* M, double* X, unsigned long* Crds, unsigned long* GridSize) {

    double *t_frac;
    double *t_crd;

    mexPrintf("Inner Function Part 1 %u, %u\n", *(GridSize), *(GridSize+1));

    Simple_Separate_Parametrics(t_frac, t_crd, *t);

    mexPrintf("Inner Function Part 2 %u, %u\n", *(GridSize), *(GridSize+1));
}



void Points_and_XJacobian_on_Splurface(double* Points, double* Jacobian, bool* Valid, double* s, double* t, double* M, double* X, unsigned long* GridSize, size_t NumPts) {

    double* J = (double*)malloc(16*sizeof(double));
    unsigned long n, Xlen, Vpts;//m, 
    Vpts = *(GridSize+1) + 3;
    Xlen = (*(GridSize) + 3)*Vpts;


    unsigned long* Crds    = (unsigned long*)malloc(Xlen*sizeof(unsigned long));
    unsigned long* RelCrds = (unsigned long*)malloc(16*sizeof(unsigned long));

    mexPrintf("Outer Function Part 1 %u, %u\n", *(GridSize), *(GridSize+1));

    for (n = 0; n < 1; ++n) {//NumPts
        Calc_Point_and_XJacobian_On_SplurfaceMap(Points + n, J, Valid + n, RelCrds, s + n, t + n, M, X, Crds, GridSize);
        mexPrintf("Outer Function Part 2 %u, %u\n", *(GridSize), *(GridSize+1));
    }

    mexPrintf("Outer Function Part 3 %u, %u\n", *(GridSize), *(GridSize+1));

    free(J); free(Crds); free(RelCrds);
}



void mexFunction( int nlhs, mxArray *plhs[],
    int nrhs, const mxArray *prhs[])
{

    // [Points, Jacobian, Valid] = Function( s, t, X, GridSize)

    double *Points, *Jacobian, *s, *t, *X, *M;
    size_t NumPts, Xlen;
    unsigned long* GridSize;
    bool* Valid;

    s   = mxGetPr(prhs[0]);
    t   = mxGetPr(prhs[1]);
    X   = mxGetPr(prhs[2]);

    GridSize = (unsigned long*)malloc(2*sizeof(unsigned long));
    *(GridSize  ) = 40;
    *(GridSize+1) = 30;

    mexPrintf("Start Mex %u, %u\n", *GridSize, *(GridSize+1));
    NumPts   = mxGetN(prhs[0]);

    Xlen = mxGetM(prhs[2])*mxGetN(prhs[2]);

    M = (double*)malloc(16*sizeof(double));


    if (nlhs == 3) {
        plhs[0] = mxCreateDoubleMatrix( 1, NumPts, mxREAL);
        Points = mxGetPr(plhs[0]);

        plhs[1] = mxCreateDoubleMatrix( NumPts, Xlen, mxREAL);
        Jacobian = mxGetPr(plhs[1]);

        plhs[2] = mxCreateLogicalMatrix( NumPts, 1);
        Valid = (bool*)mxGetData(plhs[2]);

        Points_and_XJacobian_on_Splurface(Points, Jacobian, Valid, s, t, M, X, GridSize, NumPts);

    }

    mexPrintf("End Mex %u, %u\n", *(GridSize), *(GridSize+1));

    free(M); free(GridSize);
}

person DavidGW    schedule 09.09.2015    source источник
comment
Если вы избавитесь от таких вещей, как Xlen = (*(GridSize) + 3)*Vpts;, станет легче. По-видимому, нынешняя указательность для вас слишком велика, и у вас нет причин избегать более C++-подобных вещей, так что... и попробуйте valgrind.   -  person deviantfan    schedule 10.09.2015
comment
И в вопросе отсутствует какой-то (намного?) соответствующий код. Начните с главного.   -  person deviantfan    schedule 10.09.2015
comment
@deviantfan В mex-функции нет main, mexFunction — это точка входа. По сути, это so/dll, который вызывает MATLAB. При этом я согласен с вашей оценкой, что ОП должен перестать быть таким счастливым указателем и попытаться упростить вещи. DavidGW, вы можете запускать mex-функции под отладчиком, вы можете попробовать.   -  person Praetorian    schedule 10.09.2015
comment
mexFunction является эквивалентом MATLAB для main()   -  person Mad Physicist    schedule 10.09.2015
comment
Я думаю, что это справедливый вопрос и не заслуживает закрытого голосования. Память кажется непреднамеренно перезаписанной. ОП: я запустил функцию с s=t=X=rand(40,30); и я получил вывод 40,30 до самого конца, поэтому вам нужно дать некоторое представление о входных данных. Нам не нужен огромный дамп, поэтому либо заставьте его сбой с чем-то вроде единиц или нулей, либо уменьшите размер матрицы до 3x3 или меньше.   -  person Ramashalanka    schedule 10.09.2015
comment
Спасибо за ответы. Что касается избыточного указателя, я удалил «Xlen = (*(GridSize) + 3)*Vpts;» строку, и я подаю значение Xlen из основного mexFunction. Я также разделил GridSize на два длинных целых числа без указателя. К сожалению, теперь код полностью убивает Matlab при запуске, поэтому мне нужно запустить его, прежде чем я смогу добавить гораздо больше, что поможет.   -  person DavidGW    schedule 10.09.2015
comment
Deviantfan: Это не первый мой mex-файл, я написал более дюжины mex-файлов с большим количеством указателей, поэтому, хотя я уверен, что это проблема указателя, я не новичок в указателях. Кроме того, большинство используемых указателей предназначены для больших векторов переменной длины, поэтому их нельзя удалить.   -  person DavidGW    schedule 10.09.2015
comment
Рамашаланка: Спасибо, что уделили время попытке запустить код. Как я уже упоминал, я сломал свою версию кода, пытаясь исправить ее, однако s и t будут находиться в диапазоне от нуля до GridSize, а не от нуля до единицы. Когда он запустится, я опубликую код, надеюсь, с входными данными, из-за которых он не будет работать.   -  person DavidGW    schedule 10.09.2015
comment
ВОЗМОЖНЫЙ ОТВЕТ: После того, как я полностью разобрал свой код, пытаясь снова запустить его, я думаю, что наконец понял, в чем проблема. И это потому, что я идиот (или потому, что я думал, что C++ немного умнее, чем он есть...). Если вы заметили, я пишу в 't_frac' и 't_crd_dbl', не выделяя им ни одной двойной памяти. Думаю, я попробую либо выделить им память, либо передать адреса не указателей в функции вместо использования указателей здесь. Предполагая, что я смогу собрать свой ядерный код... :(   -  person DavidGW    schedule 10.09.2015
comment
Почему бы вам не вернуть std::pair или boost::tuple или простую структуру из Simple_Separate_Parametrics вместо того, чтобы передавать выходные данные через указатели? Почему вы так много передаете через указатели, а не ссылки? А как насчет const-корректности? Почему у вас есть функции, которые принимают миллиарды параметров вместо структуры (или кортежа)? Почему нет RAII? Почему вы malloc представляете простой массив из 16 двойников (J)? Это читается как C, а не C++. Не пишите C, когда у вас есть компилятор C++. Не используйте голые указатели на собственные данные, используйте std::unique_ptr, но предпочитайте значения в стеке для небольших фиксированных массивов. Ургх!   -  person Kuba hasn't forgotten Monica    schedule 10.09.2015
comment
Also, most of the pointers that are used are for large vectors of variable length, so they can't really be removed. и т.д. и т.п.: Это неправильно. Как сказал КубаОбер, здесь вам не нужны необработанные указатели и malloc.   -  person deviantfan    schedule 10.09.2015


Ответы (2)


API-интерфейс mex, хотя и доступен на C++, на самом деле является API-интерфейсом C, и его прямое использование делает код похожим на код C. Это не очень хорошее место.

Итак, давайте сначала напишем код, предполагая, что существуют хорошие оболочки массива MEX C++11.

Начнем с параметров функции. Предположим, что DoubleArray и LogicalArray будут представлять массивы заданного типа и иметь конструкторы, которые инициализируют их из правых параметров или создают новые для левых параметров. Массивы являются просто дескрипторами базовых данных Matlab и представляют собой логический указатель. Они будут увеличиваться, чтобы компенсировать их положение по отношению к базовым данным.

Мы не должны использовать массивы фиксированного размера в стиле C, вместо этого следует использовать тип std::array.

struct Params {
   DoubleArray s, t, X, inGridSize;
   size_t NumPts;
   std::array<size_t, 2> GridSize;
   DoubleArray Points, Jacobian;
   LogicalArray Valid;
   std::array<double, 16> M;

   Params(mxArray * plhs[], const mxArray * prhs[]) :
      s(prhs, 0),
      t(prhs, 1),
      X(prhs, 2),
      inGridSize(prhs, 3),
      NumPts(s.size()),
      Points(1, NumPts, plhs, 0),
      Jacobian(NumPts, X.size(), plhs, 1),
      Valid(NumPts, 1, plhs, 2)
   {
      std::copy(inGridSize.begin(), inGridSize.end(), GridSize.begin());
   }
};

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

Это довольно непротиворечиво, хотя можно утверждать, что, возможно, inGridSize не нужен, и мы должны инициализировать GridSize из временного DoubleArray.

Дополнительные параметры для расчета отдельной точки можно аналогичным образом обернуть в структуру:

struct PointParams {
   unsigned int Vpts;
   unsigned int Xlen;
   std::array<double, 16> J;
   std::vector<unsigned int> Crds { Xlen };
   std::array<unsigned int, 16> RelCrds;

   PointParams(const Params & p) :
      Vpts(p.GridSize[1] + 3),
      Xlen((p.GridSize[0] + 3)*Vpts)
   {}
};

Наконец, функция Simple_Separate_Parametrics вычисляет результат типа Parametrics:

struct Parametrics {
   double t_crd_dbl, t_frac;
};

Parametrics Simple_Separate_Parametrics(double t_full) {
   Parametrics p;
   p.t_crd_dbl = floor(t_full);
   p.t_frac = t_full - p.t_crd_dbl;
   return p;
}

Calc_Point_... ссылается на Params и PointParams:

void Calc_Point_and_XJacobian_On_SplurfaceMap(Params & p, PointParams & pp) {
   mexPrintf("Begin Inner Function %u, %u\n", p.GridSize[0], p.GridSize[1]);

   auto pms = Simple_Separate_Parametrics(p.t[0]);

   mexPrintf("End Inner Function %u, %u\n", p.GridSize[0], p.GridSize[1]);
}

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

void Points_and_XJacobian_on_Splurface(Params p) {
   mexPrintf("Begin Outer Function %u, %u\n", p.GridSize[0], p.GridSize[1]);

   for (unsigned int n = 0; n < 1; ++n) {//NumPts
      PointParams pp(p);
      Calc_Point_and_XJacobian_On_SplurfaceMap(p, pp);
      p.Points += 1;
      p.Valid += 1;
      p.s += 1;
      p.t += 1;
   }

   mexPrintf("End Outer Function %u, %u\n", p.GridSize[0], p.GridSize[1]);
}

mexFunction проверяет правильность подсчета аргументов, инициализирует начальный Params и запускает рабочую функцию Points_and_....

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) {
   // [Points, Jacobian, Valid] = Function( s, t, X, GridSize)

   if (nrhs != 4)
      mexErrMsgTxt("Need four input parameters");
   if (nlhs != 3)
      mexErrMsgTxt("Need three output parameters");
   if (mxGetNumberOfElements(prhs[3]) != 2)
      mexErrMsgTxt("GridSize needs two elements");

   Params p { plhs, prhs };
   mexPrintf("Begin Mex %u, %u\n", p.GridSize[0], p.GridSize[1]);
   Points_and_XJacobian_on_Splurface(p);
   mexPrintf("End Mex %u, %u\n", p.GridSize[0], p.GridSize[1]);
}

Вы заметите, что ни в одном из этих кодов не происходит явного выделения памяти.

Итак, как нам реализовать волшебный соус? Через общий класс MxArray, параметризованный специализацией Traits, зависящей от типа значения. Конечно, его можно повторно использовать в любом файле MEX.

Тип массива действует как массив C, который знает свои границы. Вы можете легко добавить проверку границ в реализации operator[]. Вы также можете добавить смещение к массиву, сместив позицию элемента на 0 индекс. size() всегда возвращает количество доступных элементов, начиная с индекса 0 и далее. Когда вы добавляете положительное смещение, size() уменьшается. Добавление отрицательного смещения увеличит size(). Кумулятивное смещение не должно быть ниже нуля, иначе поведение будет неопределенным.

// https://github.com/KubaO/stackoverflown/tree/master/questions/mex-32490874

#include <cstddef>
#include <cstdint>
#include <cmath>
#include <vector>
#include <array>

template <typename T> struct Traits;

template <typename T, class Tr = Traits<T>> class MxArray {
   const mxArray * m_data;
   T * m_ptr;
public:
   typedef T * iterator;
   typedef const T * const_iterator;
   MxArray(const mxArray * data[], int index) :
      m_data(Tr::check(data, index)),
      m_ptr(Tr::ptr(m_data))
   {}
   MxArray(int n, int m, mxArray * out[], index) :
      m_data(out[index] = Tr::create(n, m)),
      m_ptr(Tr::ptr(out[index]))
   {}
   size_t size() const { return mxGetNumberOfElements(m_data) - offset(); }
   int n_dims() const { return mxGetNumberOfDimensions(m_data); }
   int dim(int i) const { return mxGetDimensions(m_data)[i]; }
   inline ptrdiff_t offset() const { return m_ptr - Tr::ptr(m_data); }
   T operator[](ptrdiff_t i) const { return m_ptr[i]; }
   T & operator[](ptrdiff_t i) { return m_ptr[i]; }
   MxArray & operator+=(ptrdiff_t offset) {
      m_ptr += offset;
      return *this;
   }
   friend MxArray operator+(MxArray lhs, ptrdiff_t offset) {
      return lhs += offset;
   }
   void resetOffset() { m_ptr = Tr::ptr(m_data); }
   iterator begin() { return m_ptr; }
   iterator end() { return m_ptr + size(); }
   const_iterator begin() const { return m_ptr; }
   const_iterator end() const { return m_ptr + size(); }
};

Трейты для типов double и mxLogical (не bool!) реализуют проверку типов и передают функциональность соответствующим API-интерфейсам mex.

const char kMsgId[] = "SO:MexExample";

template <> struct Traits<double> {
   static const mxArray * check(const mxArray * data[], int index) {
      if (! mxIsDouble(data[index]))
         mexErrMsgIdAndTxt(kMsgId, "Expected real type for input parameter #%d", index);
      return data[index];
   }
   static double * ptr(const mxArray * data) {
      return mxGetPr(data);
   }
   static mxArray * create(int n, int m) {
      return mxCreateDoubleMatrix(n, m, mxREAL);
   }
};

template <> struct Traits<mxLogical> {
   static const mxArray * check(const mxArray * data[], int index) {
      if (! mxIsLogical(data[index]))
         mexErrMsgIdAndTxt(kMsgId, "Expected logical type for input parameter #%d", index);
      return data[index];
   }
   static mxLogical * ptr(const mxArray * data) {
      return mxGetLogicals(data);
   }
   static mxArray * create(int n, int m) {
      return mxCreateLogicalMatrix(n, m);
   }
};

Наконец, DoubleArray и LogicalArray — это псевдонимы простых типов, которые параметризируют MxArray для нужного типа.

typedef MxArray<double> DoubleArray;
typedef MxArray<mxLogical> LogicalArray;

Неопределенное поведение — обращаться к данным mxLogical, как если бы это были данные bool, но допустимо преобразование между одним значением mxLogical и bool.

person Kuba hasn't forgotten Monica    schedule 10.09.2015

Причина возникновения проблем заключалась в том, что не использовался вызов malloc после объявления указателя:

double* Variable;
*Variable = 0.0;
Function_that_Calculates_Variable( Variable );

Это вызывало либо сбои, либо переполнение памяти. Я не понял/забыл, что вам нужно было выделить указатель на одно значение. Поэтому я заменил его на:

double Variable;
Variable = 0.0;
Function_that_Calculates_Variable( &Variable );

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

Задача решена! (После гораздо большего количества часов отладки, чем я готов признать: P)

person DavidGW    schedule 10.09.2015
comment
Проблема не решена в том, что код по-прежнему представляет собой ужасный беспорядок. Он должен быть короче, легче читать и намного сложнее, чтобы позволить вам совершать такие ошибки по замыслу. Это в лучшем случае плохой код C. Аргументы вывода в стиле C через указатели мучительны. Function_that_Calculates_Variable должен возвращать переменную (или их пару/кортеж/структуру) по значению. Только если вы хотите изменить массивы, переданные со стороны Matlab, вы вынуждены набрасывать данные, переданные указателями, и даже в этом случае вы должны обернуть их для безопасного использования из С++. - person Kuba hasn't forgotten Monica; 10.09.2015
comment
Пожалуйста, опишите математическую операцию, которую вы хотите выполнить, в самом вопросе (отредактируйте вопрос). Что такое splurface и карта splurface? Что бы вы хотели, чтобы код делал? Я хотел бы конкретизировать пример, чтобы действительно сделать что-то полезное. Не могли бы вы описать, что такое параметры s, t, X и т. д.? - person Kuba hasn't forgotten Monica; 10.09.2015