A fixed-format D-spec data structure groups one or more subfield D-spec lines
under a DS header line. The header carries the DS name in columns 7-21 with
DS in the def-type field (columns 24-25); the subfield lines that follow have
a blank def-type and describe the layout (length, data type, decimals, keywords)
the same way standalone D-specs do.
When the data-type column (column 40) is left blank, the type is inferred from
the decimal-positions column (columns 41-42). A blank data type with decimal
positions is numeric, and the default numeric type depends on where the field is
defined: a DS subfield defaults to zoned (so 6 0 is 6S 0 and 9 2
is 9S 2), while a standalone field or prototype parameter defaults to
packed (so 6 0 is 6P 0 and 9 2 is 9P 2). This holds whether the
subfield is written in length notation or with absolute FROM/TO byte positions.
A blank data type without decimal positions is character in every case.
The DS terminates implicitly when:
- another D-spec line declares a new non-subfield entity (a non-blank def-type
such as
S,DS,C,PR,PI), or - a different spec type appears (H, F, C, I, O), or
- end-of-file is reached.
Example: a 6-byte character buffer whose first two bytes are also addressable as a 2-byte slice via the OVERLAY keyword.
D DateDS DS
D DateChr 6A
D MonChr 2A OVERLAY(DateChr)
D DayChr 2A OVERLAY(DateChr:3)
OVERLAY Keyword
OVERLAY(parent) aligns the subfield with the first byte of parent's storage;
OVERLAY(parent:N) starts at byte N (1-indexed) within the parent. Reads
and writes through any overlaid subfield are visible through the parent and
every sibling overlay. Subfields without OVERLAY are laid out sequentially in
declaration order and occupy their own bytes.
Subfields that overlap - through OVERLAY, POS(n), or from/to positions - share storage: a write through one is visible through every other view of those
bytes. Subfields that do not overlap occupy their own bytes.
OVERLAY(parent:*NEXT) positions the subfield at the next available byte
within the parent - one byte past the highest position used so far by the
subfields overlaying that parent. Because it tracks the highest position used
by all prior overlays of the parent (not merely the immediately preceding
subfield), an earlier-but-lower overlay declared in between does not pull the
next *NEXT subfield back. This lets a run of overlaid subfields be packed
end-to-end without hard-coding byte positions.
Character Subfields Overlaying Numeric Ones
A data structure's storage is a single byte buffer under one code page - the
job's, or the field's own where a CCSID keyword names one. A character
subfield laid over a numeric one therefore reads that numeric's storage image
directly:
| Numeric subfield | Storage | Read through a CHAR overlay |
|---|---|---|
ZONED(5:0) = 12345 |
x'F1F2F3F4F5' |
12345 |
ZONED(5:0) = -12345 |
x'F1F2F3F4D5' |
1234N - the sign is overpunched into the last byte's zone, giving the J - R letter for that digit; the field is still five bytes |
PACKED(5:2) = 3.14 |
x'00314F' |
the characters those BCD bytes happen to be - not meaningful as text, but stable |
Overlaying a character subfield to inspect or move a numeric's bytes therefore works as written. Note the packed row: a program doing this on packed data is reading BCD, so treat the result as bytes rather than text.
Assigning one character subfield to another is a byte move, so every byte
survives - including two the code page cannot tell apart as text. Under IBM037
and IBM500, bytes x'15' and x'25' both map to the same character, so a move
routed through text would turn one into the other; a packed field holding
250.00 stores x'25000F', so that byte occurs in ordinary data. Moving a
character overlay of such a field to another field returns 250.00, matching
IBM i, where a character field is bytes and has no intermediate text form.
The byte move applies to a plain subfield-to-subfield assignment. An assignment
whose source is an expression - a concatenation, a %SUBST, a BIF result - genuinely produces text and is encoded through the code page, which is the same
thing IBM i does when a program builds a value rather than moving one. The
practical consequence is that those two byte values are not distinguishable once
a value has passed through a text operation.
SAMEPOS Keyword
SAMEPOS(subfield) positions the subfield at the same starting byte offset as the referenced subfield. It is the modern, name-based alternative to OVERLAY. Unlike OVERLAY, the SAMEPOS subfield does not need to fit within the referenced subfield. The referenced subfield must be previously defined in the same data structure.
DCL-DS MyDs;
FullName CHAR(10);
FirstPart CHAR(5) SAMEPOS(FullName);
END-DS;
Positional Notation (FROM/TO byte positions)
A subfield may instead be defined by absolute byte positions: the FROM
column (positions 26-32) gives the starting byte and the TO column
(positions 33-39) gives the ending byte, so the subfield occupies
TO - FROM + 1 bytes of the structure. Subfields whose byte ranges overlap
share storage, exactly like OVERLAY. This is most often used to carve fixed
fields out of a buffer:
D rec DS
D FromBuf 1 20A
D RecCode 1 3A
D RecName 4 13A
D RecQty 14 20A
Here RecCode is bytes 1-3, RecName bytes 4-13 (10 bytes), and RecQty
bytes 14-20 (7 bytes), all overlaying the 20-byte FromBuf. For numeric types
the byte span maps to the type's digit count: a packed span is 2n-1 digits
(e.g. a 5-byte span is 9 digits), and a binary (B) span is 4 digits for 2
bytes or 9 digits for 4 bytes. Positional subfields overlap by definition, so a
write through one is visible through the others.
The PACKEVEN keyword on a packed subfield changes the digit count from the
default odd formula (2n-1) to the even formula (2n-2). For example, a 3-byte
packed span is 5 digits by default, but 4 digits with PACKEVEN.
OCCURS Keyword
OCCURS(n) on a DS header declares a multiple-occurrence data structure
with n independent copies of the subfield layout. Subfields are accessed by
name - the current occurrence determines which copy is read or written.
Fixed-format:
D DS1 DS OCCURS(3)
D FLD1 5 0
D FLD2 10A
Free-format:
DCL-DS myds OCCURS(3);
fld1 PACKED(5:0);
fld2 CHAR(10);
END-DS;
Switching occurrences:
In free-format, use the %OCCUR BIF to get or set the active occurrence (1-based):
%OCCUR(myds) = 2; // set occurrence to 2
curOc = %OCCUR(myds); // get current occurrence
In fixed-format, use the OCCUR opcode (C-spec):
| Field | Content |
|---|---|
| Factor 1 | Occurrence to set (optional; 1-based numeric) |
| Opcode | OCCUR |
| Factor 2 | Multiple-occurrence DS name |
| Result | Variable to receive current occurrence (optional) |
| Cols 73 - 74 | ER indicator (optional; set on out-of-range) |
At least one of Factor 1 or Result must be specified. The default occurrence
is 1. Out-of-range values (< 1 or > OCCURS count) raise status 00122; handle
with the (E) extender or an ER indicator.
C 2 OCCUR DS1
C Z-ADD 100 FLD1
C OCCUR DS1 CUROC
Clearing OCCURS data structures:
CLEAR on an OCCURS data structure clears only the current occurrence
by default. Each subfield is set to its type default (numeric → 0, alpha
→ blanks, indicator → *OFF). The occurrence level is unchanged.
With the *ALL qualifier, all occurrences are cleared and the
occurrence level is reset to 1.
Free-format:
CLEAR DS1; // clear current occurrence only
CLEAR *ALL DS1; // clear all occurrences, reset to occurrence 1
Fixed-format (*ALL goes in Factor 2, cols 36 - 49):
C CLEAR DS1
C CLEAR *ALL DS1
ALIGN Keyword
ALIGN on a DS header places integer, unsigned and float subfields on their
natural byte boundaries, inserting padding before a subfield when necessary so
its storage begins on that boundary:
| Subfield | Boundary |
|---|---|
INT(5) / UNS(5) (2 bytes) |
2 |
INT(10) / UNS(10) (4 bytes), FLOAT(4) |
4 |
INT(20) / UNS(20) (8 bytes), FLOAT(8) |
8 |
Every other subfield type stays packed against the preceding one. A pointer
subfield is always 16-byte aligned, with or without ALIGN. The padding counts
toward %SIZE: a CHAR(3) followed by an aligned INT(10) sizes 8 (the integer
starts at offset 4), not 7.
Free-format and fixed-format both honor the keyword:
DCL-DS aligned QUALIFIED ALIGN;
a CHAR(3);
n INT(10); // placed at offset 4
END-DS;
D aligned DS QUALIFIED ALIGN
D a 3A
D n 10I 0
Plain ALIGN aligns the subfields but does not round the structure up.
ALIGN(*FULL) additionally pads the whole structure up to a multiple of its
strictest member alignment, so an array element's stride equals %SIZE - an
INT(10) followed by a CHAR(1) sizes 5 under ALIGN but 8 under
ALIGN(*FULL). Use ALIGN(*FULL) when the structure maps to a non-packed C
structure or is used as a DIM array whose stride must match a C layout.