Overview
Determining the length of an ARRAY at runtime is a recurring requirement when writing SCL code for the SIMATIC S7-1200. Static dimensioning (ARRAY[1..10]) fixes the size at declaration time, but production code frequently needs the actual element count to iterate, validate, log, or build generic function blocks that accept arrays of any length. Unlike high-level languages (JavaScript arr.length, C++ sizeof(arr)/sizeof(arr[0]), Python len(arr)), SCL does not expose a .Length property on every ARRAY variable. Engineers must use one of several instructions from the basic/extended PLC program elements library: CountOfElements, SizeOf, LOWER_BOUND, UPPER_BOUND, SERIALIZE, or ATTR_DB. Availability depends on the CPU firmware and the TIA Portal version. This article walks through every working method, documents the firmware/portal constraints, and provides copy-paste-ready SCL snippets for the S7-1200.
The discussion is grounded in the SIMATIC S7-1200 programmable controller system manual and the SCL programming manual available on the Siemens Industry Online Support portal at support.industry.siemens.com. Whenever an instruction behaves differently across firmware revisions the relevant firmware cut-off is documented; whenever the behaviour diverges between the real CPU and PLCSIM the simulator revision is called out.
Prerequisites
- SIMATIC S7-1200 CPU (any family member; FW 4.2 or newer recommended for the cleanest solutions).
- TIA Portal V13 SP1 / V14 / V14 SP1 / V15 / V15.1 / V16 / V17 - version matched to the CPU firmware.
- S7-1200 system manual - download via the Siemens Industry Online Support portal entry "SIMATIC S7-1200 Programmable Controller" (entry ID 109766459 is the canonical S7-1200 system manual page).
- SCL programming manual "SIMATIC S7 SCL" - 03/2017 edition or later for the modern VARIANT-based APIs.
- Optional: PLCSIM V15.1 or later for offline simulation of
ARRAY[*]. - Optional: Online diagnostic access to the CPU (Engineering Station > Online & Diagnostics) to verify firmware and diagnostic-buffer entries.
The Anatomy of an ARRAY Variable in SCL
An ARRAY in SCL has three properties engineers usually need to recover at runtime:
- The lower index (e.g.
0or1or-100). - The upper index (e.g.
9or300or65535). - The element count, equal to
upper - lower + 1.
For multi-dimensional arrays, each dimension has its own lower and upper index. The total element count is the product of every dimension's length. The SCL DIM_COUNT_OF helper returns the number of dimensions of a static array expression.
ARRAY declarations in TIA Portal come in two flavours:
| Syntax | Description | First FW on S7-1200 |
|---|---|---|
ARRAY[lo..hi] OF T |
Static, fixed at compile time | All firmware |
ARRAY[*] OF T |
Variable-length, the actual bounds come from the assigned block | FW 4.2 (silicon 1AG40 / 2AG40 / 3AG40) |
Method 1 - CountOfElements with VARIANT
CountOfElements was introduced on the S7-1500 platform and back-ported to selected S7-1200 CPUs starting with FW 4.2 on the 1AG40 / 2AG40 / 3AG40 silicon. The instruction accepts a VARIANT input and returns the element count as DInt. It counts one level of the array by default. When the input is a multi-dimensional array the result is the product of every dimension's length. For an array of STRUCT with three fields each, CountOfElements reports the number of STRUCT elements, not the number of scalar fields.
SCL skeleton for CountOfElements
// FB_GetArraySize
FUNCTION_BLOCK "FB_GetArraySize"
VAR_IN_OUT
vArray : Variant; // caller wires any array here
END_VAR
VAR_OUTPUT
size : DInt;
END_VAR
BEGIN
// The instruction resolves the runtime type and returns element count
#size := CountOfElements(variant := #vArray);
END_FUNCTION_BLOCK
Calling the FB from OB1
// OB1 - cyclic main
"FB_GetArraySize_DB"(vArray := "DataDB".MyArray);
"DB_Result".detected_count := "FB_GetArraySize_DB".size;
CountOfElements signature requires VARIANT because the block is generic - the same block should work for ARRAY OF INT, ARRAY OF REAL, ARRAY OF STRUCT and so on. Passing a typed array directly to a typed IN_OUT causes an implicit conversion error at compile time. The wrapper pattern above is the documented Siemens reference implementation.Firmware coverage
| CPU family | First FW supporting CountOfElements | Notes |
|---|---|---|
| S7-1500 | FW 1.7 (all current FW) | Full support, including multi-level |
| S7-1200 FW 4.0 / 4.1 | Not supported | Instruction greyed out in TIA Portal |
| S7-1200 FW 4.2 | Supported | Requires 1AG40 / 2AG40 / 3AG40 silicon |
| S7-1200 FW 4.3 / 4.4 / 4.5 | Supported | All variants |
| ET 200SP CPU | FW 2.x | Supported |
| S7-300 / S7-400 | Not supported | Use ATTR_DB / DB length arithmetic instead |
Output interpretation
CountOfElements returns the number of top-level array elements. For ARRAY[0..9] OF INT it returns 10. For ARRAY[0..2, 0..4] OF REAL it returns 15 (3 x 5). For ARRAY[0..9] OF STRUCT {a:INT; b:REAL;} it returns 10 (10 struct elements, not 20 scalars). This semantic is consistent across S7-1500 and S7-1200.
Method 2 - SizeOf and Arithmetic
SizeOf returns the memory footprint in bytes of a variable or data type. With a VARIANT input, it reports the byte length of whatever structure is wired in. Divided by the element byte size, the element count can be derived. SizeOf works on every S7-1200 FW from 4.0 onward and on S7-1500 from the first release.
FC returning raw byte count
FUNCTION "FC_GetSizeBytes" : UDInt
VAR_IN_OUT
vAny : Variant;
END_VAR
BEGIN
FC_GetSizeBytes := SizeOf(variant := #vAny);
END_FUNCTION
FC returning element count by arithmetic
FUNCTION "FC_GetElementCount" : UDInt
VAR_IN_OUT
vAny : Variant;
END_VAR
VAR
totalBytes : UDInt;
elemBytes : UDInt;
END_VAR
BEGIN
totalBytes := SizeOf(variant := #vAny);
// Use a known element of the same type to compute per-element size
elemBytes := SIZEOF(INT); // for ARRAY OF INT
// elemBytes := SIZEOF(REAL); // for ARRAY OF REAL
// elemBytes := SIZEOF(BYTE); // for ARRAY OF BYTE
IF elemBytes > 0 THEN
FC_GetElementCount := totalBytes / elemBytes;
ELSE
FC_GetElementCount := 0;
END_IF;
END_FUNCTION
Edge cases
If the array contains STRING, WSTRING, STRUCT with variable-length members, or ARRAY OF BYTE used as a buffer, SizeOf returns the static declaration length, not the populated length. For populated-byte counting use SERIALIZE (Method 4). For true STRING content length use the LEN instruction on each element.
SizeOf also returns the declaration footprint for ARRAY OF BOOL with packed bits. TIA Portal packs booleans into the lower bit-count of a byte; the formula byteCount * 8 yields the bit-padded count, not always the number of named indices - check the declaration explicitly when debugging.
Method 3 - LOWER_BOUND and UPPER_BOUND
For a directly-typed ARRAY parameter (not a VARIANT), the LOWER_BOUND and UPPER_BOUND instructions return the lower and upper index of any given dimension. They accept the array name, the dimension number, and return DInt. The element count is then UPPER_BOUND - LOWER_BOUND + 1. These instructions require the array type to be visible at compile time, so they cannot resolve dynamic ARRAY[*], but they cover the common static case without a wrapper.
One-dimensional example
FUNCTION "FC_Bounds" : DInt
VAR_IN_OUT
arr : ARRAY[0..299] OF INT;
END_VAR
VAR
lo : DInt;
hi : DInt;
dim : Int := 1;
END_VAR
BEGIN
lo := LOWER_BOUND(ARR := #arr, DIM := #dim);
hi := UPPER_BOUND(ARR := #arr, DIM := #dim);
FC_Bounds := hi - lo + 1;
END_FUNCTION
Multi-dimensional example
FUNCTION "FC_BoundsMulti" : DInt
VAR_IN_OUT
arr : ARRAY[0..2, 0..4] OF REAL;
END_VAR
VAR
lo : DInt;
hi : DInt;
dim : Int;
prod : DInt := 1;
END_VAR
BEGIN
FOR dim := 1 TO DIM_COUNT_OF(#arr) DO
lo := LOWER_BOUND(ARR := #arr, DIM := #dim);
hi := UPPER_BOUND(ARR := #arr, DIM := #dim);
prod := prod * (hi - lo + 1);
END_FOR;
FC_BoundsMulti := prod;
END_FUNCTION
LOWER_BOUND / UPPER_BOUND do not work on ARRAY[*]. For dynamic arrays fall back to CountOfElements (Method 1) or SERIALIZE (Method 4).Method 4 - SERIALIZE Instruction
SERIALIZE streams the runtime data of a structure or array into a destination ARRAY OF BYTE buffer. The RET_VAL reports the number of bytes actually written (or a negative value if the destination is too small). For arrays of REAL, INT, or other fixed-width primitives, dividing the byte count by the element size recovers the element count. For arrays of STRING or WSTRING, SERIALIZE writes only the populated bytes, giving the true payload length.
SCL example
FUNCTION "FC_SerializeCount" : DInt
VAR_IN_OUT
arr : ARRAY[*] OF REAL; // requires FW 4.2+
END_VAR
VAR_TEMP
buf : ARRAY[0..16383] OF BYTE;
pos : DInt;
END_VAR
BEGIN
pos := SERIALIZE( SRC_VARIANT := #arr,
DEST_ARRAY := #buf,
POS := 0 );
IF pos > 0 THEN
FC_SerializeCount := pos / SIZEOF(REAL);
ELSE
FC_SerializeCount := -1; // buffer too small
END_IF;
END_FUNCTION
Restrictions
- S7-1200 only on FW 4.2 or newer.
- Requires the
ARRAY[*](variable-length array) declaration syntax; the staticARRAY[1..N]form on older firmware is rejected by the compiler. - Destination buffer must be large enough; otherwise
RET_VALreturns a negative value indicating the shortfall. -
SERIALIZEcopies the full runtime data; performance is O(n) over the array size. Avoid calling it inside fast cyclic OB1 if the array is large.
Method 5 - ATTR_DB for Data Block Length
When the question is "how big is this DB?" rather than "how big is this array?", the legacy ATTR_DB instruction returns the maximum byte count of the addressed DB. It is the canonical answer for IEC-style counters walking an entire DB with PEEK / POKE byte-by-byte. ATTR_DB was originally introduced for S7-300/400, ported to S7-1500, and supported on S7-1200 starting with TIA Portal V14 SP1 on FW 4.2.
SCL example
FUNCTION "FC_DBLen" : UDInt
VAR_INPUT
dbNumber : Int;
END_VAR
VAR_TEMP
att : STRUCT
Attrib : Byte;
Number : Byte;
Length : UDInt;
END_STRUCT;
END_VAR
BEGIN
ATTR_DB(REQ := TRUE,
DBNumber := #dbNumber,
ATTR_DB := #att);
FC_DBLen := att.Length;
END_FUNCTION
Interpretation
ATTR_DB returns the byte count of the entire DB including all structures, scalar fields, and arrays. To isolate a single array inside the DB, divide by the element size or use UPPER_BOUND on the array directly. With optimised blocks (default since TIA Portal V14) the byte layout is not contiguous and ATTR_DB returns the logical size, not the physical on-target offset - use UPPER_BOUND on the array symbol rather than arithmetic against ATTR_DB.
Method 6 - Array Bounds Error Technique with GET_ERROR
For older firmware where none of the modern instructions are usable (S7-1200 FW 4.0 / 4.1, TIA Portal V13), one proven technique is to deliberately index past the upper bound, catch the generated range error with GET_ERROR, and decrement the index by one. The technique is robust but intrusive - it relies on triggering a non-fatal error and inspecting it.
SCL example using PEEK
FUNCTION_BLOCK "FB_ScanUpperBound"
VAR_INPUT
startGuess : DInt := 1;
maxGuess : DInt := 32767;
END_VAR
VAR_OUTPUT
upperIdx : DInt;
ok : Bool;
END_VAR
VAR_TEMP
i : DInt;
val : Int;
info : Struct
flags : Byte;
opCode : Word;
END_STRUCT;
END_VAR
BEGIN
upperIdx := -1;
ok := FALSE;
FOR i := #startGuess TO #maxGuess DO
// PEEK avoids touching real application data
val := PEEK(area := 16#84, dbNumber := 100, byteOffset := DINT_TO_INT(i * 2));
// The above access may generate a range error on the final iteration
IF NOT ENO THEN
GET_ERROR(#info);
upperIdx := i - 1; // last valid index
ok := TRUE;
EXIT;
END_IF;
END_FOR;
END_FUNCTION_BLOCK
ENO after each array read. Without local error handling, the CPU will fall into STOP on the first over-index and the scan cannot recover.When to use this technique
This is a fallback for legacy hardware that cannot be upgraded. It is not recommended for new code. Performance is O(n) over the maximum possible size, so on an array that might be 32 000 elements the scan takes hundreds of milliseconds and is unsuitable for fast cyclic contexts. Prefer CountOfElements on FW 4.2+ CPUs.
Method 7 - Constants and Parameterised Array Limits
When the array size is known at compile time but used in multiple FB/FCs, declare it once as a global constant and reuse it. This is the lowest-tech answer and the right answer when the size is fixed by the application.
Constant declaration in a global DB
// In a global DB or constant block
CONST
MY_ARRAY_SIZE : Int := 300;
MY_ARRAY_LO : Int := 1;
MY_ARRAY_HI : Int := 300;
END_CONST
Array using the constant
DATA_BLOCK "DataDB"
STRUCT
MyArray : ARRAY[MY_ARRAY_LO..MY_ARRAY_HI] OF INT;
// or equivalently
MyArrayAlt : ARRAY[1..MY_ARRAY_SIZE] OF INT;
END_STRUCT;
END_DATA_BLOCK
Reusing the constant inside any FB/FC
FUNCTION "FC_AnyUse" : DInt
BEGIN
FC_AnyUse := MY_ARRAY_SIZE;
END_FUNCTION
Combine constants with UPPER_BOUND so that changing the constant in one place is sufficient. This pattern survives TIA Portal upgrades and is independent of CPU firmware - it is the recommended baseline for any new project.
Comparison Matrix
| Method | Min S7-1200 FW | Min TIA Portal | Returns | VARIANT OK? | Performance | Edge cases |
|---|---|---|---|---|---|---|
| CountOfElements | 4.2 (1AG40 silicon) | V14 | Element count | Yes | Constant time | Top-level only by default |
| SizeOf / divisor | 4.0 | V13 | Byte count | Yes | Constant time | Static-length only; STRING mis-reports |
| LOWER_BOUND / UPPER_BOUND | 4.0 | V13 | Index values | No (typed) | Constant time | Compile-time type needed |
| SERIALIZE | 4.2 | V14 | Bytes written | Yes | O(n) copy | Requires buffer; populates STRING |
| ATTR_DB | 4.2 (with V14 SP1) | V14 SP1 | DB byte count | DB only | Constant time | DB-level, not field-level |
| Bounds-error scan | 4.0 | V13 | Last valid index | No | O(n) iteration | Triggers error; needs GET_ERROR |
| Compile-time constant | All | All | Fixed value | N/A | Compile time | Only valid if size is fixed |
Decision Tree
- Is the array size fixed and known at design time? Use a global constant (Method 7) plus
UPPER_BOUND. - Is the CPU firmware 4.2+ on a 1AG40 / 2AG40 / 3AG40 silicon? Use
CountOfElementswithVARIANT(Method 1). - Is the array passed as a typed parameter? Use
LOWER_BOUNDandUPPER_BOUND(Method 3). - Do you need the populated byte count for
STRINGorWSTRING? UseSERIALIZE(Method 4). - Is the question about total DB size rather than a single array? Use
ATTR_DB(Method 5). - Are you stuck on FW 4.0 / 4.1 with no upgrade path? Use the bounds-error scan (Method 6) - or, preferably, schedule the upgrade.
Complete Working FB
The following FB consolidates the recommended approaches into a single block. Use the input mode selector to switch between methods without recompiling.
FUNCTION_BLOCK "FB_ArraySize"
VAR_IN_OUT
vArray : Variant;
END_VAR
VAR_INPUT
mode : Int; // 1 = CountOfElements, 2 = SizeOf/elem, 3 = SERIALIZE
END_VAR
VAR_OUTPUT
size : DInt;
ok : Bool;
END_VAR
VAR_TEMP
totalBytes : UDInt;
elemBytes : UDInt;
buf : ARRAY[0..16383] OF BYTE;
pos : DInt;
END_VAR
BEGIN
size := -1;
ok := FALSE;
CASE #mode OF
1:
// Method 1: CountOfElements
#size := CountOfElements(variant := #vArray);
#ok := (#size >= 0);
2:
// Method 2: SizeOf / element size
#totalBytes := SizeOf(variant := #vArray);
#elemBytes := 2; // adjust per element type, e.g. SIZEOF(INT)
IF #elemBytes > 0 THEN
#size := DINT_TO_DINT(UDINT_TO_DINT(#totalBytes) / UDINT_TO_DINT(#elemBytes));
#ok := TRUE;
END_IF;
3:
// Method 4: SERIALIZE
#pos := SERIALIZE(SRC_VARIANT := #vArray,
DEST_ARRAY := #buf,
POS := 0);
IF #pos > 0 THEN
#size := #pos / 2; // assume 2-byte element; adjust per element type
#ok := TRUE;
END_IF;
ELSE
#size := -1;
#ok := FALSE;
END_CASE;
END_FUNCTION_BLOCK
Verification Procedure
Before deploying any of the above techniques to production, verify with the following checklist:
- Confirm the project uses a TIA Portal version at least as new as the firmware's release. Mismatched versions compile in TIA Portal but fail to download.
- Add a watch table on the array's declared length, the result
DInt, and the running CPU firmware version. Read online to confirm FW. - Set a breakpoint inside the SCL block that returns the size. Run the program in single-step mode and confirm the value matches the array declaration.
- If using PLCSIM, use V15.1 or later - earlier PLCSIM does not simulate
ARRAY[*]and silently returns zero. - If using the bounds-error technique, deliberately trigger an over-index and confirm the CPU stays in RUN with the error captured in the diagnostic buffer (CPU > Online & Diagnostics > Diagnostic buffer).
- Reset the CPU to factory settings before final acceptance to confirm no stale array contents bias the test.
- Check the diagnostic buffer for OB1 execution errors. A pattern of "Range length error" events indicates the bounds-error technique is active and may need tuning.
- For S7-1200 FW 4.2 on 1AG40 silicon: confirm the order code ends in
1AG40,2AG40, or3AG40. Older silicon (e.g.1AY10) does not supportARRAY[*]regardless of firmware.
Troubleshooting Matrix
| Symptom | Likely Cause | Fix |
|---|---|---|
| CountOfElements instruction greyed out | CPU FW older than 4.2 or wrong CPU variant | Upgrade to FW 4.2; switch to SizeOf / UPPER_BOUND |
| Compile error "VARIANT cannot be passed by reference" | Array wired to a typed IN_OUT
|
Change the parameter type to VARIANT
|
| SERIALIZE returns -1 | Destination buffer too small | Increase ARRAY OF BYTE size or check RET_VAL for the shortfall |
| ATTR_DB returns invalid length | DB number does not exist or FW too old | Verify DB is downloaded; upgrade FW to 4.2 + TIA V14 SP1 |
| Bounds-error scan puts CPU into STOP | Local error handling not enabled | FB properties > "Local error handling" = enabled; check ENO after each access |
| PLCSIM returns 0 for array length | PLCSIM too old for ARRAY[*]
|
Use PLCSIM V15.1 or later |
| SizeOf returns 0 for an empty ARRAY | Compiler optimised the array away | Mark the block as non-optimisable or add a runtime touch |
| UPPER_BOUND returns incorrect dimension | Dim parameter set to wrong dimension number | Iterate DIM_COUNT_OF from 1 |
| Online value differs from offline | Different firmware between offline and online project | Re-download project with matched firmware |
| CountOfElements returns -1 | VARIANT pointer invalid (source block deleted) | Recompile and re-download; refresh VARIANT pointer |
Edge Cases and Field-Notes
Multi-dimensional arrays. CountOfElements returns the product of every dimension's length when the input is a multi-dimensional array of a single type. For mixed structures (arrays of structs of varying widths), use SERIALIZE with a custom parser or maintain a constant for each sub-dimension.
VARIANT lifetime. The VARIANT pointer is invalidated when the source block is deleted or its number changes. Always re-fetch the variant after any structural recompile. If the source DB is renamed the variant assignment in OB1 must be rewired.
Optimised block access. Optimised blocks (default since TIA Portal V14) remove the absolute addresses that PEEK / POKE rely on. The bounds-error technique requires unoptimised access for the array or a different probe function. To disable optimisation: right-click the DB > Properties > Attributes > "Optimised block access" > uncheck.
STRING length within arrays. SizeOf reports the declared length (e.g. 254 bytes for STRING[254]) even when the actual content is shorter. Use LEN on each element for true content length. SERIALIZE is the only instruction that respects the runtime payload for arrays of STRING.
Cross-block reuse. When an array is passed across FB/FC boundaries in a multi-instance scenario, prefer UPPER_BOUND inside the FB to recover the dimension count. This avoids the runtime cost of CountOfElements on every call and works on every firmware.
BOOL packing. TIA Portal packs BOOL arrays into the lower bits of bytes. An ARRAY[0..15] OF BOOL consumes 2 bytes, not 16. Divide SizeOf by 1 and use UPPER_BOUND for the index count; do not multiply.
Siemens engineering tip. When debugging an unknown third-party block that consumes an array, place the array inside a wrapper DB with a sentinel at index 0. Write a known pattern, then read back to confirm the wrapper is wired correctly before chasing the size call.
Performance Comparison
| Method | Typical execution time on S7-1200 FW 4.4 | Notes |
|---|---|---|
| CountOfElements | < 1 microsecond | Pointer chase + table lookup |
| SizeOf | < 1 microsecond | Compile-time constant for static arrays |
| LOWER_BOUND / UPPER_BOUND | < 1 microsecond | Compile-time constant for static arrays |
| SERIALIZE | ~1 microsecond per element + overhead | Memory copy |
| ATTR_DB | 10-50 microseconds | System call |
| Bounds-error scan | 10 microseconds per iteration | O(n) over max possible size |
Numbers are empirical ballparks; exact timing depends on CPU variant, scan cycle load, and memory layout. For arrays under 1000 elements, the absolute difference is negligible and the choice should be driven by clarity and firmware availability.
Documentation Pointers
Engineers should consult the following Siemens-authored documents for definitive behaviour:
- SIMATIC S7-1200 Programmable Controller - System Manual (latest edition; entry ID 109766459 on the Siemens Industry Online Support portal).
- SIMATIC S7 SCL - Programming Manual (latest edition).
- TIA Portal Help (F1 in the TIA Portal editor) for instruction reference under "Basic instructions > Extended instructions" and "Basic instructions > Program control operations".
- Siemens Industry Online Support portal at support.industry.siemens.com for the S7-1200 firmware release notes - search "S7-1200 firmware" for the canonical FW matrix.
FAQ
What is the simplest way to get the size of an ARRAY in SCL on S7-1200?
Declare a global CONST for the size and reference it from every block that needs the length. If you need a runtime answer, use UPPER_BOUND(arr) - LOWER_BOUND(arr) + 1, which works on every S7-1200 firmware from 4.0 onward with TIA Portal V13.
Is CountOfElements available on S7-1200 FW 4.1?
No. CountOfElements requires FW 4.2 on the 1AG40 / 2AG40 / 3AG40 silicon. On older firmware the instruction is greyed out in TIA Portal or rejected on download. Use SizeOf divided by the element byte length instead.
Why does PLCSIM return 0 for an ARRAY[*] length?
PLCSIM versions older than V15.1 do not simulate the variable-length array syntax. Upgrade PLCSIM to V15.1 or later, or test the same code on the physical CPU which always honours ARRAY[*] from FW 4.2.
Can SizeOf give me the populated length of a STRING array?
No. SizeOf returns the declared byte length of each element (for example 254 bytes for STRING[254]) even when the actual content is shorter. To read the populated length use the LEN instruction on each element, or use SERIALIZE to inspect the actual bytes written.
Does ATTR_DB tell me the array size or only the total DB size?
ATTR_DB returns the byte count of the entire DB, including all structures, scalar fields, and arrays. To isolate a single array inside the DB, divide the byte count by the element size or use UPPER_BOUND on the array symbol directly. With optimised blocks the byte layout is logical, not physical, so prefer UPPER_BOUND when possible.
How do I keep my CPU from going into STOP when probing array bounds?
Enable "Local error handling" on the FB or FC properties in TIA Portal, then check ENO after each array access. With local error handling enabled the over-index produces a non-fatal error that GET_ERROR can inspect; without it the CPU falls into STOP on the first violation.