You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
106 lines
3.8 KiB
106 lines
3.8 KiB
3 months ago
|
Generic data structures and algorithms
|
||
|
======================================
|
||
|
|
||
|
The C preprocessor is a very powerful tool. One handy way to use it
|
||
|
can be generating generic data structures and algorithms. Here you can
|
||
|
find some conventions that are used in all such generic structures in
|
||
|
libUCW, and also hints for use of these structures.
|
||
|
|
||
|
- <<idea,General idea>>
|
||
|
- <<use,How to use them>>
|
||
|
- <<implement,How it is implemented>>
|
||
|
- Modules with generics
|
||
|
* <<growbuf:gbuf,`gbuf.h`>>
|
||
|
* <<sort:,Sorting>>
|
||
|
* <<binheap:,`binheap.h`>>
|
||
|
* <<hashtable:,`hashtable.h`>>
|
||
|
|
||
|
// TODO The module list
|
||
|
|
||
|
[[idea]]
|
||
|
General idea
|
||
|
------------
|
||
|
|
||
|
The idea is simple. If you have some code, you can customize it a
|
||
|
little by preprocessor macros. You can change constants, data types it
|
||
|
operates on, whole expressions, or you can compile parts of the code
|
||
|
conditionally. You can generate new function names using macros.
|
||
|
|
||
|
So if you provide few macros for data types, function names and
|
||
|
parameters and include some code using them, it gets modified by it
|
||
|
and a code for a specific data type is created. Then you can provide
|
||
|
new macros and include it again, to get another version of the code,
|
||
|
with different function names and types.
|
||
|
|
||
|
[[use]]
|
||
|
How to use them
|
||
|
---------------
|
||
|
|
||
|
The use is best explained with an example, so we will suppose there
|
||
|
is a header file `array.h`, which contains a generic array data type
|
||
|
and an indexing function, which returns a pointer to n'th element.
|
||
|
|
||
|
To get an array of integers, we need to provide macro for used data
|
||
|
type and macro that will provide prefixes for identifier names. Then
|
||
|
we include the file. Then we could get another array with unsigned
|
||
|
integers, so we will do the same:
|
||
|
|
||
|
#define ARRAY_TYPE int
|
||
|
#define ARRAY_PREFIX(name) intarray_##name
|
||
|
#include <array.h>
|
||
|
|
||
|
#define ARRAY_TYPE uint
|
||
|
#define ARRAY_PREFIX(name) uintarray_##name
|
||
|
#include <array.h>
|
||
|
|
||
|
This will generate the data types (presumably `intarray_t` and
|
||
|
`uintarray_t`) and the index functions (`intarray_index` and
|
||
|
`uintarray_index`). We can use them like anything else.
|
||
|
|
||
|
Maybe the `ARRAY_PREFIX` deserves some attention. When the header file
|
||
|
wants to generate an identifier, it uses this macro with
|
||
|
some name. Then the macro takes the name, adds a prefix to it and
|
||
|
returns the new identifier, so `ARRAY_PREFIX(t)` will generate
|
||
|
`intarray_t` in the first case and `uintarray_t` in the second. This
|
||
|
allows having more than one instance of the same data structure or
|
||
|
algorithm, because it generates different identifiers for them.
|
||
|
|
||
|
A similar macro is needed for every generic header in libUCW.
|
||
|
|
||
|
[[implement]]
|
||
|
How it is implemented
|
||
|
---------------------
|
||
|
|
||
|
For those who want to write their own or are just interested, how it
|
||
|
works, here is the `array.h` header and some description to it.
|
||
|
|
||
|
#define ARRAY_A_TYPE ARRAY_PREFIX(t)
|
||
|
typedef ARRAY_TYPE *ARRAY_A_TYPE
|
||
|
|
||
|
static ARRAY_TYPE *ARRAY_PREFIX(index)(ARRAY_A_TYPE array, uint index)
|
||
|
{
|
||
|
return array + index;
|
||
|
}
|
||
|
|
||
|
#undef ARRAY_A_TYPE
|
||
|
#undef ARRAY_TYPE
|
||
|
#undef ARRAY_PREFIX
|
||
|
|
||
|
There are few things that are worth noticing. The first two lines
|
||
|
define the data type. The macro (`ARRAY_A_TYPE`) is only for
|
||
|
convenience inside the header, since such type names can be used quite
|
||
|
often inside the header (if it is large).
|
||
|
|
||
|
Then there is the function with its name generated (do not get scared
|
||
|
by the double parenthesis, ones will be eaten by the macro, the second
|
||
|
ones are real function parameters). The function is static, since more
|
||
|
than one `.c` file might want to use the same header with the same
|
||
|
prefix -- each one generates it's own instance.
|
||
|
|
||
|
And the end just undefines all the macros, so user may define them
|
||
|
again and get another instance of the data structure.
|
||
|
|
||
|
Also note it is not protected against multiple inclusion in the usual
|
||
|
way (eg. `#ifndef ARRAY_H` ...), since multiple inclusion is desired
|
||
|
-- it generates multiple versions of the data structure.
|