Arrays
This page has been automatically translated using the Google Translate API services. We are working on improving texts. Thank you for your understanding and patience.
Show me your flowcharts and conceal your tables, and I shall continue to be mystified. Show me your tables, and I won't usually need your flowcharts; they'll be obvious. Representation Is the Essence of Programming. Frederick P. Brooks, Jr. - The Mythical Man Month.
Functions
ArrSt(type)* | arrst_create (...) |
ArrSt(type)* | arrst_copy (...) |
void | arrst_destroy (...) |
void | arrst_clear (...) |
ArrSt(type)* | arrst_read (...) |
void | arrst_write (...) |
uint32_t | arrst_size (...) |
type* | arrst_get (...) |
type* | arrst_first (...) |
type* | arrst_last (...) |
type* | arrst_all (...) |
void | arrst_grow (...) |
type* | arrst_new (...) |
type* | arrst_new0 (...) |
type* | arrst_new_n (...) |
type* | arrst_new_n0 (...) |
type* | arrst_prepend_n (...) |
type* | arrst_insert_n (...) |
void | arrst_append (...) |
void | arrst_prepend (...) |
void | arrst_insert (...) |
void | arrst_delete (...) |
void | arrst_pop (...) |
void | arrst_sort (...) |
void | arrst_sort_ex (...) |
type* | arrst_search (...) |
type* | arrst_bsearch (...) |
void | arrst_foreach (...) |
void | arrst_foreach_rev (...) |
void | arrst_end (void) |
Being able to work with collections of records of the same type is essential when designing the data model. In addition to the basic types and struct
, Union
or class
The C language offers us the array construction, which allows you to store several elements under the same variable (Listing 1):
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Or, dynamically (Listing 2):
1 2 3 4 |
uint32_t n = get_n(); uint32_t *integers = heap_new_n(n, uint32_t); real32_t *reals = heap_new_n(n, real32_t); Product *products = heap_new_n(n, Product); |
These constructions store elements in contiguous positions of memory and, although they are very quick to consult, they lack the functionality to insert, delete, search or sort. In many cases, the data is not available when the container is created, but they are entering or leaving dynamically during the program execution, so we cannot anticipate in advance a maximum number with which to make the memory reservation. The Array
type implemented in NAppGUI is, in essence, a dynamic C array and a series of methods to manipulate it. By dynamic we understand that the structure adjusts its size to the actual amount of elements, keeping the main premise that all remain in memory together.
When created, memory is reserved for a few records (Figure 1). Later, we can add new elements at the end of the array (typical) or insert them in any random position in case we already have data in the container. In the latter case, the rest of the elements will be shifted to the right. As soon as the number of reserved records is exceeded, the internal dynamic block will be doubled to accommodate the new positions. In the same way it is possible to eliminate any element of the collection, moving the rest to the left to maintain the spatial coherence of the structure. If the number of items decreases by half, the memory block will be reduced. In this way, during the life of the container, the memory will be adjusted by multiplying or dividing by 2 the number of reserved elements.

Array
adapt their internal memory to the number of elements.1. Registers or pointers
Depending on what is stored inside, we can use two kinds of array (Listing 3). The array of records will keep the entire object inside the container and the array of pointers only a reference to it, the actual object being located in another memory address (Figure 2). Although the internal logic of the structure is the same, access to the elements differs slightly.
- arrst_create will create an array of records.
- arrpt_create will create an array of pointers.
1 2 |
ArrSt(Product) *arrst = arrst_create(Product); ArrPt(Product) *arrpt = arrpt_create(Product); |

Use ArrSt can slightly improve performance, thanks to spatial consistency, which reduces cache failures, and saving calls to the memory manager Arrays vs Sets comparative. But this will not always be possible, and we cannot use them in these cases:
- Opaque objects: If the type definition is not public, the container cannot calculate the space required for each element, so we can only work with pointers to them.
- Shared objects: If other structures of the model keep pointers to the elements of the container, we will have Segmentation Fault problems due to the change of memory addresses when relocating the internal container block (Figure 3).

2. Constructors
When memory is reserved for an object, either in the Stack Segment
1 |
Product product; |
at Heap Segment
1 |
Product *product = heap_new(Product);
|
or in a container
1 |
Product *product = arrst_new(array, Product);
|
its initial content is garbage, understood as undetermined bytes. Initializing an object is assigning valid and consistent values to each field of the object (Listing 4).
Product
.
1 2 3 4 5 6 7 8 |
static void i_init(Product *product) { product->type = ekCPU; product->code = str_c(""); product->description = str_c(""); product->image64 = image_copy(gui_respack_image(NOIMAGE_PNG)); product->price = 0.f; } |
For its part, a constructor is an initializer that previously reserves memory dynamically to store the object (Listing 5).
Product
.
1 2 3 4 5 6 |
static Product *i_create(void) { Product *product = heap_new(Product); i_init(product); return product; } |
When we use register arrays, we will only need to initialize the object, since the space to store it has been reserved by the container itself (Listing 6). However, in pointer arrays, the memory for the object must be explicitly reserved, since the container will only save a reference.
1 2 3 4 5 6 7 8 9 |
Product product; i_init(&product); arrst_append(array, product, Product); Product *product = arrst_new(array, Product); i_init(product); Product *product = i_create(); arrpt_append(array, product, Product); |
Use arrst_new, arrst_insert_n or arrst_prepend_n whenever possible to insert into record arrays, as they avoid having to copy the object.
3. Serialization
A special case of the constructor are the readers (de-serializers). When we create an array from the content of a Streams (Listing 7), we need a method capable of creating or initializing an element from the stream itself. Depending on the type of container it will be necessary to reserve memory for each item or not.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
static void i_init_read(Stream *stm, Product *product) { product->type = stm_read_enum(stm, type_t); product->code = str_read(stm); product->description = str_read(stm); product->image64 = image_read(stm); product->price = stm_read_r32(stm); } static Product *i_read(Stream *stream) { Product *product = heap_new(Product); i_init_read(stream, product); return product; } ArrSt(Product) *arrst = arrst_read(i_init_read, Product); ArrPt(Product) *arrpt = arrpt_read(i_read, Product); |
In the same way we can write (serialize) the contents of an array in a write stream (Listing 8). In this case, a single write function is sufficient for both types of containers, since each one knows how to access its elements.
1 2 3 4 5 6 7 8 9 10 11 |
static void i_write(Stream *stm, const Product *product) { stm_write_enum(stm, product->type, type_t); str_write(stm, product->code); str_write(stm, product->description); image_write(stm, product->image64); stm_write_r32(stm, product->price); } arrst_write(stm, arrst, i_write, Product); arrpt_write(stm, arrpt, i_write, Product); |
4. Destructors
In programming many times we are confused by the verbs: 'delete', 'destroy', 'free', 'erase', 'remove', 'clear' since they essentially mean the same thing but with subtle differences. In NAppGUI we will use one verb or another depending on concrete actions:
- Free: Only free dynamic memory allocated to an object (Listing 9). You need a double pointer, since the object will be invalidated (
=NULL
) after freeing it, avoiding references to free memory areas. - Remove: It destroys the fields of an object, but does not free the memory of the object itself. It is the opposite of the initializer (Listing 10).
- Destroy: The combination of the previous two. Destroy the fields of the object and free its memory (Listing 11). It is the opposite of the constructor. Obviously, it requires a double pointer to invalidate the reference.
- Delete: Delete an element from an array or other type of container (Listing 12). It may have associated a destructor or remover, although it is not mandatory.
- Clear: Delete all the elements of a container, but do not destroy it, just leave it to zero (Listing 13). Like arrst_delete, optionally can free objects memory.
1 2 3 4 |
1 2 3 4 5 6 7 8 |
static void i_remove(Product *product) { str_destroy(&product->code); str_destroy(&product->description); image_destroy(&product->image64); } arrst_destroy(&arrst, i_remove, Product); |
1 2 3 4 5 6 7 |
static void i_destroy(Product **product) { i_remove(*product); heap_free(product, Product); } arrpt_destroy(&arrpt, i_destroy, Product); |
1 2 3 4 5 6 7 8 |
// Just delete. arrst_delete(arrst, 4, NULL, Product); // Delete and remove (arrst). arrst_delete(arrst, 4, i_remove, Product); // Delete and destroy (arrpt). arrpt_delete(arrpt, 4, i_destroy, Product); |
1 2 3 |
arrst_clear(arrst, NULL, Product); arrst_clear(arrst, i_remove, Product); arrpt_clear(arrpt, i_destroy, Product); |
5. Sort and search
The usual way to use arrays will be to add elements at the end by arrst_new then iterate over all. This "natural" order will be enough in most cases, but we may need to organize the elements following another criterion for:
- Present the information ordered by one or several fields of the structure.
- Optimize searches. To locate a certain element, there is no choice but to travel the entire array, with linear cost
O(n)
. But we can solve the search in logarithmic timeO(logn)
if the array is sorted, dramatically increasing performance especially in large sets (Figure 4). - Use the function arrst_sort, to sort an array. We will have to pass a comparison function, which will determine the order relationship (Listing 14).

1 2 3 4 5 6 7 |
static int i_compare(const Product *p1, const Product *p2) { return str_scmp(p1->code, p2->code); } arrst_sort(arrst, i_compare, Product); arrpt_sort(arrpt, i_compare, Product); |
To search for an element within an array, we must also provide a function that compares the object with a key. This key contains the search criteria and is usually smaller than the element itself. Many times it is just a simple number or a text string (Listing 15).
- arrst_search Slow method. It will search for elements in a linear way, one by one
O(n)
. - arrst_bsearch Fast method. It will search elements in logarithmic way,
O(logn)
. The array must be sorted according to the same criteria as the search.
1 2 3 4 5 6 7 8 9 10 11 12 |
static int i_compare_key(const Product *p1, const char_t *key) { return str_cmp(p1->code, key); } uint32_t pos; Product *pr1, *pr2; // Slow O(n) pr1 = arrst_search(arrst, i_compare_key, "G3900", &pos, Product, char_t); // Fast O(logn) pr2 = arrst_bsearch(arrst, i_compare_key, "G3900", &pos, Product, char_t); |
6. Arrays of basic types
The basic types are a particular case of single-field structure, so we will use it ArrSt. In the specific case of enum
we must create an alias by typedef
, as ArrSt(type)
does not support the keyword enum
, just as does not support struct
keyword. In C++ this alias is not necessary. When destroying the array we will pass NULL
to the destructor parameter, since the basic types do not generate dynamic memory.
1 2 3 4 5 |
typedef enum _type_t type_t; ArrSt(uint32_t) *integers = arrst_create(uint32_t); ArrSt(type_t) *types = arrst_create(type_t); arrst_destroy(&integers, NULL, uint32_t); arrst_destroy(&types, NULL, type_t); |
arrst_create ()
Create an empty array.
ArrSt(type)* arrst_create(type);
type | Object type. |
Return
The new array.
arrst_copy ()
Create a copy of an array.
ArrSt(type)* arrst_copy(const ArrSt(type) *array, FPtr_scopy func_copy, type);
array | The original array. |
func_copy | Function that must copy the fields of each object. |
type | Object type. |
Return
The copy of the original array.
Remarks
The copy function must allocate memory to the fields that require it, but NOT to the object itself. If we pass NULL
, a byte-by-byte copy of the original object will be made, which may pose an integrity risk if the array elements contain String
or other objects that need dynamic memory.
arrst_destroy ()
Destroy an array and all its elements.
void arrst_destroy(ArrSt(type)**, FPtr_remove func_remove, type);
The array. It will be set to | |
func_remove | Function that must free the memory associated with the object's fields, but not the object itself Destructors. If |
type | Object type. |
arrst_clear ()
Delete the contents of the array, without destroying the container that will be left with zero elements.
void arrst_clear(ArrSt(type) *array, FPtr_remove func_remove, type);
array | The array. |
func_remove | Remove function. See arrst_destroy. |
type | Object type. |
arrst_read ()
Create an array by reading its contents from a Streams (de-serialization).
ArrSt(type)* arrst_read(Stream *stream, FPtr_read_init func_read, type);
stream | A read stream. |
func_read | Function to initialize an object from the data obtained from a stream. This function should not reserve memory for the object itself (the container already does). Serialization. |
type | Object type. |
Return
The array readed.
arrst_write ()
Write an array in a Streams (serialization).
void arrst_write(Stream *stream, const ArrSt(type) *array, FPtr_write func_write, type);
stream | A write stream. |
array | The array. |
func_write | Function that writes the content of an element in a stream Serialization. |
type | Object type. |
arrst_size ()
Get the number of elements in an array.
uint32_t arrst_size(const ArrSt(type) *array, type);
array | The array. |
type | Object type. |
Return
Number of elements.
arrst_get ()
Get a pointer to the item in pos
position.
type* arrst_get(const ArrSt(type) *array, const uint32_t pos, type);
array | The array. |
pos | Item position or index. |
type | Object type. |
Return
Item Pointer.
arrst_first ()
Gets a pointer to the first element of the array.
type* arrst_first(const ArrSt(type) *array, type);
array | The array. |
type | Object type. |
Return
Item pointer.
arrst_last ()
Get a pointer to the last element of the array.
type* arrst_last(const ArrSt(type) *array, type);
array | The array. |
type | Object type. |
Return
Item Pointer.
arrst_all ()
Get a pointer to the internal memory of the array, which gives direct access to all the elements.
type* arrst_all(const ArrSt(type) *array, type);
array | The array. |
type | Object type. |
Return
Base pointer. Increasing it one by one we will iterate over the elements.
Remarks
Use arrst_foreach to iterate over all elements in a more secure and elegant way.
arrst_grow ()
Add n
elements, not initialized, at the end of the array.
void arrst_grow(ArrSt(type) *array, const uint32_t n, type);
array | The array. |
n | Number of items to add. |
type | Object type. |
arrst_new ()
Reserve space for an element at the end of the array.
type* arrst_new(ArrSt(type) *array, type);
1 2 3 4 5 6 7 8 |
// arrst_append copies 'product' Product product; i_init_product(&product, ...); arrst_append(array, product, Product); // arrst_new avoids the copy Product *product = arrst_new(array, Product); i_init_product(product, ...); |
array | The array. |
type | Object type. |
Return
Pointer to added element.
Remarks
It is slightly faster than arrst_append
, especially in large structures, since it avoids copying the contents of the object. Initial memory content is indeterminate.
arrst_new0 ()
Reserve space for an element at the end of the array and initialize it to 0.
type* arrst_new0(ArrSt(type) *array, type);
array | The array. |
type | Object type. |
Return
Pointer to added element.
Remarks
Same as arrst_new but initializing all memory to 0.
arrst_new_n ()
Reserve space for multiple elements at the end of the array.
type* arrst_new_n(ArrSt(type) *array, const uint32_t n, type);
array | The array. |
n | Number of elements to add. |
type | Object type. |
Return
Pointer to the first element added.
Remarks
Same as arrst_new but reserving multiple elements in the same call. Initial memory content is indeterminate.
arrst_new_n0 ()
Reserve space for several elements at the end of the array and initialize them to 0.
type* arrst_new_n0(ArrSt(type) *array, const uint32_t n, type);
array | The array. |
n | Number of elements to add. |
type | Object type. |
Return
Pointer to the first element added.
Remarks
Same as arrst_new_n but initializing all memory to 0.
arrst_prepend_n ()
Reserve space for several elements at the beginning of the array. The rest of the elements will be shifted to the right.
type* arrst_prepend_n(ArrSt(type) *array, const uint32_t n, type);
array | The array. |
n | Number of elements to insert. |
type | Object type. |
Return
Pointer to the first inserted element.
Remarks
Initial memory content is indeterminate.
arrst_insert_n ()
Reserve space for several elements in an arbitrary position of the array.
type* arrst_insert_n(ArrSt(type) *array, const uint32_t pos, const uint32_t n, type);
array | The array. |
pos | Position where it will be inserted. The current element in |
n | Number of elements to insert. |
type | Object type. |
Return
Pointer to the first inserted element.
Remarks
Initial memory content is indeterminate.
arrst_append ()
Append an element to the end of the array.
void arrst_append(ArrSt(type) *array, type value, type);
array | The array. |
value | Item to add. |
type | Object type. |
arrst_prepend ()
Insert an element at the beginning of the array. The rest of the elements will be shifted to the right.
void arrst_prepend(ArrSt(type) *array, type value, type);
array | The array. |
value | Item to insert. |
type | Object type. |
arrst_insert ()
Insert an element in an arbitrary array position.
void arrst_insert(ArrSt(type) *array, const uint32_t pos, type value, type);
array | The array. |
pos | Position where it will be inserted. The current item in |
value | Item to insert. |
type | Object type. |
arrst_delete ()
Remove an element from the array.
void arrst_delete(ArrSt(type) *array, const uint32_t pos, FPtr_remove func_remove, type);
array | The array. |
pos | Position of the item to be deleted. The current item in |
func_remove | 'Remove' function. See arrst_destroy. |
type | Object type. |
arrst_pop ()
Remove the last element from the array.
void arrst_pop(ArrSt(type) *array, FPtr_remove func_remove, type);
array | The array. |
func_remove | 'Remove' function. See arrst_destroy. |
type | Object type. |
arrst_sort ()
Sort array elements using Quicksort.
void arrst_sort(ArrSt(type) *array, FPtr_compare func_compare, type);
array | The array. |
func_compare | Function to compare two elements. Sort and search. |
type | Object type. |
arrst_sort_ex ()
Sort array elements using Quicksort and additional data.
void arrst_sort_ex(ArrSt(type) *array, FPtr_compare_ex func_compare, type, dtype);
array | The array. |
func_compare | Function to compare two elements using an additional data. |
type | Object type. |
dtype | Type of data in the comparison function. |
arrst_search ()
Search for an element in the array linearly O(n)
.
type* arrst_search(ArrSt(type) *array, FPtr_compare func_compare, ktype key, uint32_t *pos, type, ktype);
1 2 3 |
uint32_t pos; uint32_t key = 345; Product *product = arrst_search(arrst, i_compare_key, &key, &pos, Product, uint32_t); |
array | The array. |
func_compare | Comparison function. The first parameter is the element, the second the search key. Sort and search. |
key | Search key. Pointer to a data type that may be different from the type of array element. |
pos | Position of the element in the array (if it exists), or |
type | Object type. |
ktype | Key type. |
Return
Pointer to the first element that matches the search criteria or NULL
if none exists.
arrst_bsearch ()
Search for an element in the array logarithmically O(logn)
.
type* arrst_bsearch(ArrSt(type) *array, FPtr_compare func_compare, ktype key, uint32_t *pos, type, ktype);
array | The array. |
func_compare | Comparison function. The first parameter is the element, the second the search key. Sort and search. |
key | Search key. Pointer to a data type that may be different from the type of array element. |
pos | Position of the element in the array (if it exists), or |
type | Object type. |
ktype | Key type. |
Return
Pointer to the first element that matches the search criteria or NULL
if none exists.
Remarks
The array must be sorted according to the same criteria as the search. If not, the result is unpredictable.
arrst_foreach ()
Iterate on all array elements. Uses arrst_end
to close the loop.
void arrst_foreach(type *elem, ArrSt(type) *array, type);
1 2 3 |
arrst_foreach(product, array, Product) bstd_printf("Index:%d, Id:%d\n", product_i, product->id); arrst_end() |
elem | Name of the 'element' variable within the loop. Adding the suffix |
array | The array. |
type | Object type. |
arrst_foreach_rev ()
Iterate on all array elements backward, from the last to the first. Uses arrst_end
to close the loop.
void arrst_foreach_rev(type *elem, ArrSt(type) *array, type);
1 2 3 4 |
// Now in reverse order arrst_foreach_rev(product, array, Product) bstd_printf("Index:%d, Id:%d\n", product_i, product->id); arrst_end() |
elem | Name of the 'element' variable within the loop. Adding the suffix |
array | The array. |
type | Object type. |
arrst_end ()
Close the loop opened by arrst_foreach or arrst_foreach_rev.
void
arrst_end(void);