Implementierung von Funktionen, Methoden und Aktionen

Themenpunkte:

  1. Kein „Call by value“ von großen Parametern in Funktionen/Methoden [++]
  2. Keine großen Variablen in Funktionen/Methoden deklarieren [++]
  3. Keine Aktionen verwenden [+]
  4. Alle Parameter einer Funktion/Methode intern verwenden [+]
  5. Rückgabewert einer Funktion/Methode nur an einer Stelle zuweisen [+]
  6. Zugriff auf Methoden so weit wie möglich einschränken [+]
  7. Gruppierung von Parametern als Struktur [+]

Kein „Call by value“ von großen Parametern in Funktionen/Methoden

Eingangs- und Ausgangsparameter sowie der Rückgabewert einer Methode werden in den meisten Fällen beim Aufruf der Funktion/Methode kopiert, das ist der „Call by value“. Bei Parametern, die viel Speicher belegen, benötigt dieser Kopierschritt mehr Zeit. Um effizienten Programmcode zu schreiben, sollten große Parameter mittels „Call by reference“ übergeben werden. So wird nur ein Zeiger gesetzt. Hierzu gibt es einige verschiedene Möglichkeiten, welche im Beispiel aufgegriffen werden. Beachten Sie hierbei, dass manche Möglichkeiten auch schreibenden Zugriff auf die übergebenen Eingangsparameter erlauben bzw. ermöglichen.

Siehe auch „Übergabe von großen Strings“.

Negatives Beispiel:

METHOD SampleMethod_neg : ST_DataCollection    // return value with call by value
VAR_INPUT
    aSensor1Data     : ARRAY[1..100] OF LREAL; // input with call by value
    aSensor2Data     : ARRAY[1..200] OF LREAL; // input with call by value
    aSensor3Data     : ARRAY[1..300] OF LREAL; // input with call by value
    aSensor4Data     : ARRAY[1..400] OF LREAL; // input with call by value
END_VAR
VAR_OUTPUT
    aOutputData      : ARRAY[1..500] OF LREAL; // output with call by value
END_VAR

Positives Beispiel:

METHOD SampleMethod_pos : HRESULT
VAR_INPUT
    aSensor1Data     : REFERENCE TO ARRAY[1..100] OF LREAL; // input with call by reference (write access possible)
    pSensor2Data     : POINTER TO ARRAY[1..200] OF LREAL;   // input buffer, similar to call by reference (write access possible)
    nSensor2DataSize : UDINT;                               // size in bytes of buffer to ensure the correct buffer size
    aOutputData      : REFERENCE TO ARRAY[1..500] OF LREAL; // output with call by reference
    stDataCollection : REFERENCE TO ST_DataCollection;      // output with call by reference
END_VAR
VAR_IN_OUT CONSTANT
    aSensor3Data     : ARRAY[1..300] OF LREAL;              // input with call by reference
END_VAR
VAR_IN_OUT
    aSensor4Data     : ARRAY[1..400] OF LREAL;              // input with call by reference (write access possible)
END_VAR

Keine großen Variablen in Funktionen/Methoden deklarieren

Bei Variablendeklarationen innerhalb einer Funktion/Methode sollten Sie eine große Anzahl sowie eine große Datengröße vermeiden. Solche Variablendeklarationen werden aus dem Stack-Speicher genommen. Werden Funktionen/Methoden verschachtelt aufgerufen, so addiert sich die benötigte Speichermenge auf dem Stack. Es ist nicht beliebig viel Stack-Speicher verfügbar und „Stack Overflow“-Situationen sollten Sie im Vorhinein vermeiden.

Die maximale Größe des Stack-Speichers wird im TwinCAT-Projekt unterhalb vom Knoten-System im Knoten Real-Time angegeben. Oft sind dies 64 KB. Deshalb empfiehlt sich, die gesamte deklarierte Datenmenge in einer Funktion/Methode möglichst klein, beispielsweise unter 1 KB, zu halten. Achten Sie daher besonders bei STRING-Typen und Arrays beliebiger Typen auf möglichst geringe Längen.

Alternativ können Variablen innerhalb der Funktionsbausteininstanz deklariert werden. Beachten Sie hierbei, dass diese Variablen dauerhaft zur Verfügung stehen und nicht bei jedem Aufruf der Methode initialisiert werden.

Für Parameter einer Funktion/Methode gilt das gleiche. Siehe hierzu „Kein Call-By-Value von großen Parametern in Funktionen/Methoden“.

Negatives Beispiel:

FUNCTION_BLOCK FB_Sample_neg
VAR
END_VAR
METHOD SampleMethod_neg : LREAL
VAR
    fSum     : LREAL;
    nCnt     : UINT;
    aLogList : ARRAY[1..50] OF STRING(1023); // locally declared in method leads to stack allocation
END_VAR

Positives Beispiel:

FUNCTION_BLOCK FB_Sample_pos
VAR
    aLogList : ARRAY[1..50] OF STRING(1023); // declared as member of the FB instance
END_VAR
METHOD SampleMethod_pos : LREAL
VAR
    fSum     : LREAL;
    nCnt     : UINT;
END_VAR

Positives Beispiel (Alternative):

FUNCTION_BLOCK FB_Sample2_pos
VAR
END_VAR
METHOD SampleMethod2_pos : LREAL
VAR
    fSum     : LREAL;
    nCnt     : UINT;
END_VAR
VAR_INST
    aLogList : ARRAY[1..50] OF STRING(1023); // declared as member of the FB instance
END_VAR

Keine Aktionen verwenden

Ein Programm oder ein Funktionsbaustein sollte keine Aktionen beinhalten. Implementieren Sie stattdessen Methoden. Diese können je nach Verwendung als PUBLIC, PRIVATE oder PROTECTED definiert werden.

Alle Parameter einer Funktion/Methode intern verwenden

Alle Parameter einer Funktion oder einer Methode sollten intern auch verwendet werden.

Negatives Beispiel:

FUNCTION F_Sample_neg : INT
VAR_INPUT 
    nA       : INT;          // Variable a in term y = a*a
    nB       : INT;          // NON COMPLIANT: nB will not be used 
END_VAR
F_Sample_neg := nA * nA;

Positives Beispiel:

FUNCTION F_Sample_pos : INT  // COMPLIANT: no unused parameter
VAR_INPUT 
    nA       : INT;          // Variable a in term y = a*a
END_VAR
F_Sample_pos := nA * nA;

Rückgabewert einer Funktion/Methode nur an einer Stelle zuweisen

Den Rückgabewert einer Funktion/Methode sollten Sie nur an einer Stelle zuweisen. Eine Ausnahme dieser Regel gilt, wenn die Zuweisungen in verschiedenen Zweigen einer IF- bzw. CASE-Anweisung durchgeführt werden.

Beispiel 1:

Negatives Beispiel:

FUNCTION F_Sample_neg_1 : LREAL
VAR
    fOnePlusHalfEpsilon  : LREAL;          // Auxiliary variable to calculate and validate the machine epsilon
END_VAR
// NON COMPLIANT: F_Sample_neg_1 is assigned or used more than once
F_Sample_neg_1            := 1.0;
 
REPEAT
    F_Sample_neg_1       := 0.5 * F_Sample_neg_1;        // NON COMPLIANT: see above
    fOnePlusHalfEpsilon  := 1.0 + 0.5 * F_Sample_neg_1;  // NON COMPLIANT: see above
UNTIL fOnePlusHalfEpsilon <= 1.0
END_REPEAT;

Positives Beispiel:

FUNCTION F_Sample_pos_1 : LREAL
VAR
    fOnePlusHalfEpsilon  : LREAL;          // Auxiliary variable to calculate and validate the machine epsilon
    fResult              : LREAL := 1.0;   // Temporary variable to 
END_VAR
REPEAT
    fResult              := 0.5 * fResult; 
    fOnePlusHalfEpsilon  := 1.0 + 0.5 * fResult;
UNTIL fOnePlusHalfEpsilon <= 1.0
END_REPEAT;
 
F_Sample_pos_1 := fResult;                 // COMPLIANT: F_Sample_pos_1 is only assigned here

Beispiel 2:

Allgemeine Programmelemente für dieses Beispiel:

// Enumeration for sample 2
{attribute 'qualified_only'}
{attribute 'strict'}
TYPE E_SampleStatus :
(
    Ok,                        // Used when the status is OK
    Warning,                   // Used when a warning occurs
    Error                      // Used when an error occurs
);
END_TYPE
// GVL_Sample - global variable list for sample 2
VAR_GLOBAL CONSTANT
    cLimitMin    : INT := 10;  // Minimal limit to validate a sample value
    cLimitMax    : INT := 20;  // Maximal limit to validate a sample value
    cTolerance   : INT := 2;   // Upper and lower tolerance to validate a sample value
END_VAR
// Returns a status variable (ok, warning, error) for a measure value, using a constant range of values defined on a GVL
FUNCTION F_Sample : E_SampleStatus
VAR_INPUT
    nValue       : INT;        // Sample value that is validated in respect of a range of values
END_VAR

Negatives Beispiel:

// NON COMPLIANT: F_Sample is assigned within different IF-statements. Multiple write access on return value cannot be excluded. 
IF (nValue >= cLimitMin) AND (nValue <= cLimitMax) THEN
    F_Sample := E_SampleStatus.Ok;
END_IF

IF (nValue >= (cLimitMin - cTolerance))
AND (nValue <= (cLimitMax + cTolerance)) THEN
    F_Sample := E_SampleStatus.Warning;
END_IF

IF (nValue < (cLimitMin - cTolerance))
OR (nValue > (cLimitMax + cTolerance))
    F_Sample := E_SampleStatus.Error;
END_IF

Positives Beispiel:

// COMPLIANT: F_Sample is only assigned within one IF-statement. No multiple write access on return value occurs.
IF (nValue >= cLimitMin) AND (nValue <= cLimitMax) THEN
    F_Sample := E_SampleStatus.Ok;
ELSIF (nValue >= (cLimitMin - cTolerance))
  AND (nValue <= (cLimitMax + cTolerance)) THEN
    F_Sample := E_SampleStatus.Warning;
ELSE
    F_Sample := E_SampleStatus.Error;
END_IF

Zugriff auf Methoden so weit wie möglich einschränken

Der Zugriff auf Methoden sollte mit Hilfe der Zugriffsmodifizierer PRIVATE oder PROTECTED so weit wie möglich eingeschränkt werden. Methoden, die nur für den internen Aufruf vorgesehen sind, können somit nicht von außen aufgerufen werden. Dies trägt dazu bei, dass ein Funktionsbaustein eher auf die Weise verwendet wird, wie es seinem vorgesehenen Verwendungszweck entspricht. Die Kapselung von Methoden führt daher zu sichererem Code. Zudem sind nachträgliche Änderungen an internen Methoden einfacher möglich, wenn diese, wie vorgesehen, vom Anwender nicht von außen aufgerufen wurden.

Gruppierung von Parametern als Struktur

Müssen Sie viele Parameter beim Aufruf angeben, so bietet es sich an, diese in einer oder mehreren Strukturen zu gruppieren.

Dies hat den Vorteil, dass innerhalb der Strukturdefinition Default-Werte hinterlegt sind. Mehrere Konstanten des Strukturdatentyps können wiederum verschiedene Default-Werte für unterschiedliche Anwendungsfälle bereitstellen.

Der Aufruf kann effizient umgesetzt werden, indem die Struktur mittels „Call by reference“ übergeben wird. So werden viele einzelne „Call by value“-Übergaben vermieden. Sehen Sie dazu auch den Themenpunkt Kein „Call by value“ von großen Parametern in Funktionen/Methoden.

 

Siehe auch: