Преобразование обработки ошибок Matlab в C

Я подумал об использовании объектно-ориентированного дизайна для кода Matlab, а затем преобразовать его в C, где в конце его следует использовать для DSP-процессора.

В чем я не уверен, так это в том, как выполнять обработку ошибок. Насколько я знаю, в C он должен быть с перечислением, и если я использую исключение в своем коде Matlab, я не уверен, как Matlab преобразует его в C.

Другая возможность — реализовать класс enum и использовать его вместо исключений.

Поскольку у меня не так много опыта в архитектуре программного обеспечения, любые предложения более чем приветствуются.


person Eagle    schedule 15.04.2014    source источник
comment
Изменение тегов с помощью MATLAB Coder, который используется для генерации кода C, а не MATLAB Compiler или продуктов развертывания.   -  person Sam Roberts    schedule 15.04.2014


Ответы (1)


Это довольно старый вопрос, но я думаю, что это довольно важный вопрос, поскольку он касается некоторых фундаментальных вопросов, касающихся того, как лучше всего использовать MATLAB Coder и спроектировать свой код.

Хорошая система обработки ошибок очень важна для любой кодовой базы среднего размера, поэтому важно, чтобы она была структурирована. В коде, предназначенном для MATLAB Coder, вы не можете использовать какие-либо обычные функции исключения (try, catch, error и т. д.), и даже «утверждение» приобретает в Coder особое значение. Это означает, что вы сами должны создать хорошую систему обработки ошибок. Это печально, но не непреодолимо.

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

  1. Вы должны поддерживать класс перечисления для добавления каждого состояния ошибки.
  2. Это не облегчает трассировку стека напрямую.
  3. Вполне вероятно, что одна и та же ошибка будет повторно использоваться в разных местах, что не позволяет точно отследить местонахождение ошибки.
  4. Он не предоставляет удобочитаемых сообщений об ошибках.
  5. Он не позволяет возвращать данные времени выполнения, например значения переменных, которые использовались в функции нижнего уровня, что могло вызвать ошибку.

Это все, что предоставляет встроенная система MATLAB, поэтому было бы неплохо иметь что-то из этого.

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

Первая функция — контейнер ошибок. Это функция, в которой хранятся постоянные переменные, в которых хранятся ошибки времени выполнения.

function [ idnum, errorid, errorstring, inneridnum ] = errorcontainer(functionname, varargin ) %#codegen
%ERRORCONTAINER The container function for error processing
%   This function contains the error ID table, and can assign new error
%   codes or look up error codes.  
%  
%   Examples:
%     To register a new error and retrieve a unique error ID:
%       idnum = errorcontainter( 'register', ID, MESSAGE )
%       where ID and MESSAGE are strings.  ID is a short machine readable
%       string, and MESSAGE is human-readable.
%
%   Optionally, an INNERIDNUM can be provided to indicate an error code for
%   a inner (lower-level) error related to this.  This will allow
%   chaining of error messages:
%
%       idnum = errorcontainter( 'register', ID, MESSAGE, inneridnum )
%
%   ID and MESSAGE can have maximum lengths of 2048 characters.  Anything
%   longer will be truncated.
%
%     e.g.  idnum = errorcontainer('register', ...
%                                  'FOO_INVALIDTYPE', ...
%                                  'First input must be an int.');
%           idnum will be a negative int32 returned.
%
%        If the ID matches an existing ID, the message will be overwritten
%        and the same idnum will be returned (the database does not grow).
%
%     To lookup an existing error code:
%        [idnum, errorid, errorstring] = errorcontainer('lookup', idnum);
%
%   See also:  errorcode2string, registererror


persistent IDLIST;
persistent ERRORSTRINGLIST;
persistent INNERID;

width = 2048;

if isempty(IDLIST)
    IDLIST = char(zeros(1,width));
    tempstr = 'ERRORCONTAINER_UNKNOWNERRORID';
    IDLIST(1,1:numel(tempstr)) = tempstr;
end
if isempty(ERRORSTRINGLIST)
    ERRORSTRINGLIST = char(zeros(1,width));
    tempstr = 'Unknown Error';
    ERRORSTRINGLIST(1,1:numel(tempstr)) = tempstr;
end
if isempty(INNERID)
    INNERID = zeros(1,1,'int32');
end

coder.varsize('IDLIST', 'ERRORSTRINGLIST', 'INNERID', [], [1,1]);
coder.varsize('errorstring', 'errorid');

switch lower(functionname)
    case 'register'
        % First see if the listed ID matches any in the database.

        errorid = varargin{1};
        if numel(errorid) > width
            errorid = errorid(1:width);
        end

        if (nargin == 4)
            inneridnum = int32(varargin{3});
        else
            inneridnum = int32(0);
        end

        errorstring = varargin{2};
        if numel(errorstring) > width
            errorstring = errorstring(1:width);
        end

        matchindex = 0;
        for i = 1:size(IDLIST,1)
            if ( strcmpi(errorid, deblank(IDLIST(i,:))) && (inneridnum == INNERID(i) ) )
                matchindex = i;
            end
        end


        if (matchindex > 0)
            idnum = int32(-matchindex);
        else
            idnum = int32(-(size(IDLIST,1)+1));
            tempstr = char(zeros(1,width));
            tempstr(1:numel(errorid)) = errorid;
            IDLIST = [IDLIST ; tempstr];  % In Coder, cannot grow with indexing.  Have to concatinte and reassign.

            tempstr = char(zeros(1,width));
            tempstr(1:numel(errorstring)) = errorstring;
            ERRORSTRINGLIST = [ERRORSTRINGLIST ; tempstr];

            INNERID = [INNERID; inneridnum];

        end

    case 'lookup'
        idnum = varargin{1};
        tidnum = idnum;
        if ((-tidnum > size(IDLIST,1)) || (-tidnum <= 0 ))
            tidnum = int32(-1);
        end
        errorid = deblank(IDLIST(-tidnum,:));
        errorstring = deblank(ERRORSTRINGLIST(-tidnum,:));
        inneridnum = INNERID(-tidnum);

    otherwise
        idnum = int32(-1);
        errorid = deblank(IDLIST(-idnum,:));
        errorstring = deblank(ERRORSTRINGLIST(-idnum,:));
        inneridnum = int32(0);
end
end

Сердцем этой функции является большой оператор switch, который позволяет выбрать выполняемую задачу. Функция контейнера ошибок предназначена для использования в качестве внутреннего хранилища и служебной процедуры для системы обработки ошибок.

Основная функция, используемая для «генерирования» ошибок, — это функция registererror, показанная здесь:

function idnum = registererror(messageid, errorstring, varargin) %#codegen
%REGISTERERROR Registers a message id and error string with error system
%  Given a message ID string and an error code string (human-readable),
%  will register the strings with the error subsystem and generate a
%  negative ID number to return.
%
%  Example:
%    idnum = registererror('FOO_INVALIDINPUT', 'Invalid input to function foo');
%  
%  Optionally, an inner id number can be handed to the registration to
%  assocaite this error with a lower-level error previously registered.
%    idnuminner = registererror('BAR_INTERNALERROR', 'Internal error in bar');
%    idnum = registererror('FOO_INVALIDINPUT', 'Invalid input to function foo', idnuminner);
%
%  See Also: errorcode2string, errorcontainer

coder.inline('never')
switch nargin
    case 2
        idnum  = errorcontainer('register', messageid, errorstring);
    case 3
        idnum  = errorcontainer('register', messageid, errorstring, varargin{1});
end
end

Функция registererror принимает строку тега ошибки (обычно это имя функции, в которой произошла ошибка, за которым следует двоеточие, за которым следует некоторый идентификатор того, что произошло) и удобочитаемое сообщение об ошибке. Он регистрирует ошибку в глобальном хранилище и возвращает идентификатор ошибки в виде int32. Правило для моего кода состоит в том, что значение int32(0) означает отсутствие ошибки, а отрицательное значение является одним из этих зарегистрированных идентификаторов ошибки.

Значения, возвращаемые registererror, не являются уникальными для каждого состояния ошибки. Они являются дескрипторами ссылок в контейнер ошибок и могут использоваться для извлечения строки тега ошибки и удобочитаемых строк.

В моем коде использование registererror обычно происходит примерно так:

...
output = someFunctionThatShouldReturnZeroIfSuccessful(filename);
if (output ~= 0)
    result = registerror('MYFUNC:THESOMEFUNCTIONFAILED', ['The function that should return zero returned something else for filename: ', filename]);
    return;
end
...

В удобочитаемой строке я могу добавить строковые данные из фактического выполнения. В этом примере я добавил имя файла, который не выполнил мою теоретическую функцию. Я делаю возвращаемые значения всех своих функций int32, и все они могут исходить от вызова registererror.

Существует дополнительный необязательный вызов registererror. Одна вещь, которую удобно сделать, это создать стек ошибок. Иногда проблема вызывалась ошибкой в ​​более глубокой функции, и нам нужна полная трассировка стека. Это легко сделать, так как внутренние идентификаторы ошибок могут быть дополнительно переданы в registererror. Давайте посмотрим на пример этого:

...
result = someFunctionThatShouldReturnZeroIfSuccessful(filename);
if (result ~= 0)
    result = registerror('MYFUNC:THESOMEFUNCTIONFAILED', ['The function that should return zero returned something else for filename: ', filename], result);
    return;
end
...

В данном случае функция someFunction... вернула значение, которое было сгенерировано вызовом registererror, и это результирующее значение было добавлено в качестве третьего аргумента к вызову registererror здесь, который возвращает другое значение ошибки. Что мы можем сделать с этим значением?

Что ж, нам нужна третья функция, которую часто можно вызвать из кода, использующего нашу библиотеку. Эта функция называется errorcode2string, и, как следует из названия, она может принимать один из наших кодов ошибок и возвращать две строки, а также любой внутренний код ошибки, связанный с ошибкой. Вот errorcode2string:

function [errorid, errorstring, innercode] = errorcode2string(errorcode) %#codegen
%ERRORCODE2STRING Return strings given an error code
%  Given an error code returned by any of the library functions, will
%  return a string with human-readable information about the error.  The
%  error codes are, in some cases, dynamically generated, so the codes
%  themselves should not be used for programmatic flow control except that
%  a negative value in a return always indicates an error occurred.
%
%  Example:
%    [errorid, errorstring, innercode] = errorcode2string(errorcode);
%
%  - errorcode is an int32 value. 
%
%  - errorid is a string that is machine-readable and can be used to trap
%  specific error codes.
%  
%  - errorstring is the returned 1 by N string of the human-readable error
%  information.
%
%  - innercode is the int32 error code of an inner error message if any.
%  It is a negative value of an error code if present, 0 if this is the
%  innermost error.
%
%  See Also: registererror

[~, errorid, errorstring, innercode] = errorcontainer('lookup', errorcode);

end

Как вы можете видеть, эта функция на самом деле является просто оболочкой для контейнера ошибок, но это неплохо сделать, чтобы сохранить порядок для любого, кто вызывает библиотеку.

Пример:

Итак, давайте запустим быстрый пример. Представьте, что внутренняя функция не удалась, и функция registererror была вызвана следующим образом:

result = registererror('SOMEFUNC:SOMETASK', 'Something terrible happened.')
result = -2

Вызывающая функция замечает, что результатом является не 0, а отрицательный код ошибки, и сама выдает ошибку:

result = registererror('CALLINGFUNC:TOPTASK', 'Trying to do some high level thing failed.', result);
result = -3

Это значение -3 возвращается нашей библиотекой в ​​вызывающий код, поэтому он знает, что произошла ошибка просто потому, что результат был отрицательным. Затем он может вызвать errorcode2string, чтобы получить дополнительную информацию об ошибке.

[errorid, errorstring, innercode] = errorcode2string(int32(-3))
errorid = CALLINGFUNC:TOPTASK
errorstring = Trying to do some high level thing failed.
innercode = -2

Поскольку внутренний код по-прежнему отрицательный, если вызывающая программа склонна к этому, она может вызвать errorcode2string, чтобы узнать больше информации по функциональному стеку. На самом деле, именно так вы узнаете, что произошло с корневой ошибкой.

[errorid, errorstring, innercode] = errorcode2string(int32(-2))
errorid = SOMEFUNC:SOMETASK
errorstring = Something terrible happened.
innercode = 0

Теперь внутренний код равен 0, поэтому мы знаем, что можем остановиться.

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

person Tony    schedule 16.04.2015