Main Content

在代码生成的可变大小支持方面与 MATLAB 的不兼容性

在标量扩展方面与 MATLAB 的不兼容性

标量扩展是对标量数据进行转换以便与向量或矩阵数据的维度相匹配的一种方法。如果一个操作数是标量,而另一个不是,则标量扩展会将标量应用于另一个操作数中的每个元素。

在代码生成过程中,标量扩展规则同样适用,除非处理的是两个可变大小表达式。在这种情况下,两个操作数必须具有相同的大小。否则,即使其中一个可变大小表达式在运行时变为标量,生成的代码也不会执行标量扩展。因此,如果启用了运行时错误检查,可能会发生运行时错误。

以如下函数为例:

function y = scalar_exp_test_err1(u) %#codegen
y = ones(3);
switch u
    case 0
        z = 0;
    case 1
        z = 1;
    otherwise
        z = zeros(3);
end
y(:) = z;

当您为此函数生成代码时,代码生成器会将 z 确定为一个上界为 3 的可变大小。

This image shows the report for the function scalar_exp_test_err1. z is variable-size with an upper bound of 3, as indicated in the size column. The semicolon next to each dimension indicates that the dimension is variable-size.

如果您运行 u 等于 0 或 1 的 MEX 函数,即使 z 在运行时为标量,生成的代码也不会执行标量扩展。因此,如果启用了运行时错误检查,可能会发生运行时错误。

scalar_exp_test_err1_mex(0)
Subscripted assignment dimension mismatch: [9] ~= [1].

Error in scalar_exp_test_err1 (line 11)
y(:) = z;

为了避免此问题,请使用索引强制 z 为标量值:

function y = scalar_exp_test_err1(u) %#codegen
y = ones(3);
switch u
    case 0
        z = 0;
    case 1
        z = 1;
    otherwise
        z = zeros(3);
end
y(:) = z(1);

在确定可变大小 N 维数组的大小方面与 MATLAB 的不兼容性

对于可变大小 N 维数组,size 函数可能在生成的代码中返回与 MATLAB® 不同的结果。在生成的代码中,size(A) 返回固定长度输出,因为它不会舍弃可变大小 N 维数组的尾部单一维度。与之相比,MATLAB 中的 size(A) 则返回可变长度输出,因为它舍弃了尾部单一维度。

例如,如果数组 A 的形状为 :?x:?x:?size(A,3)==1,则 size(A)

  • 在生成的代码中返回三元素向量

  • 在 MATLAB 代码中返回二元素向量

解决办法

如果您的应用程序要求生成的代码返回与 MATLAB 代码大小相同的可变大小 N 维数组,请考虑以下解决办法之一:

  • 使用具有两个参数的 size

    例如,size(A,n) 在生成的代码和 MATLAB 代码中返回相同的答案。

  • 重写 size(A)

    B = size(A);
    X = B(1:ndims(A));

    此版本返回具有可变长度输出的 X。但是,不能将可变大小 X 传递给需要固定大小参量的矩阵构造函数,例如 zeros

在确定空数组的大小方面与 MATLAB 的不兼容性

空数组的大小在生成的代码中可能与 MATLAB 源代码中不同。此大小在生成的代码中可能为 1x00x1,但在 MATLAB 中可能为 0x0。因此,不应编写依赖特定大小的空矩阵的代码。

以如下代码为例:

function y = foo(n) %#codegen
x = [];
i = 0;
while (i < 10)
    x = [5 x];
    i = i + 1;
end
if n > 0
    x = [];
end
y = size(x);
end

串联要求其操作数与未串联的维度的大小相匹配。在前面的串联中,标量值的大小为 1x1x 的大小为 0x0。为了支持此用例,代码生成器将 x 的大小确定为 [1 x :?]。因为串联之后还有一次赋值 x = [],所以在生成的代码中,x 的大小为 1x0 而不是 0x0

在确定表示为 '' 的空字符向量的大小时,此行为会持续存在。以如下代码为例:

function out = string_size
out = size('');
end

此处,out 的值在生成代码中可能是 1x00x1,但在 MATLAB 中是 0x0

要了解在确定因删除数组元素而产生的空数组的大小时与 MATLAB 的不兼容性,请参阅删除数组元素所生成的空数组的大小

解决方法

如果您的应用程序会检查矩阵是否为空,请使用以下解决办法之一:

  • 重写代码以使用 isempty 函数,而不是 size 函数。

  • 不要使用 x=[] 创建空数组,而应使用 zeros 创建具有特定大小的空数组。例如:

    function y = test_empty(n) %#codegen
    x = zeros(1,0);
    i=0;
    while (i < 10)
        x = [5 x];
        i = i + 1;
    end
    if n > 0
        x = zeros(1,0);
    end
    y=size(x);
    end

在确定空数组的类方面与 MATLAB 的不兼容性

空数组的类在生成的代码中可能与 MATLAB 源代码中不同。因此,不应编写依赖空矩阵的类的代码。

以如下代码为例:

function y = fun(n)
x = [];
if n > 1
    x = ['a' x];
end
y=class(x);
end 
fun(0) 在 MATLAB 中返回 double,但在生成的代码中返回 char。当语句 n > 1 为 false 时,MATLAB 不会执行 x = ['a' x]x 的类为 double,即空数组的类。但是,代码生成器将考虑所有执行路径。它基于语句 x = ['a' x] 确定 x 的类为 char

解决方法

不要使用 x=[] 创建空数组,而应创建特定类的空数组。例如,使用 blanks(0) 创建空字符数组。

function y = fun(n)
x = blanks(0);
if n > 1
    x = ['a' x];
end
y=class(x);
end

在矩阵-矩阵索引方面与 MATLAB 的不兼容性

在矩阵-矩阵索引中,使用的是一个矩阵(索引矩阵)对另一个矩阵(数据矩阵)进行索引。在 MATLAB 中,矩阵-矩阵索引的一般规则是结果的维度与索引矩阵的维度相同。例如,如果 AB 是矩阵,则 size(A(B)) 等于 size(B)。然而,当 AB 是向量时,MATLAB 会应用不同的规则。当执行向量-向量索引时,结果的方向与数据矩阵的方向相同。例如,如果 A 是 1×5,B 是 3×1,则 A(B) 是 1×3。

代码生成器尝试应用与 MATLAB 相同的矩阵-矩阵索引规则。如果 AB 在代码生成时是可变大小矩阵,则代码生成器遵循一般的 MATLAB 索引规则,并假设 size(A(B)) 等于 size(B)。在运行时,如果 AB 是方向不同的向量,则此假设不正确。因此,如果启用了运行时错误检查,可能会发生错误。

要避免这种运行时错误,请尝试以下解决方案之一:

  • 如果 AB 在运行时是固定大小矩阵,则在代码生成时将此矩阵定义为固定大小。

  • 如果 AB 在运行时均为向量,请确保其方向匹配。

  • 如果您的代码在运行时特意接受具有不同方向的矩阵和向量,请包含对向量-向量索引进行显式检查,并强制向量具有相同的方向。例如,使用 isvector 函数确定 AB 是否均为向量,如果是,则使用 colon 运算符强制这两个向量均为列向量。

    ...
    if isvector(A) && isvector(B)
        Acol = A(:);
        Bcol = B(:);
        out = Acol(Bcol);
    else
        out = A(B);
    end
    ...

在建立向量-向量索引方面与 MATLAB 的不兼容性

在向量-向量索引中,使用一个向量(索引向量)对另一个向量(数据向量)进行索引。在 MATLAB 中,向量-向量索引的规则是结果向量的方向与数据向量的方向相同。例如,如果 A 是 1×5,B 是 3×1,则 A(B) 是 1×3。但是,如果 A 是标量,则此规则不适用。如果 A 是标量,则 A(B) 的方向与索引向量 B 的方向相同。

代码生成器尝试应用与 MATLAB 相同的向量-向量索引规则。如果 A 在代码生成时是可变大小向量,则代码生成器假设 A(B) 的方向与 A 的方向相同。但是,此假设是错误的,如果同时满足以下两个条件,就会发生运行时错误:

  • 在代码生成时,A 的方向与 B 的方向不匹配。

  • 在运行时,A 是标量,B 是向量。

要避免这种运行时错误,请尝试以下解决方案之一:

  • 如果 A 在运行时是标量,则在代码生成时将 A 定义为标量。

  • 如果 AB 在代码生成时定义为向量,请确保其方向相同。

  • 如果 AB 在代码生成时是具有不同方向的可变大小向量,请确保 A 在运行时不是标量。

  • 如果 AB 在代码生成时是具有不同方向的可变大小向量,请确保 B 在运行时不是向量。

在进行逻辑索引时与 MATLAB 的不兼容性

在逻辑索引中,您使用的是一个逻辑值数组(索引数组)对另一个数组(数据数组)进行索引。在 MATLAB 执行中,生成的数组的形状取决于索引和数据数组是矩阵、向量还是标量。当您为使用可变大小数组执行逻辑索引的 MATLAB 代码生成 C/C++ 代码时,代码生成器可能会对索引结果的形状作出假设,而这些假设对于某些运行时输入是错误的,并且会导致运行时错误。

如果索引和数据数组在运行时是固定大小,则可以通过在代码生成时将这两个数组都定义为固定大小来避免这种运行时错误。

如果在代码生成时必须将其中一个或两个数组都定义为可变大小,请参考下表,了解针对某些输入可能出现的运行时错误的一些可能的解决方案。在下表中,A 是数据数组,B 是逻辑索引数组。

在代码生成时的定义运行时的输入可能的解决方案
A 是固定大小矩阵,B 是可变大小矩阵。B 是行向量。使用 colon 运算符在运行时强制 B 成为列向量。
A 是可变大小矩阵,B 是固定大小矩阵。A 是行向量。使用 colon 运算符在运行时强制 A 成为列向量。
A 是可变大小矩阵,B 是可变大小矩阵。A 是行向量。使用 colon 运算符在运行时强制 A 成为列向量。
A 是可变大小矩阵,B 是可变大小矩阵。A 是矩阵,B 是行向量。使用 colon 运算符在运行时强制 B 成为列向量。
A 是可变大小矩阵,B 是向量。 AB 是向量,但 AB 的方向不同。强制 AB 在运行时具有相同的方向。

在代码生成的矩阵索引操作方面与 MATLAB 的不兼容性

以下限制适用于代码生成时的矩阵索引操作:

  • 初始化以下样式:

    for i = 1:10
        M(i) = 5;
    end
    

    在本例中,执行循环时 M 的大小会改变。代码生成不支持数组大小随着时间改变而增加。

    对于代码生成,需要预分配 M

    M = zeros(1,10);
    for i = 1:10
        M(i) = 5;
    end
    

以下限制适用于在禁用动态内存分配的情况下进行代码生成时的矩阵索引操作:

  • M(i:j),其中 ij 在循环中改变

    在代码生成过程中,不会为大小随着程序的执行而改变的表达式动态分配内存。要实现这种行为,请使用 for- 循环,如下所示:

    ...
    M = ones(10,10);
    for i=1:10
        for j = i:10
            M(i,j) = 2*M(i,j);
        end
    end
    ...

    注意

    矩阵 M 必须在进入循环之前定义。

在串联可变大小矩阵方面与 MATLAB 的不兼容性

要进行代码生成,当您串联可变大小数组时,未串联的维度必须完全匹配。

当串联内可变大小元胞数组的花括号索引不返回任何元素时的差异

假设:

  • c 是一个可变大小元胞数组。

  • 可以使用花括号访问 c 的内容。例如,c{2:4}

  • 将结果包含在串联中。例如,[a c{2:4} b]

  • c{I} 不返回任何元素。c 为空或者花括号内的索引生成空结果。

基于这些条件,MATLAB 将忽略串联中的 c{I}。例如,[a c{I} b] 变为 [a b]。代码生成器将 c{I} 视为空数组 [c{I}]。串联变为 [...[c{i}]...]。此串联将忽略 [c{I}] 数组。因此,[c{I}] 的属性与串联 [...[c{i}]...] 兼容,代码生成器将根据以下规则指定 [c{I}] 的类、大小和复/实性:

  • 类和复/实性与元胞数组的基类型相同。

  • 第二个维度的大小始终为 0。

  • 对于其余的维度,Ni 的大小取决于基类型中对应的维度是固定大小还是可变大小。

    • 如果基类型中对应的维度为可变大小,则结果中的维度大小为 0。

    • 如果基类型中对应的维度为固定大小,则结果中的维度也具有该大小。

假设 c 的基类型的类为 int8,大小为 :10x7x8x:?。在生成的代码中,[c{I}] 的类为 int8[c{I}] 的大小为 0x0x8x0。第二个维度为 0。第一个和最后一个维度为 0,因为这两个维度在基类型中为可变大小。第三个维度为 8,因为基类型的第三个维度的大小固定为 8。

在串联内部,如果可变大小元胞数组的花括号索引不返回任何元素,生成的代码与 MATLAB 可能存在以下差异:

  • 在生成的代码中,[...c{i}...] 的类可能不同于 MATLAB 中的类。

    c{I} 不返回任何元素时,MATLAB 将从串联中删除 c{I}。因此,c{I} 不影响结果的类。MATLAB 基于其余的数组并根据类的优先级确定结果的类。请参阅不同类的有效合并。在生成的代码中,由于代码生成器将 c{I} 视为 [c{I}],因此 [c{I}] 的类将影响整体串联 [...[c{I}]...] 的结果的类。根据前面所述的规则确定 [c{I}] 的类。

  • 在生成的代码中,[c{I}] 的大小可能不同于 MATLAB 中的大小。

    在 MATLAB 中,串联 [c{I}] 为 0×0 双精度值。在生成的代码中,根据前面所述的规则确定 [c{I}] 的大小。