Написание замены для API Matlab C для записи .matfiles

#c #matlab #c99

#c #matlab #c99

Вопрос:

Я работаю над исследовательской моделью, которая выводит результаты в формат файла matlab .mat и изначально связана с библиотекой matlab для использования ее функций вывода файлов. Недавно требования изменились (кто бы мог подумать), и ранее работавший только для Linux код теперь должен компилироваться в Windows, и предпочтительно не требовать matlab для сборки, но все равно выводить файлы .mat.
Итак, я поискал и нашел libmatio (http://sourceforge.net/projects/matio /). Хотя с этим легко связать в Linux (вы просто устанавливаете его из репозиториев), это ужасно в Windows (в принципе, нет информации о его сборке в Windows). На самом деле, похоже, поддержка Windows была фактически отключена в версии 1.3.3 (еще в 2008 году).
Кроме того, API полностью отличается от того, который предоставляется matlab, что потребовало бы от меня переписать / реструктурировать большую часть кода.

Итак, мне пришла в голову эта безумная идея… Мне нужна была встроенная замена для Matlab API, предпочтительно без использования библиотеки (чтобы упростить компиляцию для непрограммистов), поэтому я начал писать одну.
Я всего лишь реализую необходимую мне функциональность (написание массивов двойников, строк и сложных двойников, а также структур и вложенности структур). Все это уже работает нормально, за исключением одного: массивов структур.

Все данные matlab содержатся в одной структуре под названием ‘mxArray’, и в зависимости от ее типа, она содержит указатели на double, complex double или один или несколько других mxArray.
Последним шагом непосредственно перед записью mxArray в файл является вычисление его размера (и размера его дочерних элементов) в байтах с помощью caling calcArraySize() .
В какой-то момент это приводит к segfault, потому что я пытаюсь получить доступ к нулевому указателю. Чтобы отследить причину, я запустил код через valgrind. Как всегда, я стараюсь решать любые проблемы в порядке их возникновения, поскольку они могут быть причиной того, что произойдет позже.
Итак, первое, о чем сообщает мне valgrind, это:

 ==8405== Invalid write of size 8
==8405==    at 0x00404541: mxSetFieldByNumber (mxSetFieldByNumber.c:18) [A]
==8405==    by 0x00411679: calcAllRayInfo (calcAllRayInfo.c:156)
==8405==    by 0x0041dd42: main (cTraceo.c:111)
==8405==    Address 0x5500250 is 0 bytes inside a block of size 4 alloc'd
==8405==    at 0x04c28f9f: malloc (vg_replace_malloc.c:236)
==8405==    by 0x00401066: mallocChar (toolsMemory.c:69)
==8405==    by 0x00404314: mxCreateStructMatrix (mxCreateStructMatrix.c:43) [B]
==8405==    by 0x00411235: calcAllRayInfo (calcAllRayInfo.c:105)
==8405==    by 0x0041dd42: main (cTraceo.c:111)
  

ПРИМЕЧАНИЕ: Я пометил [A] и [B] в приведенном ниже коде.
Определение структуры (показаны только соответствующие элементы):

 struct mxArray{
  bool           isStruct;  //determines if this mxArray is a structure (which contains other mxArrays)
  bool           isChild;   //determines wheter this mxArray is a Child of another (when set, its name will not be written to the matfile, as it is already defined in the parent's fieldnames
  uintptr_t      nFields;
  char           **fieldNames;  //something like: {"theta","r","z"};
  struct mxArray **field; //pointer to member mxArrays. only used when isStruct is set.
};
typedef struct mxArray mxArray;
  

Функция, которую я использую для выделения памяти для structMatrix вместе с ее содержимым:

 mxArray* mxCreateStructMatrix(uintptr_t nRows, uintptr_t nCols, uintptr_t nFields, const char **fieldNames){
  /*
   * creates a 2D array of structures
   */
  mxArray*  outArray = NULL;

  /* do some input value validation */

  // allocate memory
  outArray  = malloc(nRows*nCols*sizeof(mxArray));
  if (outArray == NULL){
    fatal("mxCreateStructMatrix(): memory allocation error.");
  }

  // allocate memory for structure members (fields)
  for (uintptr_t iStruct=0; iStruct<nCols*nRows; iStruct  ){
    outArray[iStruct].nFields       = nFields;
    outArray[iStruct].fieldNames        = malloc(nFields*sizeof(char*));

    //copy fieldnames into struct info
    for (uintptr_t iField=0; iField<nFields; iField  ){
      //NOTE: strlen returns length of string not including the terminating NULL character
      outArray[iStruct].fieldNames[iField] = mallocChar(strlen(fieldNames[iField]) 1);  // [B] <=======
      strncpy(outArray[iStruct].fieldNames[iField], fieldNames[iField], strlen(fieldNames[iField]));
    }

    outArray[iStruct].field     = NULL;
    outArray[iStruct].field     = malloc(nFields*sizeof(mxArray*));
    if (outArray[iStruct].field == NULL){
      fatal("mxCreateStructMatrix(): memory allocation error.n");
    }
  }
return outArray;
}
  

Two other allocation functions exist for mxArrays:

 mxArray* mxCreateDoubleMatrix(uintptr_t nRows, uintptr_t nCols, uintptr_t numericType){
  /*
   * creates a 2D array of double precision floating point values.
   * can be real or complex.
   */
  [snip]
}
mxArray* mxCreateString(const char *inString)
  /*
   * creates an mxArray containing a string.
   */
  [snip]
}
  

Эта функция назначает один mxArray дочерним для другого:

 void    mxSetFieldByNumber(mxArray* mxStruct,       //pointer to the mxStruct
                           uint32_t index,      //linear index of the element 
                           uint32_t iField,     //index of the structure's field which we want to set.
                           mxArray* inArray){       //the mxArray we want to assign to the mxStruct
  /* 
   * Assigns an mxArray to one of the fields of a structArray
   */
  inArray->isChild = true;  //determines that this mxArray is a child of another one
  mxStruct[index].field[iField] = inArray;  // [A] <===============
}
  

Использование является:

 //create parent mxArray:
mxStruct = mxCreateStructMatrix(1, //number of rows
                                1, //number of columns
                                2, //number of fields in each element
                                fieldNames1);   //list of field names

//create children:
mxY = mxCreateDoubleMatrix(1 ,1, mxREAL);
mxZ = mxCreateDoubleMatrix(1 ,1, mxREAL);
mxSubStruct = mxCreateStructMatrix(1, //number of rows
                                   1, //number of columns
                                   3, //number of fields in each element
                                   fieldNames2); //list of field names

/* copy some values into the mxArrays */
[snip]

//link children to parents
mxSetFieldByNumber( mxStruct, //pointer to the parent mxArray
                    0,        //index of the element (linear)
                    0,        //position of the field (in this case, field 0 is "w"
                    mxY);     //the mxArray we want to add to the mxStruct

mxSetFieldByNumber( mxStruct,   0,  1,  mxZ);

mxSetFieldByNumber( mxSubStruct,    0,  0,  mxY);
mxSetFieldByNumber( mxSubStruct,    0,  1,  mxZ);

mxSetFieldByNumber( mxStruct,   0,  2,  mxSubStruct);
  

По всей видимости, mxStruct[index].field[iField] = inArray; выполняется запись в mxStruct[index].fieldNames , таким образом, завершается mxStruct[index].field[iField] == NULL , что затем вызывает ошибку segfault при попытке получить к нему доступ.
Как это может быть? оба выделяются правильно при вызове mxCreateStructMatrix , так как же эти указатели могут перекрываться? Что я упускаю из виду?

Комментарии:

1. Есть ли шанс, что вы могли бы использовать C с чистыми классами, std::vectors и тому подобным?

2. Какую версию файла .mat вам нужно записать? Более поздние версии Matlab используют формат hdf5, поэтому вы могли бы просто использовать библиотеки hdf5 и добавлять к ним информацию, специфичную для Matlab.

3. @Gabriel: Я никогда не работал с C , только C.

4. @Jonas: Я пишу файлы matfiles версии 6. Я также проверил hdf5, но весь смысл в том, чтобы не менять мою существующую кодовую базу, которая уже использует Matlab API для записи файлов. Вот почему я пишу функции с теми же именами, что и в API.

5. @EmanuelEy: Возможно, я неправильно понимаю вашу проблему, но разве тип файла matfile не зависит только от API для записи файлов? Другими словами, вместо использования или обратного проектирования Matlab API для записи mat-файлов версии 6, вы могли бы вместо этого создать гораздо более простой код версии 7? Опять же, возможно, я вас неправильно понимаю, и я не знаю всех ваших ограничений, но если вы в любом случае не сможете использовать Matlab API в будущем, возможно, стоит подумать о написании версии 7.

Ответ №1:

Я думаю, что проблема в вашем самом последнем утверждении:

 mxSetFieldByNumber( mxStruct,   0,  /* THIRD FIELD */ 3,  mxSubStruct);
  

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

 mxStruct = mxCreateStructMatrix(1, 1, /* TWO */ 2, fieldNames1);
  

В отличие от MATLAB, ваш код (насколько я могу судить) не поддерживает добавление полей структуры «на лету»:

 %# -- MATLAB code --
s = struct('f1',[], 'f2',[]);
s.f3 = 99;       %# add a new field
  

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

Комментарии:

1. на самом деле вы пытаетесь получить доступ к 4-му полю структуры (индексация на основе нуля в C)

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

3. что касается добавления полей структур «на лету»: это полезная функция, возможно, я реализую ее позже — спасибо!

4. итак, хотя ваш ответ не совсем правильный, он заставил меня еще раз проверить свой код, и вот оно: я записывал в позицию в массиве структур, которая не существовала — так просто… но, эй, это также означает, что мой код действительно работает 🙂 просто нужно добавить еще одно условие для проверки входных данных.

5. этот мой небольшой проект уже некоторое время используется ежедневно, поэтому я загрузил его на github