Search code examples
c++cmatlabstructuremex

Strange goings on when reading structure data in mex file


I have been confused by a very strange mex error just now . . .

Boiling my problem right down to its core, we end up with the following simple mex code. It just displays if given structure fields are empty or not ...

#include "mex.h"

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{    
    int numElements  = mxGetNumberOfElements(prhs[0]);
    int numFields = mxGetNumberOfFields(prhs[0]);

    mxArray* tmpData;
    const char* tmpName;

    for (int structIdx=0; structIdx<numElements; ++structIdx)
    {
        for (int fieldIdx=0; fieldIdx<numFields; ++fieldIdx)
        {
            tmpData = mxGetFieldByNumber(prhs[0], structIdx, fieldIdx);
            tmpName = mxGetFieldNameByNumber(prhs[0], fieldIdx);

            if (mxIsEmpty(tmpData))
                mexPrintf("struct(%i).%s is empty\n", structIdx+1, tmpName );
            else
                mexPrintf("struct(%i).%s contains data\n", structIdx+1, tmpName );
        }
    }    
}

If we compile this code and call it structcrash then the following matlab code . .

clc
x.a=1;
x.b=2;
x(2).a=3;
x(2).b=4;

structcrash(x);

...gives the output we might expect...

  • struct(1).a contains data
  • struct(1).b contains data
  • struct(2).a contains data
  • struct(2).b contains data

If we give the mex function a structure containing an empty field like so...

clc
y.a = [];
structcrash(y);

... then we also get the expected output ...

  • struct(1).a is empty

Now, things get very strange if you use code like this ...

clc
y(2).b = 4;
structcrash(y);

If we inspect the y structure, is is now a 2 element structure with 2 fields in each element. y(1).a is empty as we specified above, and y(1).b has been automatically created and given an empty value when we added the b field. Similarly, y(2).a was automatically created when we increased the structure size by adding y(2).b. The structure looks perfectly logical, however using as an input to the mex file results in a segfault.

By selectively commenting-out various lines of code, I can confirm that the command that causes the segfault is mxIsEmpty(tmpData).

Can anyone else replicate this error and am I doing something fundamentally wrong here? It looks like a bug in the mex API code to me, but I wanted to check here first. Thanks

EDIT: Based on @David Heffernan's advice I modified the code as follows

        if(tmpData!=NULL) {
            if (mxIsEmpty(tmpData))
                mexPrintf("struct(%i).%s is empty\n", structIdx+1, tmpName );
            else
                mexPrintf("struct(%i).%s contains data\n", structIdx+1, tmpName );
        }

...and the segfault no longer occurs. However, this is still very ominous. If you create two structures like in the following example and examine them using the workspace view, f and g look absolutely identical in every way. I cannot find any way in which they differ using standard matlab programming commands.

>> f(2).a=123;
>> g(1).a=[];
>> g(2).a=123

... but the whos command reveals a difference ...

  Name      Size            Bytes  Class     Attributes

  f         1x2               192  struct              
  g         1x2               296  struct 

... and my updated mex function obviously does too ...

>>structcrash(f)
struct(2).a contains data
>> structcrash(g)
struct(1).a is empty
struct(2).a contains data

So the moral of this story is that the Matlab IDE makes structs look nice and square by inserting fields in all structs when you insert a new field into a particular struct element. However, in reality, in the underlying memory, this is not the case.

Beware!


Solution

  • What is happening is that mxGetFieldByNumber is returning NULL which you then pass to mxIsEmpty and so produce the seg fault. The documentation states that mxGetFieldByNumber returns NULL if there is no value assigned to the specified field.

    To solve this you will need to guard against passing NULL to mxIsEmpty:

    if (tmpData == NULL || mxIsEmpty(tmpData))
        mexPrintf("struct(%i).%s is empty\n", structIdx+1, tmpName);
    else
        mexPrintf("struct(%i).%s contains data\n", structIdx+1, tmpName);