Main Content

本页翻译不是最新的。点击此处可查看最新英文版本。

生成的代码和 MATLAB 代码之间的差异

为了将 MATLAB® 代码转换为高效的 C/C++ 代码,代码生成器引入了优化,有意使生成的代码与原始源代码具有不同的行为,并且有时产生不同的结果。

以下是一些差异:

在以下情形中会出现这些差异:

  • 使用 codegen (MATLAB Coder) 命令或 MATLAB Coder™ 生成 MEX 和独立的 C/C++ 代码。

  • 通过使用 fiaccel 命令生成 MEX 来实现定点代码加速。

  • 使用 Simulink® 进行 MATLAB Function 模块仿真。

当您运行生成的 fiaccel MEX、C/C++ MEX 或独立的 C/C++ 代码时,运行时错误检查可以检测到其中的一些差异。默认情况下,对 MEX 代码启用运行时错误检查,对独立 C/C++ 代码禁用运行时错误检查。为帮助您在部署代码之前识别和解决差异,代码生成器将差异的一部分报告为潜在差异

具有多种可能输出的函数

某些数学运算,如矩阵的奇异值分解和特征值分解,可以有多个答案。实现此类运算的两种不同算法可能会为相同的输入值返回不同的输出。相同算法的两种不同实现也可能会出现这种行为。

对于此类数学运算,使用生成代码中的对应函数与使用 MATLAB 可能会为相同的输入值返回不同的输出。要查看某个函数是否有这种行为,请在对应的函数参考页中,参阅扩展功能下的 C/C++ 代码生成部分。此类函数的示例包括 svdeig

写入 ans 变量

当您在未指定输出参数的情况下运行 MATLAB 代码并返回输出时,MATLAB 会将输出隐式写入 ans 变量中。如果工作区中已存在变量 ans,MATLAB 会将其值更新为返回的输出。

从此类 MATLAB 代码生成的代码不会将输出隐式写入 ans 变量。

例如,定义 MATLAB 函数 foo,它在第一行中显式创建 ans 变量。在第二行执行时,该函数会隐式更新 ans 的值。

function foo %#codegen
ans = 1;
2;
disp(ans);
end

在命令行中运行 fooans 的最终值,即 2,显示在命令行上。

foo
2

foo 生成一个 MEX 函数。

codegen foo

运行生成的 MEX 函数 foo_mex。此函数显式创建 ans 变量,并为其赋值 1。但是,foo_mex 并不将 ans 的值隐式更新为 2

foo_mex
1

逻辑短路

假设您的 MATLAB 代码中逻辑运算符 &| 放在方括号([])内。对于这种代码模式,生成的代码不会对这些逻辑运算符采用短路行为,但某些 MATLAB 执行会采用短路行为。请参阅Tips提示

例如,定义 MATLAB 函数 foo,该函数在 if...end 模块的条件表达式中使用方括号内的 & 运算符。

function foo
if [returnsFalse() & hasSideEffects()]
end
end

function out = returnsFalse
out = false;
end

function out = hasSideEffects
out = true;
disp('This is my string');
end

& 运算符的第一个参数始终是 false,并确定条件表达式的值。因此,在 MATLAB 执行中,使用短路,并且不计算第二个参数。因此,foo 在执行期间不会调用 hasSideEffects 函数,也不会在命令行显示任何内容。

foo 生成一个 MEX 函数。调用生成的 MEX 函数 foo_mex

foo_mex
This is my string

在生成的代码中,没有使用短路。因此,调用 hasSideEffects 函数,字符串显示在命令行中。

循环索引溢出

假设 for 循环结束值等于或接近循环索引数据类型的最大值或最小值。在生成的代码中,循环索引的最后一次递增或递减可能会导致索引变量溢出。索引溢出可能导致无限循环。

启用内存完整性检查时,如果代码生成器检测到循环索引可能溢出,它会报告错误。软件错误检查是保守型的。它可能会误报循环索引溢出。默认情况下,内存完整性检查对 MEX 代码启用,对独立 C/C++ 代码禁用。请参阅为什么要在 MATLAB 中测试 MEX 函数? (MATLAB Coder)Generate Standalone C/C++ Code That Detects and Reports Run-Time Errors (MATLAB Coder)

为了避免循环索引溢出,请使用下表中的解决方法。

可能导致溢出的循环条件解决方法
  • 循环索引递增 1。

  • 结束值等于整数类型的最大值。

如果循环不必覆盖整数类型的全部范围,请重写循环,使结束值不等于整数类型的最大值。例如,将:

N=intmax('int16')
for k=N-10:N
替换为:
for k=1:10

  • 循环索引递减 1。

  • 结束值等于整数类型的最小值。

如果循环不必覆盖整数类型的全部范围,请重写循环,使结束值不等于整数类型的最小值。例如,将:

N=intmin('int32')
for k=N+10:-1:N
替换为:
for k=10:-1:1

  • 循环索引递增或递减 1。

  • 起始值等于整数类型的最小值或最大值。

  • 结束值等于整数类型的最大值或最小值。

如果循环必须覆盖整数类型的全部范围,请将循环的开始值、步长值和结束值的类型转换为更大的整数或双精度值。例如,重写:

M= intmin('int16');
N= intmax('int16');
for k=M:N
	% Loop body
end
为:
M= intmin('int16');
N= intmax('int16');
for k=int32(M):int32(N)
	% Loop body
end

  • 循环索引按不等于 1 的值递增或递减。

  • 在最后一次循环迭代中,循环索引不等于结束值。

重写循环,使最后一次循环迭代中的循环索引等于结束值。

使用单精度操作数对 for 循环进行索引

假设在您的 MATLAB 代码中,您正在对具有冒号运算符的 for 循环进行索引,其中至少一个冒号操作数是 single 类型的操作数,并且迭代次数大于 flintmax('single') = 16777216。当所有这些条件都满足时,代码生成可能会产生运行时或编译时错误,因为生成代码为循环索引变量计算的值与 MATLAB 计算的值不同。

以如下 MATLAB 代码为例:

function j = singlePIndex
n = flintmax('single') + 2;
j = single(0);
for i = single(1):single(n)
    j = i;
end
end

此代码段会在 MATLAB 中执行,但它会导致编译时或运行时错误,因为循环索引变量 i 的值在生成代码中的计算方式不同。代码生成器显示编译时或运行时错误,并停止代码生成或执行以防止出现这种差异。

为了避免这种差异,请用双精度类型或整数类型操作数替换单精度类型操作数。

有关运行时错误的详细信息,请参阅Generate Standalone C/C++ Code That Detects and Reports Run-Time Errors (MATLAB Coder)

未执行的 for 循环的索引

在您的 MATLAB 代码和生成代码中,在一个 for 循环执行完成后,索引变量的值等于其在 for 循环的最后一次迭代中的值。

在 MATLAB 中,如果循环不执行,则索引变量的值存储为 [ ](空矩阵)。在生成代码中,如果循环不执行,则索引变量的值不同于 MATLAB 索引变量。

  • 如果在运行时提供 for 循环的开始和结束变量,则索引变量的值等于范围的开始值。以如下 MATLAB 代码为例:

    function out = indexTest(a,b)
    for i = a:b
    end
    out = i;
    end

    假设 ab 作为 1-1 传递。则 for 循环不会执行。在 MATLAB 中,out 被赋值为 [ ]。在生成代码中,out 被赋予 a 的值,即 1

  • 如果在编译时间之前提供 for 循环的开始和结束值,则在 MATLAB 和生成的代码中都会赋予索引变量值 [ ]。以如下 MATLAB 代码为例:

    function out = indexTest
    for i = 1:-1
    end
    out = i;
    end

    在 MATLAB 和生成的代码中,out 都被赋值为 [ ]。

字符大小

MATLAB 支持 16 位字符,但生成的代码以 8 位字符表示,这是适用于大多数嵌入式语言(如 C)的标准大小。请参阅代码生成中的字符编码

表达式中的计算顺序

生成的代码不强制指定表达式中的计算顺序。对于大多数表达式,计算的顺序并不重要。对于具有副作用的表达式,生成的代码可能以不同于原始 MATLAB 代码的顺序产生副作用。产生副作用的表达式包括:

  • 修改持久性或全局变量的表达式

  • 将数据显示到屏幕的表达式

  • 将数据写入文件的表达式

  • 修改句柄类对象属性的表达式

此外,生成的代码不强制非短路逻辑运算符的计算顺序。

为获得更可预测的结果,最好在编码时根据计算顺序将表达式拆分为多个语句。

  • 重写

    A = f1() + f2();

    A = f1();
    A = A + f2();

    使生成的代码在调用 f2 之前调用 f1

  • 将多输出函数调用的输出赋给不相互依赖的变量。例如,重写

    [y, y.f, y.g] = foo;

    [y, a, b] = foo;
    y.f = a;
    y.g = b;
    

  • 当您访问元胞数组的多个元胞的内容时,将结果赋给不相互依赖的变量。例如,重写

    [y, y.f, y.g] = z{:};
    

    [y, a, b] = z{:};
    y.f = a;
    y.g = b;
    

构造函数句柄时的名称解析

MATLAB 和代码生成遵循不同优先级规则来解析符号 @ 后面的名称。这些规则不适用于匿名函数。下表总结了这些优先级规则。

表达式MATLAB 中的优先顺序代码生成中的优先顺序
不包含句点的表达式,例如 @x

嵌套函数、局部函数、私有函数、路径函数

局部变量、嵌套函数、局部函数、私有函数、路径函数

仅包含一个句点的表达式,例如 @x.y

局部变量、路径函数

局部变量、路径函数(与 MATLAB 相同)

包含多个句点的表达式,例如 @x.y.z

路径函数

局部变量、路径函数

如果 x 是局部变量且本身就是函数句柄,则生成代码和 MATLAB 对表达式 @x 的解释将有所不同:

  • MATLAB 生成错误。

  • 生成代码将 @x 解释为 x 本身的函数句柄。

以下示例说明对于包含两个句点的表达式在行为上的差异。

假设您的当前工作文件夹包含一个包 x,该包又包含另一个包 y,其中再包含函数 z。当前工作文件夹还包含您要为其生成代码的入口函数 foo

Image of current folder showing the files z.m and foo.m with respect to the packages x and y.

以下是文件 foo 的定义:

function out = foo
    x.y.z = @()'x.y.z is an anonymous function';
    out = g(x);
end

function out = g(x)
    f = @x.y.z;
    out = f();
end

以下是函数 z 的定义:

function out = z
    out = 'x.y.z is a package function';
end

foo 生成一个 MEX 函数。分别调用生成的 MEX 函数 foo_mex 和 MATLAB 函数 foo

codegen foo
foo_mex
foo
ans =

    'x.y.z is an anonymous function'


ans =

    'x.y.z is a package function'

生成代码产生第一个输出。MATLAB 产生第二个输出。代码生成将 @x.y.z 解析为在 foo 中定义的局部变量 x。MATLAB 将 @x.y.z 解析为在 x.y 包内的 z

终止行为

生成的代码与 MATLAB 源代码的终止行为不一致。例如,如果无限循环不具有副作用,则优化会从生成的代码中删除它们。因此,即使对应的 MATLAB 代码不会终止,生成的代码也可能会终止。

可变大小 N 维数组的大小

对于可变大小 N 维数组,size 函数在生成的代码中返回的结果可能与在 MATLAB 源代码中不同。size 函数有时会在生成的代码中返回尾部维(单一维),但在 MATLAB 中始终会丢弃尾部维。例如,对于维度为 [4 2 1 1] 的 N 维数组 Xsize(X) 可能会在生成的代码中返回 [4 2 1 1],但在 MATLAB 中始终返回 [4 2]。请参阅在确定可变大小 N 维数组的大小方面与 MATLAB 的不兼容性

空数组的大小

空数组的大小在生成的代码中可能与 MATLAB 源代码中不同。请参阅在确定空数组的大小方面与 MATLAB 的不兼容性

删除数组元素所生成的空数组的大小

删除数组的所有元素将生成空数组。此空数组的大小在生成的代码中可能与在 MATLAB 源代码中不同。

情形示例代码MATLAB 中空数组的大小生成的代码中空数组的大小
使用 colon 运算符 (:) 删除 m×n 数组的所有元素。
coder.varsize('X',[4,4],[1,1]);
X = zeros(2);
X(:) = [];
0-by-01-by-0
使用 colon 运算符 (:) 删除行向量的所有元素。
coder.varsize('X',[1,4],[0,1]);
X = zeros(1,4);
X(:) = [];
0-by-01-by-0
使用 colon 运算符 (:) 删除列向量的所有元素。
coder.varsize('X',[4,1],[1,0]);
X = zeros(4,1);
X(:) = [];
0-by-00-by-1
通过一次删除一个元素来删除列向量的所有元素。
coder.varsize('X',[4,1],[1,0]);
X = zeros(4,1);
for i = 1:4
    X(1)= [];
end
1-by-00-by-1

单精度和双精度操作数的二元按元素运算

如果您的 MATLAB 代码包含涉及单精度类型操作数和双精度类型操作数的二元按元素运算,则生成代码的运算结果与 MATLAB 的运算结果可以会稍有不同。

对于此类运算,MATLAB 会先将两项操作数都转换为双精度类型,并用双精度类型执行运算。然后,MATLAB 再将运算结果转换为单精度类型并返回结果。

而生成代码则是将双精度类型操作数转换为单精度类型。然后用两个单精度类型执行运算并返回结果。

例如,定义一个 MATLAB 函数 foo,用它调用二元按元素运算 plus

function out = foo(a,b)
out = a + b;
end

定义单精度类型的变量 s1 和双精度类型的变量 v1。为 foo 生成一个 MEX 函数,该函数接受单精度类型输入和双精度类型输入。

s1 = single(1.4e32); 
d1 = -5.305e+32; 
codegen foo -args {s1, d1} 

使用 s1d1 作为输入调用 foofoo_mex。比较两个结果。

ml = foo(s1,d1); 
mlc = foo_mex(s1,d1);
ml == mlc
ans =

  logical

   0

比较的输出是逻辑值 0,这表明生成代码和 MATLAB 对这些输入分别产生了不同结果。

浮点数值结果

在下列情况下,生成的代码可能不会产生与 MATLAB 相同的浮点数值结果:

 计算机硬件使用扩展精度寄存器时

 对于某些高级库函数

 对于 BLAS 库函数的实现

NaN 和无穷

NaNInf 值在数学上没有意义时,它们在生成的代码中的模式可能不会与在 MATLAB 代码中完全相同。例如,如果 MATLAB 输出包含 NaN,生成的代码的输出也应包含 NaN,但不一定在相同的位置。

NaN 的位模式在 MATLAB 代码输出和生成的代码输出之间可能有所不同,这是因为用于生成代码的 C99 语言标准未对所有实现中的 NaN 指定唯一位模式。请避免比较不同实现中的位模式,例如,在 MATLAB 输出和 SIL 或 PIL 输出之间进行比较。

负零

在浮点类型中,值 0 具有正号或负号。在算术上,0 等于 -0,但某些运算对 0 输入符号敏感。示例包括 rdivideatan2atan2dangle。除以 0 会生成 Inf,而除以 -0 会生成 -Inf。同样,atan2d(0,-1) 生成 180,而 atan2d (-0,-1) 生成 -180

如果代码生成器检测到某浮点变量只接受适当范围的整数值,则代码生成器可以在生成的代码中对该变量使用整数类型。如果代码生成器对该变量使用整数类型,则该变量将 -0 存储为 +0,因为整数类型不存储值 0 的符号。如果生成的代码将该变量强制转换回浮点类型,则 0 的符号为正。除以 0 会生成 Inf,而不是 -Inf。同样,atan2d(0,-1) 生成 180,而不是 -180

在其他情况下,生成的代码可能会以不同于 MATLAB 的方式处理 -0。例如,假设您的 MATLAB 代码使用 z = min(x,y) 计算两个双精度标量值 xy 的最小值。生成的 C 代码中对应的行可能是 z = fmin(x,y)。函数 fmin 是在 C 编译器的运行时数学库中定义的。由于比较运算 0.0 == -0.0 在 C/C++ 中返回 true,因此编译器对 fmin 的实现可能会为 fmin(0.0,-0.0) 返回 0.0-0.0

代码生成目标

coder.target 函数在 MATLAB 中返回的值与在生成的代码中返回的值不同。这是为了帮助您确定您的函数是在 MATLAB 中执行,还是已针对仿真或代码生成目标进行编译。请参阅 coder.target

MATLAB 类属性初始化

在代码生成之前,MATLAB 在类加载时计算类默认值。代码生成器使用 MATLAB 计算的值。它不会重新计算默认值。如果属性定义使用函数调用来计算初始值,则代码生成器不会执行此函数。如果函数具有修改全局变量或持久变量等副作用,则生成的代码可能会产生与 MATLAB 不同的结果。有关详细信息,请参阅为代码生成定义类属性

具有 set 方法的嵌套属性赋值中的 MATLAB

当您为句柄对象属性赋值时,如果该属性本身就是另一个对象的属性(依此类推),则生成的代码可以调用 MATLAB 不调用的句柄类 set 方法。

例如,假设您定义一组变量,使得 x 是句柄对象,pa 是对象,pb 是句柄对象,pcpb 的属性。然后进行嵌套属性赋值,例如:

x.pa.pb.pc = 0;

在本例中,生成的代码调用对象 pb 的 set 方法和 x 的 set 方法。MATLAB 仅调用 pb 的 set 方法。

MATLAB 句柄类析构函数

在下列情况下,生成的代码中句柄类析构函数的行为可能与 MATLAB 中的行为不同:

  • 有些独立对象在 MATLAB 中的销毁顺序可能与生成的代码中的顺序不同。

  • 生成的代码中的对象的生命周期可能与它们在 MATLAB 中的生命周期不同。

  • 生成的代码不销毁部分构造的对象。如果句柄对象在运行时未完全构造,则生成的代码将生成错误消息,但不为该对象调用 delete 方法。对于 System object™,如果在 setupImpl 中存在运行时错误,则生成的代码不为该对象调用 releaseImpl

    MATLAB 确实会调用 delete 方法来销毁部分构造的对象。

有关详细信息,请参阅Code Generation for Handle Class Destructors

可变大小数据

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

复数

请参阅复数数据的代码生成

将使用连续一元运算符的字符串转换为 double

如果将包含多个连续一元运算符的字符串转换为 double,则在 MATLAB 和生成的代码之间可能产生不同结果。以如下函数为例:

function out = foo(op)
out = double(op + 1);
end

对于输入值 "--",该函数将字符串 "--1" 转换为 double。在 MATLAB 中,结果为 NaN。在生成的代码中,结果为 1

显示函数

MATLAB 代码中省略分号的语句和表达式隐式调用 display 函数。您也可以显式调用 display,如下所示:

display(2+3);
     5

为调用 display 函数的 MATLAB 代码生成的 MEX 代码将保留对此函数的调用并显示输出。在为无权访问 MATLAB Runtime 的目标生成的独立代码中,删除了对 display 的隐式和显式调用。这包括对 display 的覆盖类方法的调用。

要在为其他目标生成的代码中显示文本,请在 MATLAB 类中覆盖 disp 函数。例如:

%MATLAB Class

classdef foo
    methods
        function obj = foo
        end
        function disp(self)
            disp("Overridden disp");
        end
    end
end

%Entry-point Function

function callDisp
a = foo;
disp(a);
end

为入口函数生成的代码如下所示:

/* Include Files */
#include "callDisp.h"
#include <stdio.h>

/* Function Definitions */
/*
 * Arguments    : void
 * Return Type  : void
 */
void callDisp(void)
{
  printf("%s\n", "Overridden disp");
  fflush(stdout);
}

函数句柄差异

通过 MATLAB 中的函数句柄调用 display 也会输出变量的名称。例如,在 MATLAB 中运行此函数会产生以下输出:

function displayDiff
z = 10;
f = @display;
f(z)
end
z =

    10

但是,为此代码段生成的代码只输出值 10

相关主题