TscVector is a generic dynamic vector library for C. It
stores application-supplied element pointers in a contiguous array while
keeping allocation, capacity management, growth policy, and bookkeeping
private.
The API follows the same general style and ownership conventions as
the other TSC container libraries, including TscList,
TscTree, TscHash, and
TscDeque.
void * elementsNULL elements permittedThe vector itself is opaque:
typedef struct TscVector TscVector;Applications do not have access to the internal element array, capacity, growth policy, or bookkeeping.
The logical vector consists of every index from zero through
count - 1.
Each logical position contains a void * value and may
contain either a non-NULL pointer or NULL.
Allocated capacity beyond count is internal storage and
is not part of the logical vector.
For example:
count = 4
capacity = 8
index 0 1 2 3
+-------+-------+-------+-------+-------+-------+-------+-------+
elements | A | B | NULL | D | | | | |
+-------+-------+-------+-------+-------+-------+-------+-------+
logical vector | unused capacity |
Index 2 exists and contains NULL.
Indexes 4 through 7 do not logically exist even though storage has been allocated for them.
Create a vector with:
TscVector *vector =
tscVectorCreate(freeElement);The element free callback is optional and may be
NULL.
Its type is:
typedef void (*TscVectorFreeFunction)(
void *element);When configured, the callback is used by operations that destroy elements, including set, delete, resize when shrinking, clear, and destroy.
For example:
TscVector *vector =
tscVectorCreate(free);may be used when every non-NULL element stored in the vector is
individually allocated with malloc().
A newly created vector is empty:
count = 0
capacity = 0
The element array is allocated only when storage is required.
The vector stores application-supplied pointers:
void *element;The library stores the pointer itself and does not copy the object it references.
NULL is a valid logical element value.
This creates an important distinction between a logical position
containing NULL and an index that does not exist.
For example, if:
count = 3
index 0 1 2
+-------+-------+-------+
elements | A | NULL | C |
+-------+-------+-------+
then:
tscVectorGet(vector, 1);returns NULL with status:
TSC_VECTOR_STATUS_OKbecause index 1 exists and contains a NULL element.
However:
tscVectorGet(vector, 3);returns NULL with status:
TSC_VECTOR_ERROR_OUT_OF_RANGEbecause index 3 does not exist.
When the distinction matters, inspect the retained status with:
tscVectorGetStatus(vector);TscVector maintains two separate sizes.
The logical number of elements is returned by:
size_t count =
tscVectorCount(vector);The number of currently allocated element slots is returned by:
size_t capacity =
tscVectorCapacity(vector);The following relationship is always maintained:
count <= capacity
Only indexes less than count are valid logical vector
positions.
Capacity beyond count is an implementation resource and must not be interpreted as containing vector elements.
For a NULL vector pointer:
tscVectorCount(NULL)and:
tscVectorCapacity(NULL)return zero.
The empty state can be tested with:
int empty =
tscVectorIsEmpty(vector);The function returns nonzero when count is zero and zero
when at least one logical element exists.
A NULL vector pointer is also reported as empty.
When an operation requires more element slots than the current capacity, the vector automatically grows its internal element array.
The exact growth policy is private to the implementation and is not part of the public API contract.
Applications should therefore not depend on a particular capacity after an automatic growth operation.
The only guarantee is:
capacity >= count
after a successful operation.
Applications that know their expected storage requirements may use
tscVectorReserve() to reduce repeated reallocations.
Ensure capacity for at least a specified number of elements with:
tscVectorReserve(vector, capacity);For example:
tscVectorReserve(vector, 1000);ensures that the vector can hold at least 1000 logical elements before another capacity increase is required.
Reserve does not change the logical element count.
For example:
before:
count = 3
capacity = 8
after tscVectorReserve(vector, 1000):
count = 3
capacity >= 1000
Calling reserve with a value less than or equal to the existing capacity is successful and leaves the allocation unchanged.
tscVectorReserve() never shrinks the vector.
Append an element to the logical end of the vector with:
TscVectorStatus status =
tscVectorAppend(vector, element);The new element is placed at the previous value of
count.
For example:
before:
count = 3
[ A ][ B ][ C ]
tscVectorAppend(vector, D);
after:
count = 4
[ A ][ B ][ C ][ D ]
NULL elements are permitted:
tscVectorAppend(vector, NULL);If additional capacity is required, the vector grows automatically.
Append is an amortized O(1) operation.
Insert an element at an arbitrary logical position with:
TscVectorStatus status =
tscVectorInsert(vector, index, element);Valid insertion indexes are:
0 through count inclusive
The existing element at index and all later elements are
shifted one position toward the end.
For example:
before:
[ A ][ B ][ C ][ D ]
tscVectorInsert(vector, 2, X);
after:
[ A ][ B ][ X ][ C ][ D ]
Insertion at index zero inserts at the beginning.
Insertion at:
tscVectorCount(vector)is equivalent to append.
An insertion index greater than count fails with:
TSC_VECTOR_ERROR_OUT_OF_RANGENULL elements are permitted.
Because later elements may need to be shifted, arbitrary insertion is O(n).
Return the element stored at an existing logical position with:
void *element =
tscVectorGet(vector, index);Valid indexes are:
0 through count - 1
The vector is not modified.
If the index is outside the logical vector, NULL is
returned and status is set to:
TSC_VECTOR_ERROR_OUT_OF_RANGEBecause a valid vector element may itself contain NULL,
applications should inspect status when the distinction is
important.
For example:
void *element =
tscVectorGet(vector, index);
if (element == NULL)
{
if (tscVectorGetStatus(vector) ==
TSC_VECTOR_ERROR_OUT_OF_RANGE)
{
/* index does not exist */
}
else
{
/* index exists and contains NULL */
}
}Indexed access is O(1).
Change the element stored at an existing logical position with:
tscVectorSet(vector, index, element);Set() does not insert a new position and does not change
count.
For example:
before:
[ A ][ B ][ C ][ D ]
tscVectorSet(vector, 2, X);
after:
[ A ][ B ][ X ][ D ]
Valid indexes are:
0 through count - 1
If the previous element is non-NULL, differs from the new pointer, and a free callback was configured, the previous element is destroyed using that callback.
For example:
tscVectorSet(vector, 2, newObject);replaces the previous object at index 2 and transfers ownership of
newObject to the vector under the normal ownership
rules.
Setting an element to the same pointer already stored at that index is a successful no-op.
This rule prevents the vector from freeing an object and then retaining the same now-invalid pointer.
Setting an element to NULL is permitted.
For example:
tscVectorSet(vector, 2, NULL);leaves index 2 as a valid logical position containing a NULL element.
A remove deletes a logical position and returns its element without invoking the configured free callback.
Remove an arbitrary element with:
void *element =
tscVectorRemove(vector, index);Later elements are shifted one position toward the beginning.
For example:
before:
[ A ][ B ][ C ][ D ]
element = tscVectorRemove(vector, 1);
returned:
B
after:
[ A ][ C ][ D ]
Ownership of the returned element belongs to the application.
If the index is outside the logical vector, the function returns
NULL and sets:
TSC_VECTOR_ERROR_OUT_OF_RANGEBecause a successfully removed element may itself be
NULL, use tscVectorGetStatus() when the
distinction matters.
Arbitrary removal is O(n) because later elements may need to be shifted.
A delete removes a logical position and destroys its element using the configured free callback when appropriate.
Delete an arbitrary element with:
tscVectorDelete(vector, index);Later elements are shifted one position toward the beginning.
The distinction between remove and delete follows the normal TSC container ownership convention:
Remove -> return the element to the application
Delete -> destroy the element
If the index is outside the logical vector, delete fails with:
TSC_VECTOR_ERROR_OUT_OF_RANGEThe final logical element can be removed efficiently with:
void *element =
tscVectorPopBack(vector);This removes and returns the element without invoking the configured free callback.
Because no remaining elements need to be shifted,
tscVectorPopBack() is O(1).
If the vector is empty, it returns NULL and sets:
TSC_VECTOR_ERROR_EMPTYA successfully removed NULL element also returns NULL,
but status remains:
TSC_VECTOR_STATUS_OKTo remove and destroy the final element instead, use:
tscVectorDeleteBack(vector);This invokes the configured free callback for the removed non-NULL element when appropriate.
Inspect the first logical element without removing it with:
void *element =
tscVectorFirst(vector);Inspect the last logical element with:
void *element =
tscVectorLast(vector);Neither operation changes the vector.
If the vector is empty, the function returns NULL and
sets:
TSC_VECTOR_ERROR_EMPTYBecause a valid first or last element may itself be
NULL, inspect the retained status when the distinction
matters.
Both operations are O(1).
Change the logical number of vector positions with:
tscVectorResize(vector, count);Resize changes the logical element count rather than merely changing allocation capacity.
When the vector grows, every newly created logical position is
initialized to NULL.
For example:
before:
count = 3
[ A ][ B ][ C ]
tscVectorResize(vector, 6);
after:
count = 6
[ A ][ B ][ C ][NULL][NULL][NULL]
Indexes 3, 4, and 5 now exist and may be accessed with
tscVectorGet() or changed with
tscVectorSet().
If additional capacity is required, the internal array grows automatically.
When the vector shrinks, positions beyond the new count cease to exist.
For example:
before:
count = 6
[ A ][ B ][ C ][ D ][ E ][ F ]
tscVectorResize(vector, 3);
after:
count = 3
[ A ][ B ][ C ]
Discarded non-NULL elements are destroyed using the configured free callback when appropriate.
Shrinking the logical vector does not reduce allocated capacity.
Unused allocated capacity can be explicitly released with:
tscVectorShrinkToFit(vector);This reduces capacity to the minimum required for the current logical element count.
For example:
before:
count = 20
capacity = 128
after tscVectorShrinkToFit(vector):
count = 20
capacity = 20
The logical elements and their order are unchanged.
If the vector is empty, shrink-to-fit may release the element array completely:
count = 0
capacity = 0
Normal remove, delete, pop, resize, and clear operations do not automatically shrink capacity.
This allows allocated storage to be reused efficiently.
The library can walk every logical element in increasing index order using a callback:
typedef int (*TscVectorWalkFunction)(
void *element,
void *context);Call:
tscVectorWalk(vector, callback, context);Traversal order is:
0, 1, 2, ... count - 1
The callback receives the stored element pointer, including NULL if the logical position contains a NULL element.
The callback returns zero to continue or nonzero to stop the walk successfully.
For example:
static int printElement(
void *element,
void *context)
{
(void)context;
if (element == NULL)
printf("(null)\n");
else
printf("%s\n", (const char *)element);
return 0;
}Then:
tscVectorWalk(vector, printElement, NULL);Early termination is considered successful and
tscVectorWalk() returns:
TSC_VECTOR_STATUS_OKModifying the vector during a walk is not supported.
Remove all logical elements while retaining allocated capacity with:
tscVectorClear(vector);The configured free callback is invoked for every non-NULL logical element when appropriate.
After a successful clear:
count = 0
but capacity is retained.
For example:
before:
count = 100
capacity = 128
after tscVectorClear(vector):
count = 0
capacity = 128
The vector remains valid and may immediately be reused without reallocating until the retained capacity is exceeded.
To also release unused element-array storage, call:
tscVectorShrinkToFit(vector);after clearing.
Destroy the complete vector with:
tscVectorDestroy(vector);All remaining non-NULL logical elements are destroyed using the configured free callback when appropriate.
The internal element array is released, followed by the vector object itself.
Passing NULL to tscVectorDestroy() is
permitted.
Operations retain their completion status in the vector object:
TscVectorStatus status =
tscVectorGetStatus(vector);A printable description is available with:
const char *message =
tscVectorStatusString(status);Status values currently include:
TSC_VECTOR_STATUS_OK
TSC_VECTOR_ERROR_INVALID_ARGUMENT
TSC_VECTOR_ERROR_OUT_OF_MEMORY
TSC_VECTOR_ERROR_OUT_OF_RANGE
TSC_VECTOR_ERROR_EMPTYTheir meanings are:
| Status | Meaning |
|---|---|
TSC_VECTOR_STATUS_OK |
operation completed successfully |
TSC_VECTOR_ERROR_INVALID_ARGUMENT |
an invalid argument was supplied |
TSC_VECTOR_ERROR_OUT_OF_MEMORY |
required internal storage could not be allocated |
TSC_VECTOR_ERROR_OUT_OF_RANGE |
an index does not identify a valid logical position |
TSC_VECTOR_ERROR_EMPTY |
an operation requiring an existing end element was attempted on an empty vector |
#include "tsc_vector.h"
#include <stdio.h>
int main(void)
{
TscVector *vector;
size_t index;
vector = tscVectorCreate(NULL);
if (vector == NULL)
return 1;
tscVectorAppend(vector, "one");
tscVectorAppend(vector, "two");
tscVectorAppend(vector, "three");
tscVectorInsert(vector, 1, "one and a half");
for (index = 0;
index < tscVectorCount(vector);
++index)
{
printf(
"%zu: %s\n",
index,
(const char *)tscVectorGet(vector, index));
}
tscVectorDestroy(vector);
return 0;
}Output:
0: one
1: one and a half
2: two
3: three
Because append and pop-back are efficient, TscVector can
also be used as a simple LIFO stack.
Push elements with:
tscVectorAppend(vector, item);and pop them with:
item = tscVectorPopBack(vector);For example:
tscVectorAppend(vector, item1);
tscVectorAppend(vector, item2);
tscVectorAppend(vector, item3);
item = tscVectorPopBack(vector); /* item3 */
item = tscVectorPopBack(vector); /* item2 */
item = tscVectorPopBack(vector); /* item1 */For applications requiring efficient insertion and removal at both
ends, TscDeque is generally a better choice.
The primary vector operations have the following expected complexity:
| Operation | Complexity |
|---|---|
tscVectorGet() |
O(1) |
tscVectorSet() |
O(1) |
tscVectorFirst() |
O(1) |
tscVectorLast() |
O(1) |
tscVectorCount() |
O(1) |
tscVectorCapacity() |
O(1) |
tscVectorIsEmpty() |
O(1) |
tscVectorAppend() |
amortized O(1) |
tscVectorPopBack() |
O(1) |
tscVectorDeleteBack() |
O(1) |
tscVectorInsert() |
O(n) |
tscVectorRemove() |
O(n) |
tscVectorDelete() |
O(n) |
tscVectorWalk() |
O(n) |
tscVectorClear() |
O(n) |
tscVectorResize() |
O(n) when elements must be initialized or destroyed |
tscVectorReserve() |
O(n) when reallocation is required |
tscVectorShrinkToFit() |
O(n) when reallocation is required |
TscVector is intended for ordered collections where
indexed access and compact contiguous pointer storage are important.
Use TscVector when:
Other TSC containers may be more appropriate for different access patterns.
Use TscList when arbitrary insertion and removal through
linked structure is more important than indexed access.
Use TscDeque when efficient insertion and removal are
required at both ends.
Use TscTree when ordered key-based lookup is
required.
Use TscHash when fast unordered key-based lookup is
required.
TscVector is a dynamic contiguous array of pointers, not
an array of application objects.
The vector stores void * values supplied by the
application.
Every index less than count is a valid logical position,
including positions containing NULL.
Allocated slots at indexes greater than or equal to
count are unused capacity and are not logical vector
elements.
The distinction between logical size and capacity is fundamental to the API.
The vector does not automatically shrink its allocation as elements
are removed. This allows storage to be reused efficiently and avoids
repeated allocation activity. Applications that need to return unused
storage to the allocator may explicitly call
tscVectorShrinkToFit().
The current implementation is not designed to provide synchronization between threads. Applications requiring concurrent access should provide appropriate external locking.