Article summary
The C language is long from dead. In fact, it is still the most popular programming language in the world. Of course, C still has those dreaded pointers that allow attempted access to arbitrary memory locations. Even though when you try to access invalid memory, things still go BOOM!
One of the most significant sources of these explosions is accessing memory past the end of an array. Even though C does have a concept of arrays of any arbitrary type, an array itself has no concept of how long it is!! This library is an attempt to remedy this dangerous hole for variable length arrays of bytes. Though the concept is applicable to arrays of any type.
The byte_array Type
The byte_array
type is defined as follows:
typedef struct _ByteArray {
size_t len; /**< Number of bytes in the `data` field. */
uint8_t* data; /**< Pointer to an allocated array of data bytes. */
} ByteArray;
This in itself it not necessarily magical, but it does allow you to pass an array around with a bit less typing.
One would traditionally define a method taking 2 byte arrays as follows:
void foo(uint8_t* bytesA, size_t lenA, uint8_t* bytesB, size_t lenB)
But, with the byte_array type, you can simply do:
void foo(byte_array a, byte_array b)
Since the byte_array is simply a struct with a pointer and a length, you can pass the array on the stack without taking up any extra space beyond the traditional method.
byte_array Goodies
Though working with the byte_array type is pretty trivial, the library add a few support methods:
In order to allow you to formulate a byte array for an arbitrary blob of data, you can use the following:
ByteArray ByteArray_Create(void* data, size_t len);
Or suppose you want to create a byte_array from a plain ole’ NULL-terminated C string directly:
ByteArray ByteArray_CreateWithCString(const char* str);
You can also extract sub-arrays from a pre-created byte_array:
ByteArray ByteArray_GetSlice(const ByteArray array, size_t start, size_t len);
The byte_buffer Type
For safety purposes, a byte_array should be treated as a constant, since we can’t easily tack memory on to the end of an array. Therefore, we encapsulate a byte_array
within a new byte_buffer
type:
typedef struct {
ByteArray array; /** ByteArray holding allocated array w/length */
size_t bytesUsed; /** Reflects the number of bytes used from the array */
} ByteBuffer;
byte_buffer Goodies
Since the byte_buffer type simply wraps a byte_array and adds a single new bytesUsed attribute, the byte_array can remain constant and bytesUsed can be updated as the buffer is consumed or appended to. This leads to the some more goodies to standardize common operations:
Create a new byte buffer from… whatever:
ByteBuffer ByteBuffer_Create(void* data, size_t max_len, size_t used);
Lil’ helper to create from a byte_array:
ByteBuffer ByteBuffer_CreateWithArray(ByteArray array);
Reset a buffer, so that it can be reused:
void ByteBuffer_Reset(ByteBuffer* buffer);
Query bytes remaining:
long ByteBuffer_BytesRemaining(const ByteBuffer buffer);
Consume some bytes from the beginning of the buffer:
ByteArray ByteBuffer_Consume(ByteBuffer* buffer, size_t len);
Append various things onto the end:
ByteBuffer* ByteBuffer_Append(ByteBuffer* buffer, const void* data, size_t len);
ByteBuffer* ByteBuffer_AppendArray(ByteBuffer* buffer, const ByteArray array);
ByteBuffer* ByteBuffer_AppendCString(ByteBuffer* buffer, const char* data);
ByteBuffer* ByteBuffer_AppendDummyData(ByteBuffer* buffer, size_t len);
Arrays of byte_buffers
To round things out, you will likely run into the case where you want to create and/or toss around an array of byte_buffers:
typedef struct {
ByteBuffer* buffers;
int count;
} ByteBufferArray;
Happy Coding!
Hopefully you find these abstractions useful for your work and sanity. Though this isn’t ground-breaking stuff, and you have likely seen similar looking things copied from project to project, in various transmogrifications, which leads to headaches and multiplication of your level of insanity. Enjoy!