Implementierung von Funktionen, Methoden und Aktionen
Themenpunkte:
- Kein „Call by value“ von großen Parametern in Funktionen/Methoden [++]
- Keine großen Variablen in Funktionen/Methoden deklarieren [++]
- Keine Aktionen verwenden [+]
- Alle Parameter einer Funktion/Methode intern verwenden [+]
- Rückgabewert einer Funktion/Methode nur an einer Stelle zuweisen [+]
- Zugriff auf Methoden so weit wie möglich einschränken [+]
- 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: