如何确保在生成的 C 代码中正确分配 emxArray?

How can I ensure an emxArray is correctly allocated in generated C code?

我有以下 .m 文件(名为 testmemoryallocation.m),用于在 Matlab Coder 中生成代码。当然,这只是一个演示概念的测试文件。

function output = testmemoryallocation(dim) %#codegen
%TESTMEMORYALLOCATION Tests allocation of large 3D arrays

output = coder.nullcopy(zeros([dim, dim, dim]));
output(:) = 5;

end

我使用以下构建脚本构建它(来自 Coder 应用程序的默认设置)

%% Create configuration object of class 'coder.CodeConfig'.
cfg = coder.config('lib','ecoder',false);
cfg.GenerateReport = true;
cfg.GenCodeOnly = true;
cfg.HardwareImplementation = coder.HardwareImplementation;
cfg.HardwareImplementation.ProdIntDivRoundTo = 'Undefined';
cfg.HardwareImplementation.TargetIntDivRoundTo = 'Undefined';

%% Define argument types for entry-point 'testmemoryallocation'.
ARGS = cell(1,1);
ARGS{1} = cell(1,1);
ARGS{1}{1} = coder.typeof(0);

%% Invoke MATLAB Coder.
codegen -config cfg testmemoryallocation -args ARGS{1}

此构建过程生成的 C 代码如下所示:

/*
 * testmemoryallocation.c
 *
 * Code generation for function 'testmemoryallocation'
 *
 */

/* Include files */
#include "rt_nonfinite.h"
#include "testmemoryallocation.h"
#include "testmemoryallocation_emxutil.h"

/* Function Definitions */
void testmemoryallocation(double dim, emxArray_real_T *output)
{
  int i0;
  int loop_ub;

  /* TESTMEMORYALLOCATION Tests allocation of large 3D arrays */
  i0 = output->size[0] * output->size[1] * output->size[2];
  output->size[0] = (int)dim;
  emxEnsureCapacity((emxArray__common *)output, i0, (int)sizeof(double));
  i0 = output->size[0] * output->size[1] * output->size[2];
  output->size[1] = (int)dim;
  emxEnsureCapacity((emxArray__common *)output, i0, (int)sizeof(double));
  i0 = output->size[0] * output->size[1] * output->size[2];
  output->size[2] = (int)dim;
  emxEnsureCapacity((emxArray__common *)output, i0, (int)sizeof(double));
  loop_ub = (int)dim * (int)dim * (int)dim;
  for (i0 = 0; i0 < loop_ub; i0++) {
    output->data[i0] = 5.0;
  }
}

/* End of code generation (testmemoryallocation.c) */

问题是:MATLAB Coder(在 testmemoryallocation.m 文件中)是否有办法验证内存是否已实际分配?内存分配发生在 emxEnsureCapacity 函数中,它本身没有 return 有关成功分配的信息(据我所知)。如果没有,我希望能够从调用函数中优雅地退出'没有足够的系统内存来完成该过程。例如,我想向 testmemoryallocation 添加一个 result 输出,指示分配内存时是否发生错误。有办法吗?

实际上,问题归结为:是否有一种方法可以访问 emxArrays 以测试结构的 data 字段不为空。也许是用 coder.ceval 调用的 .c 文件?有没有办法指示 coder.ceval 传递 emxArray 类型而不是基本类型(double 或 int),以便可以编写底层 .c 代码来与结构交互?

编辑: 接受的优秀答案解决了这个问题和生成的 c 代码中 (2015a) Coder 中的潜在错误。在 _emxutil 文件中,如果请求的元素数量超过 intmax/2,emxEnsureCapacity 函数可能会出现无限循环。

我已经解决了这个问题,我认为我有一个很好的解决方案。我创建了一个名为 isallocated 的 MATLAB 函数,它将可变大小的双精度浮点数组作为输入,如果数组分配正确则输出 1,否则输出 0。如果在MATLAB环境下调用,总是returns 1.

function result = isallocated(inarray) %#codegen
%ISALLOCATED Returns true if the array memory is allocated properly.
%  Will check the array to verify that the data pointer is not null.

if coder.target('MATLAB')
    result = int32(1);
    return;
end

result = int32(0);
coder.cinclude('emxArray_helpers.h');
coder.updateBuildInfo('addSourceFiles', 'emxArray_helpers.c');
result = coder.ceval('isallocated_helper', coder.ref(inarray));

end

这个函数只是isallocated_helper的包装器,这里唯一的技巧是coder.ref命令将指针提取到inarray中的数据数组。这具有获取 emxArray 结构的 data 成员值的效果。

c 函数 isallocated_helper 出奇的无聊;这只是一个测试,看指针是否为空。为了完整起见,我将代码放在这里:

// emxArray_helpers.c
// A set of helper functions for emxArray types

int isallocated_helper(double * arrayptr)
{
    if (arrayptr)
        return 1;
    return 0;
}

仅此而已。这提供了一个很好的测试,如果我更改 testmemoryallocation.m 文件来使用它,它看起来像:

function [result, output] = testmemoryallocation(dim) %#codegen
%TESTMEMORYALLOCATION Tests allocation of large 3D arrays

output = coder.nullcopy(zeros([dim, dim, dim]));
result = isallocated(output);
if result == 1
    output(:) = 5;
else
    output = zeros([1,1,2]);
end

end

在生成的 c 代码片段中,我们可以看到测试:

...
/* TESTMEMORYALLOCATION Tests allocation of large 3D arrays */
i0 = output->size[0] * output->size[1] * output->size[2];
output->size[0] = (int)dim;
output->size[1] = (int)dim;
output->size[2] = (int)dim;
emxEnsureCapacity((emxArray__common *)output, i0, (int)sizeof(double));

/* ISALLOCATED Returns true if the array memory is allocated properly. */
/*   Will check the array to verify that the data pointer is not null.   */
*result = isallocated_helper(&output->data[0]);
if (*result == 1) {
...

很高兴看到你在进步。关于 emxEnsureCapacity 问题,这是不幸的,它是一个错误。我会处理它以确保我们在即将发布的版本中修复它。同时,有一种方法可以修补生成的源代码。对于配置对象,在生成 C 代码后有 'PostCodeGenCommand' 选项,即 executed/evaluated。这允许您在编译之前应用补丁。例如,

cfg.PostCodeGenCommand = 'mypatch';

然后'mypatch.m'是:

function mypatch

cfiles = all_cfiles([pwd filesep 'codegen']);
for i = 1:numel(cfiles)
    cfile = cfiles{i};
    if strcmp(cfile(end-9:end), '_emxutil.c')
        text = fileread(cfile);
        text = regexprep(text, '(while \(i < newNumel\) \{\s+i <<= 1; (\s+\})', ' if (i < 0) i = 0x7fffffff; ');
        filewrite(cfile, text);
        disp(cfiles{i});
    end
end

function filewrite(filename, text)
f = fopen(filename, 'w');
fprintf(f, '%s', text);
fclose(f);

function files = all_cfiles(d)
files = {};
fs = dir(d);
for i = 1:numel(fs)
    if (fs(i).name(1) == '.')
        continue
    end
    if fs(i).isdir
        files = [files all_cfiles([d filesep fs(i).name])];
    else
        if strcmp(fs(i).name(end-1:end), '.c')
            files{end+1} = [d filesep fs(i).name];
        end
    end
end

关于将 'emxArray' 传递给外部 C 函数(在 coder.ceval() 调用中使用 coder.ref())这有点问题。这是因为 'emxArray' 表示可能存在也可能不存在,具体取决于矩阵的大小。还有一个阈值(在配置对象中)可以让您知道何时切换到 "full" emxArrays 或将它们保留为 "upperbound allocated" 变量(调用边界被拆分为数据和大小作为单独的变量。 ) 可变大小数组有更多的表示形式(例如,如果结构中有上界变量。)这是我们没有 emxArrays 直接接口的主要原因,因为类型兼容性可能会根据这些参数发生变化。因此,始终提取数据将使其类型兼容。如果你需要访问大小,这里假设 'x' 是一个向量,那么你可以显式地传递大小:

coder.ceval('foo', x, int32(numel(x)));

不幸的是,这不允许您更改 'foo' 中 'x' 的大小。

还有一件事你可以做,但请直接与我联系(电子邮件),我会告诉你更多。

亚历山大·波蒂玛
软件工程师
MATLAB 程序员团队
数学作业