J
jacob navia
Lists
-----
Single linked lists use a single pointer to the next element. The data
for the element comes right behind that pointer to avoid the overhead
that yet another pointer would represent.
So, we use this structure:
typedef struct _list_element {
struct _list_element *Next;
char Data[MINIMUM_ARRAY_INDEX]; // See below
} list_element;
The list header uses this structure to store the elements. As you can
see, there is no space wasted in a pointer to the element stored. The
element stored is placed just behind the Next pointer. The downside of
this decision is that we can’t recycle this object to store other
different objects of different size.
The constant MINIMUM ARRAY INDEX is defined as 1 if we are compiling in
C90 mode or as nothing if we are compiling in C99 mode. In C99 mode we
have a flexible structure, that consists of a fixed and a variable part.
The fixed part is the pointer to the next element. The variable part is
the object we are storing in the list.
Alignment
---------
Some machines require that data be stored at particular addresses,
always a multiple of two. For instance SPARC machines require that
doubles be aligned at a multiple of 8. The structure for our list
element above would provoke a crash when used to store doubles 4.
In those machines the list element structure is defined as follows:
typedef struct _ListElement {
struct _ListElement *Next;
#ifdef SPARC32
void *alignment;
#endif
char Data[MINIMUM_ARRAY_INDEX];
} ListElement;
This assumes that sizeof(void *) is 4.
In machines that handle unaligned data gracefully without crashing
alignment re- quirements aren’t useless, since in most cases they
provoke a performance loss.
The list header
---------------
struct _List {
ListInterface *VTable;
size_t count;
unsigned Flags;
unsigned timestamp;
size_t ElementSize;
list_element *Last;
list_element *First;
CompareFunction Compare;
ErrorFunction RaiseError;
ContainerHeap *Heap;
ContainerAllocator *Allocator;
};
In the public containers.h header file we refer always to an abstract
structure List. We define it here. This schema allows other
implementation to use the same header with maybe radically different
implementations of their data structure.
The individual fields are as follows:
1.
1.1: Vtable. Pointer to a table of methods for this container
1.2: count. Number of elements in the list
1.3: Flags. Bit fields for flags about this list.
1.4: ElementSize. Number of bytes the data occupies.
2. timestamp. This field is incremented at each modification of the
list, and allows the iterators to detect if the container changes during
an iteration: they store the value of this field at the start of the
iteration, and before each iteration they compare it with its current
value. If there are any changes, they return NULL .
3. Last. Stores a pointer to the last element of the list. This allows
the addition of an element at the end of the list to be fast, avoiding a
complete rescan of the list. This field is an optimization, all
algorithms of a single linked list would work without this field.
4. First. The start of the linked list.
5. Compare. A comparison function for the type of elements stored in the
list.
6. RaiseError. A function that will be called when an error occurs. This
field is necessary only if you want to keep the flexibility of having a
different error function for each list that the client software builds.
An alternative implementation would store a pointer to an error function
in the interface.
7. Allocator. A set of functions that allocates memory for this list. In
an implementation that needs less flexibility and is more interested in
saving space it could be replaced by the default allocator.
The sample implementation has certainly a quite voluminous header
because of a design decision to keep things very flexible. Other
implementations could trim most of the fields, and an absolute minimal
implementation would trim Last, Compare, RaiseError, Heap,
and Allocator. If the implementation assumes that only one iterator per
container is allowed, the timestamp field could be replace by a single
bit (’changed’) in the Flags field.
The list interface
------------------
The list interface is a table of functions that holds all the methods
of the container. For the list container we have:
typedef struct tagListInterface {
int (*Add)(List *L,const void *newval);
int (*AddRange)(List *L, size_t n,const void *data);
void *(*Advance)(ListElement **pListElement);
int (*Append)(List *l1,List *l2);
int (*Apply)(List *L,int(Applyfn)(void *,void *),void *arg);
void *(*Back)(const List *l);
int (*Clear)(List *L);
int (*Contains)(const List *L,const void *element);
List *(*Copy)(const List *L);
int (*CopyElement)(const List *list,size_t idx,void *OutBuffer);
List *(*Create)(size_t element_size);
List *(*CreateWithAllocator)(size_t elementsize,
const ContainerAllocator *mm);
void *(*ElementData)(ListElement *le);
int (*Equal)(const List *l1,const List *l2);
int (*Erase)(List *L,const void *);
int (*EraseAll)(List *l,const void *);
int (*EraseAt)(List *L,size_t idx);
int (*EraseRange)(List *L,size_t start,size_t end);
int (*Finalize)(List *L);
ListElement *(*FirstElement)(List *l);
void *(*Front)(const List *l);
const ContainerAllocator *(*GetAllocator)(const List *list);
void *(*GetElement)(const List *L,size_t idx);
size_t (*GetElementSize)(const List *l);
unsigned (*GetFlags)(const List *L);
ContainerHeap *(*GetHeap)(const List *l);
List *(*GetRange)(const List *l,size_t start,size_t end);
int (*IndexOf)(const List *L,const void *SearchedElement,
void *ExtraArgs,size_t *result);
List *(*Init)(List *aList,size_t element_size);
int (*InitIterator)(List *L,void *buf);
List *(*InitWithAllocator)(List *aList,size_t element_size,
const ContainerAllocator *mm);
List *(*InitializeWith)(size_t elementSize,size_t n,
const void *data);
int (*InsertAt)(List *L,size_t idx,const void *newval);
int (*InsertIn)(List *l, size_t idx,List *newData);
ListElement *(*LastElement)(List *l);
List *(*Load)(FILE *stream, ReadFunction loadFn,void *arg);
Iterator *(*NewIterator)(List *L);
ListElement *(*NextElement)(ListElement *le);
int (*PopFront)(List *L,void *result);
int (*PushFront)(List *L,const void *str);
int (*RemoveRange)(List *l,size_t start, size_t end);
int (*ReplaceAt)(List *L,size_t idx,const void *newval);
int (*Reverse)(List *l);
int (*RotateLeft)(List *l, size_t n);
int (*RotateRight)(List *l,size_t n);
int (*Save)(const List *L,FILE *stream, SaveFunction saveFn,
void *arg);
int (*Select)(List *src,const Mask *m);
List *(*SelectCopy)(const List *src,const Mask *m);
CompareFunction (*SetCompareFunction)(List *l,CompareFunction fn);
DestructorFunction (*SetDestructor)(List *v,DestructorFunction fn);
int (*SetElementData)(List *l, ListElement *le,void *data);
ErrorFunction (*SetErrorFunction)(List *L,ErrorFunction);
unsigned (*SetFlags)(List *L,unsigned flags);
size_t (*Size)(const List *L);
size_t (*Sizeof)(const List *l);
size_t (*SizeofIterator)(const List *);
ListElement *(*Skip)(ListElement *l,size_t n);
int (*Sort)(List *l);
List *(*SplitAfter)(List *l, ListElement *pt);
int (*UseHeap)(List *L, const ContainerAllocator *m);
int (*deleteIterator)(Iterator *);
} ListInterface;
Note that ther is a single Vtable for all lists of the same type.
All list just a pointer to a common method table.
In a future post I will explain this functions in detail and propose
code for them.
Feel free to comment on this.
jacob
-----
Single linked lists use a single pointer to the next element. The data
for the element comes right behind that pointer to avoid the overhead
that yet another pointer would represent.
So, we use this structure:
typedef struct _list_element {
struct _list_element *Next;
char Data[MINIMUM_ARRAY_INDEX]; // See below
} list_element;
The list header uses this structure to store the elements. As you can
see, there is no space wasted in a pointer to the element stored. The
element stored is placed just behind the Next pointer. The downside of
this decision is that we can’t recycle this object to store other
different objects of different size.
The constant MINIMUM ARRAY INDEX is defined as 1 if we are compiling in
C90 mode or as nothing if we are compiling in C99 mode. In C99 mode we
have a flexible structure, that consists of a fixed and a variable part.
The fixed part is the pointer to the next element. The variable part is
the object we are storing in the list.
Alignment
---------
Some machines require that data be stored at particular addresses,
always a multiple of two. For instance SPARC machines require that
doubles be aligned at a multiple of 8. The structure for our list
element above would provoke a crash when used to store doubles 4.
In those machines the list element structure is defined as follows:
typedef struct _ListElement {
struct _ListElement *Next;
#ifdef SPARC32
void *alignment;
#endif
char Data[MINIMUM_ARRAY_INDEX];
} ListElement;
This assumes that sizeof(void *) is 4.
In machines that handle unaligned data gracefully without crashing
alignment re- quirements aren’t useless, since in most cases they
provoke a performance loss.
The list header
---------------
struct _List {
ListInterface *VTable;
size_t count;
unsigned Flags;
unsigned timestamp;
size_t ElementSize;
list_element *Last;
list_element *First;
CompareFunction Compare;
ErrorFunction RaiseError;
ContainerHeap *Heap;
ContainerAllocator *Allocator;
};
In the public containers.h header file we refer always to an abstract
structure List. We define it here. This schema allows other
implementation to use the same header with maybe radically different
implementations of their data structure.
The individual fields are as follows:
1.
1.1: Vtable. Pointer to a table of methods for this container
1.2: count. Number of elements in the list
1.3: Flags. Bit fields for flags about this list.
1.4: ElementSize. Number of bytes the data occupies.
2. timestamp. This field is incremented at each modification of the
list, and allows the iterators to detect if the container changes during
an iteration: they store the value of this field at the start of the
iteration, and before each iteration they compare it with its current
value. If there are any changes, they return NULL .
3. Last. Stores a pointer to the last element of the list. This allows
the addition of an element at the end of the list to be fast, avoiding a
complete rescan of the list. This field is an optimization, all
algorithms of a single linked list would work without this field.
4. First. The start of the linked list.
5. Compare. A comparison function for the type of elements stored in the
list.
6. RaiseError. A function that will be called when an error occurs. This
field is necessary only if you want to keep the flexibility of having a
different error function for each list that the client software builds.
An alternative implementation would store a pointer to an error function
in the interface.
7. Allocator. A set of functions that allocates memory for this list. In
an implementation that needs less flexibility and is more interested in
saving space it could be replaced by the default allocator.
The sample implementation has certainly a quite voluminous header
because of a design decision to keep things very flexible. Other
implementations could trim most of the fields, and an absolute minimal
implementation would trim Last, Compare, RaiseError, Heap,
and Allocator. If the implementation assumes that only one iterator per
container is allowed, the timestamp field could be replace by a single
bit (’changed’) in the Flags field.
The list interface
------------------
The list interface is a table of functions that holds all the methods
of the container. For the list container we have:
typedef struct tagListInterface {
int (*Add)(List *L,const void *newval);
int (*AddRange)(List *L, size_t n,const void *data);
void *(*Advance)(ListElement **pListElement);
int (*Append)(List *l1,List *l2);
int (*Apply)(List *L,int(Applyfn)(void *,void *),void *arg);
void *(*Back)(const List *l);
int (*Clear)(List *L);
int (*Contains)(const List *L,const void *element);
List *(*Copy)(const List *L);
int (*CopyElement)(const List *list,size_t idx,void *OutBuffer);
List *(*Create)(size_t element_size);
List *(*CreateWithAllocator)(size_t elementsize,
const ContainerAllocator *mm);
void *(*ElementData)(ListElement *le);
int (*Equal)(const List *l1,const List *l2);
int (*Erase)(List *L,const void *);
int (*EraseAll)(List *l,const void *);
int (*EraseAt)(List *L,size_t idx);
int (*EraseRange)(List *L,size_t start,size_t end);
int (*Finalize)(List *L);
ListElement *(*FirstElement)(List *l);
void *(*Front)(const List *l);
const ContainerAllocator *(*GetAllocator)(const List *list);
void *(*GetElement)(const List *L,size_t idx);
size_t (*GetElementSize)(const List *l);
unsigned (*GetFlags)(const List *L);
ContainerHeap *(*GetHeap)(const List *l);
List *(*GetRange)(const List *l,size_t start,size_t end);
int (*IndexOf)(const List *L,const void *SearchedElement,
void *ExtraArgs,size_t *result);
List *(*Init)(List *aList,size_t element_size);
int (*InitIterator)(List *L,void *buf);
List *(*InitWithAllocator)(List *aList,size_t element_size,
const ContainerAllocator *mm);
List *(*InitializeWith)(size_t elementSize,size_t n,
const void *data);
int (*InsertAt)(List *L,size_t idx,const void *newval);
int (*InsertIn)(List *l, size_t idx,List *newData);
ListElement *(*LastElement)(List *l);
List *(*Load)(FILE *stream, ReadFunction loadFn,void *arg);
Iterator *(*NewIterator)(List *L);
ListElement *(*NextElement)(ListElement *le);
int (*PopFront)(List *L,void *result);
int (*PushFront)(List *L,const void *str);
int (*RemoveRange)(List *l,size_t start, size_t end);
int (*ReplaceAt)(List *L,size_t idx,const void *newval);
int (*Reverse)(List *l);
int (*RotateLeft)(List *l, size_t n);
int (*RotateRight)(List *l,size_t n);
int (*Save)(const List *L,FILE *stream, SaveFunction saveFn,
void *arg);
int (*Select)(List *src,const Mask *m);
List *(*SelectCopy)(const List *src,const Mask *m);
CompareFunction (*SetCompareFunction)(List *l,CompareFunction fn);
DestructorFunction (*SetDestructor)(List *v,DestructorFunction fn);
int (*SetElementData)(List *l, ListElement *le,void *data);
ErrorFunction (*SetErrorFunction)(List *L,ErrorFunction);
unsigned (*SetFlags)(List *L,unsigned flags);
size_t (*Size)(const List *L);
size_t (*Sizeof)(const List *l);
size_t (*SizeofIterator)(const List *);
ListElement *(*Skip)(ListElement *l,size_t n);
int (*Sort)(List *l);
List *(*SplitAfter)(List *l, ListElement *pt);
int (*UseHeap)(List *L, const ContainerAllocator *m);
int (*deleteIterator)(Iterator *);
} ListInterface;
Note that ther is a single Vtable for all lists of the same type.
All list just a pointer to a common method table.
In a future post I will explain this functions in detail and propose
code for them.
Feel free to comment on this.
jacob