Cross-platform C SDK logo

Cross-platform C SDK

Arrays

❮ Back
Next ❯
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 (...)
voidarrst_destroy (...)
voidarrst_clear (...)
ArrSt(type)*arrst_read (...)
voidarrst_write (...)
uint32_tarrst_size (...)
type*arrst_get (...)
type*arrst_last (...)
type*arrst_all (...)
voidarrst_grow (...)
type*arrst_new (...)
type*arrst_new0 (...)
type*arrst_new_n (...)
type*arrst_new_n0 (...)
type*arrst_prepend_n (...)
type*arrst_insert_n (...)
voidarrst_append (...)
voidarrst_prepend (...)
voidarrst_insert (...)
voidarrst_delete (...)
voidarrst_pop (...)
voidarrst_sort (...)
voidarrst_sort_ex (...)
type*arrst_search (...)
type*arrst_bsearch (...)
voidarrst_foreach (...)
voidarrst_foreach_rev (...)
voidarrst_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):

Listing 1: C Arrays.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
typedef struct _product_t Product;
struct _product_t
{
    type_t type;
    String *code;
    String *description;
    Image *image64;
    real32_t price;
};

uint32_t integers[100];
real32_t reals[100];
Product products[100];

Or, dynamically (Listing 2):

Listing 2: Dynamic arrays.
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.

Setting an array structure to the number of items it has stored.
Figure 1: The 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.

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).
  • Segmentation fault error when relocating the memory block that stores the array.
    Figure 3: Register arrays are dangerous with external references.

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 7).

Listing 7: Initializing an object 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 8).

Listing 8: Constructor of object 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 9). However, in pointer arrays, the memory for the object must be explicitly reserved, since the container will only save a reference.

Listing 9: Insert correctly initialized objects.
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 10), 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.

Listing 10: Reading an array from a stream.
 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 11). In this case, a single write function is sufficient for both types of containers, since each one knows how to access its elements.

Listing 11: Writing an array in a stream.
 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 12). You need a double pointer, since the object will be invalidated (=NULL) after freeing it, avoiding references to free memory areas.
  • Listing 12: Freeing the memory of an object.
    1
    2
    3
    4
    
    Product *product = heap_new(Product);
    ...
    heap_free(&product, Product);
    // product = NULL
    
  • 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 13).
  • Listing 13: Freeing memory from object fields.
    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);
    
  • Destroy: The combination of the previous two. Destroy the fields of the object and free its memory (Listing 14). It is the opposite of the constructor. Obviously, it requires a double pointer to invalidate the reference.
  • Listing 14: Free the object's memory and all its contents.
    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);
    
  • Delete: Delete an element from an array or other type of container (Listing 15). It may have associated a destructor or remover, although it is not mandatory.
  • Listing 15: Delete an item from a container.
    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);
    
  • Clear: Delete all the elements of a container, but do not destroy it, just leave it to zero (Listing 16). Like arrst_delete, optionally can free objects memory.
  • Listing 16: Clear a container, deleting all its items.
    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 time O(logn) if the array is sorted, dramatically increasing performance especially in large sets (Figure 4).
  • Illustration of a dichotomous search in a set of 1000 elements.
    Figure 4: In a maximum of 10 steps we will find an element among a thousand (20 steps for a million).
  • Use the function arrst_sort, to sort an array. We will have to pass a comparison function, which will determine the order relationship (Listing 17).
  • Listing 17: Sort arrays by product code.
    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 18).

  • 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.
  • Listing 18: Search for an item by its code.
     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_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 NULL after destruction.

func_remove

Function that must free the memory associated with the object's fields, but not the object itself Destructors. If NULL only the array will be destroyed and not the internal content of the elements.

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_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 pos and following will be shifted to the right.

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 pos and following will be shifted to the right.

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 pos+1 and following will be shifted to the left.

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 UINT32_MAX if it does not exist. Can be NULL.

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 UINT32_MAX if it does not exist. Can be NULL.

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 '_i' we get the index.

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 '_i' we get the index.

array

The array.

type

Object type.


arrst_end ()

Close the loop opened by arrst_foreach or arrst_foreach_rev.

void
arrst_end(void);
❮ Back
Next ❯