Commit 983c43f0 authored by Romain Vimont's avatar Romain Vimont Committed by Thomas Guillem

vlc_vector: add helpers for vectors

Add a new API for handling general-purpose vectors, intended to replace
ARRAY_*.

Like ARRAY_*, it provides macros to handle a dynamic array generic
over the type of its items.

Contrary to ARRAY_*:
 - it does not abort on allocation failure (but reports the error);
 - it uses size_t instead of int to store the capacity and the size;
 - it checks for overflows on reallocation.

For illustration purpose, embedding a vector of input_item_t* in a
struct looks like:

    struct playlist {
        struct VLC_VECTOR(input_item_t *) items;
        // ...
    };

The main features (with names inspired by std::vector from C++ and
std::vec::Vec from Rust) are:
 - void vlc_vector_init(pv)
      init the vector (use VLC_VECTOR_INITIALIZER for static init)
 - void vlc_vector_destroy(pv)
      destroy the vector and release any associated resources
 - void vlc_vector_clear(pv)
      remove all items from the vector
 - vec.size
      read the size of the vector
 - vec.data[i]
      access the i_th item
 - bool vlc_vector_push(pv, item)
      push an item to the end of the vector
 - bool vlc_vector_push_all(pv, items, count)
      push serveral items to the end of the vector
 - bool vlc_vector_insert(pv, index, item)
      insert an item at index
 - bool vlc_vector_insert_all(pv, index, items, count)
      insert several items at index
 - void vlc_vector_move(pv, index, target)
      move an item to target
 - void vlc_vector_move_all(pv, index, count, target)
      move a slice of items to target
 - void vlc_vector_remove(pv, index)
      remove an item
 - void vlc_vector_remove_slice(pv, index, count)
      remove a slice of items
 - void vlc_vector_swap_remove(pv, index)
      remove an item in O(1) without preserving ordering
 - bool vlc_vector_reserve(pv, mincap)
      increase the capacity of the vector in advance
 - void vlc_vector_shrink_to_fit(pv)
      resize the vector to its actual size
 - void vlc_vector_index_of(pv, index, pidx)
      find the index of an item (returns the result in *pidx)
 - vlc_vector_foreach(item, pv)
      a for-each loop

It uses an exponential growth policy for both increasing and decreasing
the size. As a consequence, the amortized complexity of
vlc_vector_push() is O(1).
Signed-off-by: Thomas Guillem's avatarThomas Guillem <thomas@gllm.fr>
parent 7c98f6ba
......@@ -1119,6 +1119,12 @@ static inline void *vlc_alloc(size_t count, size_t size)
return mul_overflow(count, size, &size) ? NULL : malloc(size);
}
VLC_USED
static inline void *vlc_reallocarray(void *ptr, size_t count, size_t size)
{
return mul_overflow(count, size, &size) ? NULL : realloc(ptr, size);
}
/*****************************************************************************
* I18n stuff
*****************************************************************************/
......
/******************************************************************************
* vlc_vector.h
******************************************************************************
* Copyright (C) 2018 VLC authors and VideoLAN
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifndef VLC_VECTOR_H
#define VLC_VECTOR_H
#include <stdbool.h>
/**
* \defgroup vector Vector
* \ingroup cext
* @{
* \file
* This provides convenience helpers for vectors.
*/
/**
* Vector struct body.
*
* A vector is a dynamic array, managed by the vlc_vector_* helpers.
*
* It is generic over the type of its items, so it is implemented as macros.
*
* To use a vector, a new type must be defined:
*
* \verbatim
* struct vec_int VLC_VECTOR(int);
* \endverbatim
*
* The struct may be anonymous:
*
* \verbatim
* struct VLC_VECTOR(const char *) names;
* \endverbatim
*
* It is convenient to define a typedef to an anonymous structure:
*
* \verbatim
* typedef struct VLC_VECTOR(int) vec_int_t;
* \endverbatim
*
* Vector size is accessible via `vec.size`, and items are intended to be
* accessed directly, via `vec.data[i]`.
*
* Functions and macros having name ending with '_' are private.
*/
#define VLC_VECTOR(type) \
{ \
size_t cap; \
size_t size; \
type *data; \
}
/**
* Static initializer for a vector.
*/
#define VLC_VECTOR_INITIALIZER { 0, 0, NULL }
/**
* Initialize an empty vector.
*/
#define vlc_vector_init(pv) (void) \
( \
/* cannot be implemened as do-while(0), called from vlc_vector_clear() */ \
(pv)->cap = 0, \
(pv)->size = 0, \
(pv)->data = NULL \
)
/**
* Destroy a vector.
*
* The vector may not be used anymore unless vlc_vector_init() is called.
*/
#define vlc_vector_destroy(pv) \
free((pv)->data)
/**
* Clear a vector.
*
* Remove all items from the vector.
*/
#define vlc_vector_clear(pv) \
( \
/* cannot be implemened as do-while(0), called from vlc_vector_resize_() */ \
vlc_vector_destroy(pv), \
vlc_vector_init(pv) \
)
/**
* The minimal allocation size, in number of items.
*
* Private.
*/
#define VLC_VECTOR_MINCAP_ ((size_t) 10)
static inline size_t
vlc_vector_min_(size_t a, size_t b)
{
return a < b ? a : b;
}
static inline size_t
vlc_vector_max_(size_t a, size_t b)
{
return a > b ? a : b;
}
static inline size_t
vlc_vector_between_(size_t x, size_t min, size_t max)
{
return vlc_vector_max_(min, vlc_vector_min_(max, x));
}
static inline size_t
vlc_vector_enforce_size_t_(size_t value)
{
return value;
}
#define VLC_VECTOR_FAILFLAG_ (~(((size_t) -1) >> 1)) /* only the MSB */
/**
* Realloc data and update vector fields.
*
* On reallocation success, return the reallocated array and update the vector
* capacity and size.
*
* On reallocation failure, return `ptr`, keep `*psize` untouched, and set the
* failflag in `*pcap` to indicate allocation failure (to be consumed by
* `vlc_vector_test_and_reset_failflag_()`).
*
* This weird behavior allows to simultaneously:
* - not require compiler extensions like "statement expressions"
* - keep the vector data, size and capacity unchanged on reallocation failure
* - not require output variables other than vector fields from the caller
* - not violate the strict aliasing rules
* - report the reallocation status (success or failure)
*
* Private.
*
* \param ptr the current data to realloc
* \param the requested capacity, in number of items
* \param the size of one item
* \pcap a pointer to the `cap` field of the vector [IN/OUT]
* \pcap a pointer to the `size` field of the vector [IN/OUT]
* \return the reallocated array, or `ptr` if reallocation failed
*/
static inline void *
vlc_vector_reallocdata_(void *ptr, size_t count, size_t size,
size_t *restrict pcap, size_t *restrict psize)
{
void *n = vlc_reallocarray(ptr, count, size);
if (!n)
{
/* this vector implementation guarantees that the capacity may not
* exceed SIZE_MAX/2 (to prevent overflows), so we can use the MSB to
* report allocation failure */
*pcap |= VLC_VECTOR_FAILFLAG_;
return ptr;
}
*pcap = count;
*psize = vlc_vector_min_(*psize, count);
return n;
}
/**
* Test and reset the fail flag.
*
* \retval true if the flag was set
* \retval false if the flag was not set
*/
static inline bool
vlc_vector_test_and_reset_failflag_(size_t *pcap)
{
if (*pcap & VLC_VECTOR_FAILFLAG_)
{
*pcap &= ~VLC_VECTOR_FAILFLAG_;
return true;
}
return false;
}
/**
* Realloc the underlying array to `newcap`.
*
* Private.
*
* \param pv a pointer to the vector
* \param newcap (size_t) the requested size
* \retval true if no allocation failed
* \retval false on allocation failure (the vector is left untouched)
*/
#define vlc_vector_realloc_(pv, newcap) \
( \
(pv)->data = vlc_vector_reallocdata_((pv)->data, newcap, \
sizeof(*(pv)->data), \
&(pv)->cap, &(pv)->size), \
!vlc_vector_test_and_reset_failflag_(&(pv)->cap) \
)
/**
* Resize the vector to `newcap` exactly.
*
* If `newcap` is 0, the vector is cleared.
*
* Private.
*
* \param pv a pointer to the vector
* \param newcap (size_t) the requested capacity
* \retval true if no allocation failed
* \retval false on allocation failure (the vector is left untouched)
*/
#define vlc_vector_resize_(pv, newcap) \
( \
(pv)->cap == (newcap) /* nothing to do */ || \
( \
(newcap) > 0 ? vlc_vector_realloc_(pv, newcap) \
: (vlc_vector_clear(pv), true) \
) \
)
static inline size_t
vlc_vector_growsize_(size_t value)
{
/* integer multiplication by 1.5 */
return value + (value >> 1);
}
/* SIZE_MAX/2 to fit in ssize_t, and so that cap*1.5 does not overflow. */
#define vlc_vector_max_cap_(pv) (SIZE_MAX / 2 / sizeof(*(pv)->data))
/**
* Increase the capacity of the vector to at least `mincap`.
*
* \param pv a pointer to the vector
* \param mincap (size_t) the requested capacity
* \retval true if no allocation failed
* \retval false on allocation failure (the vector is left untouched)
*/
#define vlc_vector_reserve(pv, mincap) \
/* avoid to allocate tiny arrays (< VLC_VECTOR_MINCAP_) */ \
vlc_vector_reserve_internal_(pv, \
vlc_vector_max_(mincap, VLC_VECTOR_MINCAP_))
#define vlc_vector_reserve_internal_(pv, mincap) \
( \
(mincap) <= (pv)->cap /* nothing to do */ || \
( \
(mincap) <= vlc_vector_max_cap_(pv) /* not too big */ && \
vlc_vector_realloc_(pv, \
/* multiply by 1.5, force between [mincap, maxcap] */ \
vlc_vector_between_(vlc_vector_growsize_((pv)->cap), \
mincap, \
vlc_vector_max_cap_(pv))) \
) \
)
/**
* Resize the vector so that its capacity equals its actual size.
*
* \param pv a pointer to the vector
*/
#define vlc_vector_shrink_to_fit(pv) \
(void) /* decreasing the size may not fail */ \
vlc_vector_resize_(pv, (pv)->size)
/**
* Resize the vector down automatically.
*
* Shrink only when necessary (in practice when cap > (size+5)*1.5)
*
* \param pv a pointer to the vector
*/
#define vlc_vector_autoshrink(pv) (void) \
( \
(pv)->cap <= VLC_VECTOR_MINCAP_ /* do not shrink to tiny length */ || \
(pv)->cap < vlc_vector_growsize_((pv)->size+5) /* no need to shrink */ || \
vlc_vector_resize_(pv, vlc_vector_max_((pv)->size+5, VLC_VECTOR_MINCAP_)) \
)
#define vlc_vector_check_same_ptr_type_(a, b) \
(void) ((a) == (b)) /* warn on type mismatch */
/**
* Push an item at the end of the vector.
*
* The amortized complexity is O(1).
*
* \param pv a pointer to the vector
* \param item the item to append
* \retval true if no allocation failed
* \retval false on allocation failure (the vector is left untouched)
*/
#define vlc_vector_push(pv, item) \
( \
vlc_vector_reserve(pv, (pv)->size + 1) && \
( \
(pv)->data[(pv)->size++] = (item), \
true \
) \
)
/**
* Append `count` items at the end of the vector.
*
* \param pv a pointer to the vector
* \param items the items array to append
* \param count the number of items in the array
* \retval true if no allocation failed
* \retval false on allocation failure (the vector is left untouched)
*/
#define vlc_vector_push_all(pv, items, count) \
vlc_vector_push_all_internal_(pv, items, vlc_vector_enforce_size_t_(count))
#define vlc_vector_push_all_internal_(pv, items, count) \
( \
vlc_vector_check_same_ptr_type_((pv)->data, items), \
vlc_vector_reserve(pv, (pv)->size + (count)) && \
( \
memcpy(&(pv)->data[(pv)->size], items, (count) * sizeof(*(pv)->data)), \
(pv)->size += (count), \
true \
) \
)
/**
* Insert an hole of size `count` to the given index.
*
* The items in range [index; size-1] will be moved. The items in the hole are
* left uninitialized.
*
* \param pv a pointer to the vector
* \param index the index where the hole is to be inserted
* \param count the number of items in the hole
* \retval true if no allocation failed
* \retval false on allocation failure (the vector is left untouched)
*/
#define vlc_vector_insert_hole(pv, index, count) \
vlc_vector_insert_hole_internal_(pv, vlc_vector_enforce_size_t_(index), \
vlc_vector_enforce_size_t_(count))
#define vlc_vector_insert_hole_internal_(pv, index, count) \
( \
vlc_vector_reserve(pv, (pv)->size + (count)) && \
( \
(index) == (pv)->size || \
( \
memmove(&(pv)->data[(index) + (count)], \
&(pv)->data[index], \
((pv)->size - (index)) * sizeof(*(pv)->data)), \
true \
) \
) && \
( \
(pv)->size += (count), \
true \
) \
)
/**
* Insert an item at the given index.
*
* The items in range [index; size-1] will be moved.
*
* \param pv a pointer to the vector
* \param index the index where the item is to be inserted
* \param item the item to append
* \retval true if no allocation failed
* \retval false on allocation failure (the vector is left untouched)
*/
#define vlc_vector_insert(pv, index, item) \
( \
vlc_vector_insert_hole(pv, index, 1) && \
( \
(pv)->data[index] = (item), \
true \
) \
)
/**
* Insert `count` items at the given index.
*
* The items in range [index; size-1] will be moved.
*
* \param pv a pointer to the vector
* \param index the index where the items are to be inserted
* \param items the items array to append
* \param count the number of items in the array
* \retval true if no allocation failed
* \retval false on allocation failure (the vector is left untouched)
*/
#define vlc_vector_insert_all(pv, index, items, count) \
( \
vlc_vector_check_same_ptr_type_((pv)->data, items), \
vlc_vector_insert_hole(pv, index, count) && \
( \
memcpy(&(pv)->data[index], items, (count) * sizeof(*(pv)->data)), \
true \
) \
)
/** Reverse a char array in place. */
static inline void
vlc_vector_reverse_array_(char *array, size_t len)
{
for (size_t i = 0; i < len / 2; ++i)
{
char c = array[i];
array[i] = array[len - i - 1];
array[len - i - 1] = c;
}
}
/**
* Right-rotate a (char) array in place.
*
* For example, left-rotating a char array containing {1, 2, 3, 4, 5, 6} with
* distance 4 will result in {5, 6, 1, 2, 3, 4}.
*
* Private.
*/
static inline void
vlc_vector_rotate_array_left_(char *array, size_t len, size_t distance)
{
vlc_vector_reverse_array_(array, distance);
vlc_vector_reverse_array_(&array[distance], len - distance);
vlc_vector_reverse_array_(array, len);
}
/**
* Right-rotate a (char) array in place.
*
* For example, left-rotating a char array containing {1, 2, 3, 4, 5, 6} with
* distance 2 will result in {5, 6, 1, 2, 3, 4}.
*
* Private.
*/
static inline void
vlc_vector_rotate_array_right_(char *array, size_t len, size_t distance)
{
vlc_vector_rotate_array_left_(array, len, len - distance);
}
/**
* Move items in a (char) array in place.
*
* Move slice [index, count] to target.
*/
static inline void
vlc_vector_move_(char *array, size_t index, size_t count, size_t target)
{
if (index < target)
vlc_vector_rotate_array_left_(&array[index], target - index + count,
count);
else
vlc_vector_rotate_array_right_(&array[target], index - target + count,
count);
}
/**
* Move a slice of items to a given target index.
*
* The items in range [index; count] will be moved so that the *new* position
* of the first item is `target`.
*
* \param pv a pointer to the vector
* \param index the index of the first item to move
* \param count the number of items to move
* \param target the new index of the moved slice
*/
#define vlc_vector_move_slice(pv, index, count, target) \
vlc_vector_move_slice_internal_(pv, \
vlc_vector_enforce_size_t_(index), \
vlc_vector_enforce_size_t_(count), \
vlc_vector_enforce_size_t_(target))
#define vlc_vector_move_slice_internal_(pv, index, count, target) \
vlc_vector_move_((char *) (pv)->data, \
(index) * sizeof((pv)->data[0]), \
(count) * sizeof((pv)->data[0]), \
(target) * sizeof((pv)->data[0]))
/**
* Move an item to a given target index.
*
* The items will be moved so that its *new* position is `target`.
*
* \param pv a pointer to the vector
* \param index the index of the item to move
* \param target the new index of the moved item
*/
#define vlc_vector_move(pv, index, target) \
vlc_vector_move_slice(pv, index, 1, target)
/**
* Remove a slice of items, without shrinking the array.
*
* If you have no good reason to use the _noshrink() version, use
* vlc_vector_remove_slice() instead.
*
* The items in range [index+count; size-1] will be moved.
*
* \param pv a pointer to the vector
* \param index the index of the first item to remove
* \param count the number of items to remove
*/
#define vlc_vector_remove_slice_noshrink(pv, index, count) \
vlc_vector_remove_slice_noshrink_internal_(pv, \
vlc_vector_enforce_size_t_(index), \
vlc_vector_enforce_size_t_(count))
#define vlc_vector_remove_slice_noshrink_internal_(pv, index, count) \
do { \
if ((index) + (count) < (pv)->size) \
memmove(&(pv)->data[index], \
&(pv)->data[(index) + (count)], \
((pv)->size - (index) - (count)) * sizeof(*(pv)->data)); \
(pv)->size -= (count); \
} while (0)
/**
* Remove a slice of items.
*
* The items in range [index+count; size-1] will be moved.
*
* \param pv a pointer to the vector
* \param index the index of the first item to remove
* \param count the number of items to remove
*/
#define vlc_vector_remove_slice(pv, index, count) \
do { \
vlc_vector_remove_slice_noshrink(pv, index, count); \
vlc_vector_autoshrink(pv); \
} while (0)
/**
* Remove an item, without shrinking the array.
*
* If you have no good reason to use the _noshrink() version, use
* vlc_vector_remove() instead.
*
* The items in range [index+1; size-1] will be moved.
*
* \param pv a pointer to the vector
* \param index the index of item to remove
*/
#define vlc_vector_remove_noshrink(pv, index) \
vlc_vector_remove_slice_noshrink(pv, index, 1)
/**
* Remove an item.
*
* The items in range [index+1; size-1] will be moved.
*
* \param pv a pointer to the vector
* \param index the index of item to remove
*/
#define vlc_vector_remove(pv, index) \
do { \
vlc_vector_remove_noshrink(pv, index); \
vlc_vector_autoshrink(pv); \
} while (0)
/**
* Remove an item.
*
* The removed item is replaced by the last item of the vector.
*
* This does not preserve ordering, but is O(1). This is useful when the order
* of items is not meaningful.
*
* \param pv a pointer to the vector
* \param index the index of item to remove
*/
#define vlc_vector_swap_remove(pv, index) \
(pv)->data[index] = (pv)->data[--(pv)->size]
/**
* Return the index of an item.
*
* Iterate over all items to find a given item.
*
* Use only for vectors of primitive types or pointers.
*
* The result is written to `*(pidx)`:
* - the index of the item if it is found;
* - -1 if it is not found.
*
* \param pv a pointer to the vector
* \param item the item to find (compared with ==)
* \param a pointer to the result (ssize_t *) [OUT]
*/
#define vlc_vector_index_of(pv, item, pidx) \
do { \
ssize_t *out = pidx; /* warning/error on type mismatch */ \
size_t vlc_vector_find_idx_; \
for (vlc_vector_find_idx_ = 0; \
vlc_vector_find_idx_ < (pv)->size; \
++vlc_vector_find_idx_) \
if ((pv)->data[vlc_vector_find_idx_] == (item)) \
break; \
*out = vlc_vector_find_idx_ == (pv)->size ? -1 : \
(ssize_t) vlc_vector_find_idx_; \
} while (0)
/**
* For-each loop.
*
* Use only for vectors of primitive types or pointers (every struct would be
* copied for a vector of structs).
*
* \param item the iteration variable [OUT]
* \param pv a pointer to the vector [OUT]
*/
#define vlc_vector_foreach(item, pv) \
for (size_t vlc_vector_idx_##item = 0; \
vlc_vector_idx_##item < (pv)->size && \
((item) = (pv)->data[vlc_vector_idx_##item], true); \
++vlc_vector_idx_##item)
/** @} */
#endif
......@@ -97,6 +97,7 @@ pluginsinclude_HEADERS = \
../include/vlc_tls.h \
../include/vlc_url.h \
../include/vlc_variables.h \
../include/vlc_vector.h \
../include/vlc_viewpoint.h \
../include/vlc_vlm.h \
../include/vlc_video_splitter.h \
......@@ -530,7 +531,8 @@ check_PROGRAMS = \
test_xmlent \
test_headers \
test_mrl_helpers \
test_arrays
test_arrays \
test_vector
TESTS = $(check_PROGRAMS) check_symbols
......@@ -553,6 +555,7 @@ test_xmlent_SOURCES = test/xmlent.c
test_headers_SOURCES = test/headers.c
test_mrl_helpers_SOURCES = test/mrl_helpers.c
test_arrays_SOURCES = test/arrays.c
test_vector_SOURCES = test/vector.c
AM_LDFLAGS = -no-install
LDADD = libvlccore.la \
......
/*****************************************************************************
* vector.c : Test for vlc_vector macros
*****************************************************************************
* Copyright (C) 2018 VLC authors and VideoLAN
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#undef NDEBUG
#include <assert.h>
#include <vlc_common.h>
#include <vlc_vector.h>
static void test_vector_insert_remove(void)
{
struct VLC_VECTOR(int) vec = VLC_VECTOR_INITIALIZER;
bool ok;
ok = vlc_vector_push(&vec, 42);
assert(ok);
assert(vec.data[0] == 42);
assert(vec.size == 1);
ok = vlc_vector_push(&vec, 37);
assert(ok);
assert(vec.size == 2);
assert(vec.data[0] == 42);
assert(vec.data[1] == 37);
ok = vlc_vector_insert(&vec, 1, 100);
assert(ok);
assert(vec.size == 3);
assert(vec.data[0] == 42);
assert(vec.data[1] == 100);
assert(vec.data[2] == 37);
ok = vlc_vector_push(&vec, 77);
assert(ok);
assert(vec.size == 4);
assert(vec.data[0] == 42);
assert(vec.data[1] == 100);
assert(vec.data[2] == 37);
assert(vec.data[3] == 77);
vlc_vector_remove(&vec, 1);
assert(vec.size == 3);
assert(vec.data[0] == 42);
assert(vec.data[1] == 37);
assert(vec.data[2] == 77);
vlc_vector_clear(&vec);
assert(vec.size == 0);
vlc_vector_destroy(&vec);
}
static void test_vector_push_array(void)
{
struct VLC_VECTOR(int) vec = VLC_VECTOR_INITIALIZER;
bool ok;
ok = vlc_vector_push(&vec, 3); assert(ok);
ok = vlc_vector_push(&vec, 14); assert(ok);
ok = vlc_vector_push(&vec, 15); assert(ok);
ok = vlc_vector_push(&vec, 92); assert(ok);
ok = vlc_vector_push(&vec, 65); assert(ok);
assert(vec.size == 5);
int items[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
ok = vlc_vector_push_all(&vec, items, 8);
assert(ok);
assert(vec.size == 13);