they just don't mention anything about whether these arrays have to be declared with packed keyword. It seems to me like a natural precondition for pointer math, but I might be wrong.
They don't have to be packed. The compiler doesn't care as long as it _knows_ what the packing state is. The packing, as Marco, pointed out can result in a performance penalty when there are many elements/fields starting at an address that is not a multiple of their size but, the compiler can calculate the correct address either way.
When exactly can we safely use pointer math on arrays?
As long as you don't "lie" to the compiler, addressing through pointers or indexing should _functionally_ be the same. IOW, the following should be an identity maintained by the compiler : ArrayBase[Index] = (ArrayBase + Index)^. In both cases, the compiler knows it's dealing with an array and as long as it knows that, the generated code should be correct.
The fast way of getting in trouble is to declare some records (say 10 for example's sake) and then _overlay_ an array of records of that type over them. In many cases, the alignment will be wrong because the compiler had no idea the 10 record declarations were to be part of an array.
There are ways to work around that, (I don't know how to do it in Pascal/FPC) but, in C/C++ you can tell the compiler the alignment for each individual record then you can declare a pointer to the first record/structure including its alignment. Once the compiler knows the alignment and the alignment of the individual elements matches the alignment of the structure pointer, you go to town.
Here is an example in C,
typedef struct TFORMATTED_OPTIONAL_HEADER
{
/* 00 */
MS_ALIGN
DWORD32 _00_OffsetMagic GCC_ALIGN = 0;
FMT_HEX _00_OffsetMagicHex ;
DWORD64 _00_ValueMagic = 0;
FMT_HEX _00_ValueMagicHex ;
FMT_DEC _00_ValueMagicDec ;
const WCHAR* _00_LabelMagic = OptionalHeaderFieldMeanings[0];
TDATA_TYPE _00_Type32Magic = dt_word;
TDATA_TYPE _00_Type64Magic = dt_word;
DWORD32 _00_DecimalMagic = FALSE;
/* 01 */
MS_ALIGN
DWORD32 _01_OffsetMajorLinkerVersion
GCC_ALIGN = 0;
FMT_HEX _01_OffsetMajorLinkerVersionHex ;
DWORD64 _01_ValueMajorLinkerVersion = 0;
FMT_HEX _01_ValueMajorLinkerVersionHex ;
FMT_DEC _01_ValueMajorLinkerVersionDec ;
const WCHAR* _01_LabelMajorLinkerVersion = OptionalHeaderFieldMeanings[1];
TDATA_TYPE _01_Type32MajorLinkerVersion = dt_byte;
TDATA_TYPE _01_Type64MajorLinkerVersion = dt_byte;
DWORD32 _01_DecimalMajorLinkerVersion = TRUE;
...
...
a bunch of definitions like the above
...
...
/* 29 */
MS_ALIGN
DWORD32 _29_OffsetNumberOfRvaAndSizes
GCC_ALIGN = 0;
FMT_HEX _29_OffsetNumberOfRvaAndSizesHex ;
DWORD64 _29_ValueNumberOfRvaAndSizes = 0;
FMT_HEX _29_ValueNumberOfRvaAndSizesHex ;
FMT_DEC _29_ValueNumberOfRvaAndSizesDec ;
const WCHAR* _29_LabelNumberOfRvaAndSizes = OptionalHeaderFieldMeanings[29];
TDATA_TYPE _29_Type32NumberOfRvaAndSizes = dt_dword;
TDATA_TYPE _29_Type64NumberOfRvaAndSizes = dt_dword;
DWORD32 _29_DecimalNumberOfRvaAndSizes = TRUE;
} FORMATTED_OPTIONAL_HEADER, *PFORMATTED_OPTIONAL_HEADER;
The MS_ALIGN and GCC_ALIGN are macros for MSVC and GCC to inform them of the alignment of every structure/record.
Then the above structs/records that were individually defined can be accessed as an array by defining the following
// NOTE: the layout of each entry in the above structure MUST match the layout
// of the entry structure below.
typedef struct TOPTIONAL_HEADER_ENTRY
{
MS_ALIGN
DWORD32 Offset GCC_ALIGN;
FMT_HEX OffsetHex;
DWORD64 Value;
FMT_HEX ValueHex;
FMT_DEC ValueDec;
const WCHAR* Label;
TDATA_TYPE Type32;
TDATA_TYPE Type64;
DWORD32 DecimalOutput;
} OPTIONAL_HEADER_ENTRY, *POPTIONAL_HEADER_ENTRY;
That last definition is used to access the individually declared structs as if they were an ARRAY of OPTIONAL_HEADER_ENTRY and it works because the alignments match.
In Pascal/FPC instead of using a dirty trick like the above, you're better off having an array of initialized variables which, you can access with an index or a pointer since the compiler didn't get "lied" to.
HTH.