Portable Byte Array

Introduction
SequenceByte container
Platform-specific solution
Another approach

Introduction

A byte array is often declared as follows.

unsigned char byteArray[100]; // 'unsigned char' may or may not be 8 bits wide

This works fine on a byte-addressable system where each memory location holds a byte (an 8-bit value, an octet).

On a word-addressable system, however, there is no concept of a memory byte. Instead, each memory location holds a word, in many cases a 32-bit value. Moreover, 8- and 16-bit integer types (e.g. uint8_t) do not exist.

A portable byte (octet) array can be declared as follows.

#include <cstdint>
std::uint_least8_t byteArray[100]; // 'uint_least8_t' is the smallest integer type at least 8 bits wide

This declaration, although portable, can lead to inefficient use of memory – each memory location (a word on some platforms) is used to hold (only) a single 8-bit value (a byte).

SequenceByte container

PETR provides SequenceByte array-like container class template that can effortlessly store multiple bytes in a word.

#include "petr/Sequence.hpp"
Petr::Container::SequenceByte<100, Petr::Container::SEQUENCE_BYTE_PACKED_32_LITTLE_ENDIAN_FAST> byteArray; // bytes packed into words

In the above example, SequenceByte (an array of 100 bytes) will pack four bytes into a word, starting with the least significant byte.

Platform-specific solution

Byte array can be defined flexibly, according to the target platform as shown below.

#include "petr/Platform.h"
#include "petr/Sequence.hpp"

template <std::size_t CAPACITY> class ByteArray :
#   if defined(PETR_BYTE_ADDRESSABLE_PLATFORM)
    public Petr::Container::SequenceByte<                CAPACITY, Petr::Container::SEQUENCE_BYTE_BASIC_FAST>
#   elif defined(PETR_WORD_ADDRESSABLE_PLATFORM) && defined(PETR_LITTLE_ENDIAN_BYTE_ORDER_PLATFORM)
    public Petr::Container::SequenceByte<                CAPACITY, Petr::Container::SEQUENCE_BYTE_PACKED_32_LITTLE_ENDIAN_FAST>
#   elif defined(PETR_WORD_ADDRESSABLE_PLATFORM) && defined(PETR_BIG_ENDIAN_BYTE_ORDER_PLATFORM)
    public Petr::Container::SequenceByte<                CAPACITY, Petr::Container::SEQUENCE_BYTE_PACKED_32_BIG_ENDIAN_FAST>
#   else
    public Petr::Container::Sequence<std::uint_least8_t, CAPACITY, Petr::Container::SEQUENCE_FAST>
#   endif
    {};

ByteArray<100> byteArray;

Individual bytes can be written to

ByteArray<100>::Index N = byteArray.getCapacity();
for (ByteArray<100>::Index i = 0; i < N; ++i)
{
    byteArray[i] = i % 256;
}

and read from.

int long unsigned sum = 0;
for (ByteArray<100>::Index i = 0; i < N; ++i)
{
    sum += byteArray[i];
}

On byte-addressable platforms, discrete bytes are accessed directly. On word-addressable platforms, bytes are transparently packed into words. On other platforms, bytes are stored without packing.

#include <cstddef>
#include <cstdint>
#include "petr/Platform.h"
#include "petr/Sequence.hpp"

template <std::size_t CAPACITY> class ByteArray :
#   if defined(PETR_BYTE_ADDRESSABLE_PLATFORM)
    public Petr::Container::SequenceByte<                CAPACITY, Petr::Container::SEQUENCE_BYTE_BASIC_FAST>
#   elif defined(PETR_WORD_ADDRESSABLE_PLATFORM) && defined(PETR_LITTLE_ENDIAN_BYTE_ORDER_PLATFORM)
    public Petr::Container::SequenceByte<                CAPACITY, Petr::Container::SEQUENCE_BYTE_PACKED_32_LITTLE_ENDIAN_FAST>
#   elif defined(PETR_WORD_ADDRESSABLE_PLATFORM) && defined(PETR_BIG_ENDIAN_BYTE_ORDER_PLATFORM)
    public Petr::Container::SequenceByte<                CAPACITY, Petr::Container::SEQUENCE_BYTE_PACKED_32_BIG_ENDIAN_FAST>
#   else
    public Petr::Container::Sequence<std::uint_least8_t, CAPACITY, Petr::Container::SEQUENCE_FAST>
#   endif
    {};
    
ByteArray<100> byteArray;
    
ByteArray<100>::Index N = byteArray.getCapacity();
for (ByteArray<100>::Index i = 0; i < N; ++i)
{
    byteArray[i] = i % 256;
}

int long unsigned sum = 0;
for (ByteArray<100>::Index i = 0; i < N; ++i)
{
    sum += byteArray[i];
}

Another approach

When developing code for a limited set of known target platforms, one may be able to select a single container type suitable for all of the platforms. For example, when writing code for a byte-addressable little-endian byte order (e.g. ARM Cortex-A in LE mode i.e. x86 compatible) and word-addressable (e.g. ADI SHARC) platforms mentioned above, a sequence container packing four bytes into a single word in little-endian order can serve both types of platforms. Advantage of this approach is simpler implementation. Disadvantage is that, depending on the optimizer, code compiled for the byte-addressable platform may not run as fast as it could.

#include "petr/Sequence.hpp"
Petr::Container::SequenceByte<100, Petr::Container::SEQUENCE_BYTE_PACKED_32_LITTLE_ENDIAN_FAST> byteArray;

Note that packing bytes into words in the same order as the byte order of the byte-addressable platform ensures that logical access and memory layout of the sequence container data are conveniently matching.

Casting raw data location and accessing the data using a byte pointer on a byte-addressable platform yields the same order of values as accessing the packed byte sequence container via an index.

Leave a Reply

Your email address will not be published. Required fields are marked *

Copyright © 2013-2023 BeneQuidem Generated 2024-02-26T00:58:24Z