Fri, 27 May 2022 11:39:50 +0200
fix missing static inline for map functions
universe@264 | 1 | --- |
universe@390 | 2 | title: UCX 2.1 Modules |
universe@264 | 3 | --- |
universe@259 | 4 | |
universe@390 | 5 | UCX 2.1 provided several modules for data structures and algorithms. |
universe@259 | 6 | You may choose to use specific modules by inclueding the corresponding header |
universe@259 | 7 | file. |
universe@390 | 8 | Please note, that some modules make use of other UCX 2.1 modules. |
universe@259 | 9 | For instance, the [Allocator](#allocator) module is used by many other modules |
universe@259 | 10 | to allow flexible memory allocation. |
universe@259 | 11 | By default the header files are placed into an `ucx` directory within your |
universe@282 | 12 | systems include directory. In this case you can use a module by including it |
universe@259 | 13 | via `#include <ucx/MODULENAME.h>`. |
universe@259 | 14 | Required modules are included automatically. |
universe@259 | 15 | |
universe@267 | 16 | <div id="modules" align="center"> |
universe@267 | 17 | |
universe@340 | 18 | ----------------------- ---------------------- -------------------------------- --------------------------- |
universe@340 | 19 | [String](#string) [Buffer](#buffer) |
universe@340 | 20 | [Allocator](#allocator) [Stack](#stack) [Memory Pool](#memory-pool) |
universe@340 | 21 | [Array](#array) [List](#list) [Map](#map) [AVL Tree](#avl-tree) |
universe@340 | 22 | [Logging](#logging) [Testing](#testing) [Utilities](#utilities) [Properties](#properties) |
universe@340 | 23 | ----------------------- ---------------------- -------------------------------- --------------------------- |
universe@267 | 24 | |
universe@267 | 25 | </div> |
universe@267 | 26 | |
universe@259 | 27 | ## Allocator |
universe@259 | 28 | |
universe@390 | 29 | *Header file:* [allocator.h](api-2.1/allocator_8h.html) |
universe@259 | 30 | *Required modules:* None. |
universe@259 | 31 | |
universe@259 | 32 | A UCX allocator consists of a pointer to the memory area / pool and four |
universe@259 | 33 | function pointers to memory management functions operating on this memory |
universe@259 | 34 | area / pool. These functions shall behave equivalent to the standard libc |
universe@259 | 35 | functions `malloc`, `calloc`, `realloc` and `free`. |
universe@259 | 36 | |
universe@259 | 37 | The signature of the memory management functions is based on the signature |
universe@259 | 38 | of the respective libc function but each of them takes the pointer to the |
universe@259 | 39 | memory area / pool as first argument. |
universe@259 | 40 | |
universe@259 | 41 | As the pointer to the memory area / pool can be arbitrarily chosen, any data |
universe@259 | 42 | can be provided to the memory management functions. One example is the |
universe@280 | 43 | [UCX Memory Pool](#memory-pool). |
universe@259 | 44 | |
universe@340 | 45 | ## Array |
universe@340 | 46 | |
universe@390 | 47 | *Header file:* [array.h](api-2.1/array_8h.html) |
universe@340 | 48 | *Required modules:* [Allocator](#allocator) |
universe@340 | 49 | |
universe@340 | 50 | The UCX Array is an implementation of a dynamic array with automatic |
universe@340 | 51 | reallocation. The array structure contains a capacity, the current size, |
universe@340 | 52 | the size of each element, the raw pointer to the memory area and an allocator. |
universe@359 | 53 | Arrays are in most cases much faster than linked list. |
universe@359 | 54 | One can decide, whether to create a new array on the heap with `ucx_array_new()` |
universe@359 | 55 | or to save one indirection by initializing a `UcxArray` structure on the stack |
universe@359 | 56 | with `ucx_array_init()`. |
universe@340 | 57 | |
universe@340 | 58 | ### Remove duplicates from an array of strings |
universe@340 | 59 | |
universe@340 | 60 | The following example shows, how a `UcxArray` can be built with |
universe@340 | 61 | a standard dynamic C array (pointer+length) as basis. |
universe@340 | 62 | |
universe@340 | 63 | ```C |
universe@370 | 64 | UcxArray* create_unique(sstr_t* array, size_t arrlen) { |
universe@340 | 65 | // worst case is no duplicates, hence the capacity is set to arrlen |
universe@370 | 66 | UcxArray* result = ucx_array_new(arrlen, sizeof(sstr_t)); |
universe@340 | 67 | // only append elements, if they are not already present in the array |
universe@340 | 68 | for (size_t i = 0 ; i < arrlen ; ++i) { |
universe@340 | 69 | if (!ucx_array_contains(result, array+i, ucx_cmp_sstr, NULL)) { |
universe@370 | 70 | ucx_array_append_from(result, array+i, 1); |
universe@340 | 71 | } |
universe@340 | 72 | } |
universe@340 | 73 | // make the array as small as possible |
universe@370 | 74 | ucx_array_shrink(result); |
universe@340 | 75 | return result; |
universe@340 | 76 | } |
universe@340 | 77 | |
universe@340 | 78 | /* ... */ |
universe@340 | 79 | |
universe@340 | 80 | sstr_t* array = /* some standard array of strings */ |
universe@340 | 81 | size_t arrlen = /* the length of the array */ |
universe@340 | 82 | |
universe@370 | 83 | UcxArray* result = create_unique(array,arrlen); |
universe@340 | 84 | |
universe@340 | 85 | /* Iterate over the array and print the elements */ |
universe@370 | 86 | sstr_t* unique = result->data; |
universe@370 | 87 | for (size_t i = 0 ; i < result->size ; i++) { |
universe@370 | 88 | printf("%" PRIsstr "\n", SFMT(unique[i])); |
universe@340 | 89 | } |
universe@340 | 90 | |
universe@340 | 91 | /* Free the array. */ |
universe@370 | 92 | ucx_array_free(result); |
universe@370 | 93 | ``` |
universe@370 | 94 | ### Preventing out of bounds writes |
universe@370 | 95 | |
universe@370 | 96 | The functions `ucx_array_reserve()`, `ucx_array_resize()`, `ucx_array_grow()`, |
universe@370 | 97 | and `ucx_array_shrink()` allow easy management of the array capacity. |
universe@370 | 98 | Imagine you want to add `n` elements to an array. If your `n` elements are |
universe@370 | 99 | already somewhere else consecutively in memory, you can use |
universe@370 | 100 | `ucx_array_append_from()` and benefit from the autogrow facility in this family |
universe@370 | 101 | of functions. Otherwise, you can ask the array to have enough capacity for |
universe@370 | 102 | holding additional `n` elements. |
universe@370 | 103 | |
universe@370 | 104 | ```C |
universe@370 | 105 | size_t n = // ... elements to add |
universe@370 | 106 | if (ucx_array_grow(array, n)) { |
universe@370 | 107 | fprintf(stderr, "Cannot add %zu elements to the array.\n", n); |
universe@370 | 108 | return 1; |
universe@370 | 109 | } |
universe@370 | 110 | for (size_t i = 0 ; i < n ; i++) { |
universe@370 | 111 | ((int*)array->data)[array->size++] = 80; |
universe@370 | 112 | } |
universe@340 | 113 | ``` |
universe@340 | 114 | |
universe@259 | 115 | ## AVL Tree |
universe@259 | 116 | |
universe@390 | 117 | *Header file:* [avl.h](api-2.1/avl_8h.html) |
universe@259 | 118 | *Required modules:* [Allocator](#allocator) |
universe@259 | 119 | |
universe@259 | 120 | This binary search tree implementation allows average O(1) insertion and |
universe@259 | 121 | removal of elements (excluding binary search time). |
universe@259 | 122 | All common binary tree operations are implemented. Furthermore, this module |
universe@259 | 123 | provides search functions via lower and upper bounds. |
universe@259 | 124 | |
universe@287 | 125 | ### Filtering items with a time window |
universe@287 | 126 | |
universe@287 | 127 | Suppose you have a list of items which contain a `time_t` value and your task |
universe@287 | 128 | is to find all items within a time window `[t_start, t_end]`. |
universe@287 | 129 | With AVL Trees this is easy: |
universe@287 | 130 | ```C |
universe@287 | 131 | /* --------------------- |
universe@287 | 132 | * Somewhere in a header |
universe@287 | 133 | */ |
universe@287 | 134 | typedef struct { |
universe@287 | 135 | time_t ts; |
universe@294 | 136 | /* other important data */ |
universe@287 | 137 | } MyObject; |
universe@287 | 138 | |
universe@287 | 139 | /* ----------- |
universe@287 | 140 | * Source code |
universe@287 | 141 | */ |
universe@287 | 142 | |
universe@314 | 143 | UcxAVLTree* tree = ucx_avl_new(ucx_cmp_longint); |
universe@294 | 144 | /* ... populate tree with objects, use '& MyObject.ts' as key ... */ |
universe@287 | 145 | |
universe@287 | 146 | |
universe@294 | 147 | /* Now find every item, with 30 <= ts <= 70 */ |
universe@287 | 148 | time_t ts_start = 30; |
universe@287 | 149 | time_t ts_end = 70; |
universe@287 | 150 | |
universe@287 | 151 | printf("Values in range:\n"); |
universe@287 | 152 | for ( |
universe@287 | 153 | UcxAVLNode* node = ucx_avl_find_node( |
universe@287 | 154 | tree, (intptr_t) &ts_start, |
universe@314 | 155 | ucx_dist_longint, UCX_AVL_FIND_LOWER_BOUNDED); |
universe@287 | 156 | node && (*(time_t*)node->key) <= ts_end; |
universe@287 | 157 | node = ucx_avl_succ(node) |
universe@287 | 158 | ) { |
universe@287 | 159 | printf(" ts: %ld\n", ((MyObject*)node->value)->ts); |
universe@287 | 160 | } |
universe@287 | 161 | |
universe@287 | 162 | ucx_avl_free_content(tree, free); |
universe@287 | 163 | ucx_avl_free(tree); |
universe@287 | 164 | ``` |
universe@287 | 165 | |
universe@259 | 166 | ## Buffer |
universe@259 | 167 | |
universe@390 | 168 | *Header file:* [buffer.h](api-2.1/buffer_8h.html) |
universe@259 | 169 | *Required modules:* None. |
universe@259 | 170 | |
universe@259 | 171 | Instances of this buffer implementation can be used to read from or to write to |
universe@259 | 172 | memory like you would do with a stream. This allows the use of |
universe@282 | 173 | `ucx_stream_copy()` from the [Utilities](#utilities) module to copy contents |
universe@282 | 174 | from one buffer to another or from file or network streams to the buffer and |
universe@259 | 175 | vice-versa. |
universe@259 | 176 | |
universe@259 | 177 | More features for convenient use of the buffer can be enabled, like automatic |
universe@259 | 178 | memory management and automatic resizing of the buffer space. |
universe@259 | 179 | See the documentation of the macro constants in the header file for more |
universe@259 | 180 | information. |
universe@259 | 181 | |
universe@290 | 182 | ### Add line numbers to a file |
universe@290 | 183 | |
universe@290 | 184 | When reading a file line by line, you have three options: first, you could limit |
universe@290 | 185 | the maximum supported line length. |
universe@290 | 186 | Second, you allocate a god buffer large |
universe@290 | 187 | enough for the most lines a text file could have. |
universe@290 | 188 | And third, undoubtedly the best option, you start with a small buffer, which |
universe@290 | 189 | adjusts on demand. |
universe@290 | 190 | An `UcxBuffer` can be created to do just that for you. |
universe@290 | 191 | Just pass the `UCX_BUFFER_AUTOEXTEND` option to the initialization function. |
universe@290 | 192 | Here is a full working program, which adds line numbers to a file. |
universe@290 | 193 | ```C |
universe@290 | 194 | #include <stdio.h> |
universe@290 | 195 | #include <ucx/buffer.h> |
universe@290 | 196 | #include <ucx/utils.h> |
universe@290 | 197 | |
universe@290 | 198 | int main(int argc, char** argv) { |
universe@290 | 199 | |
universe@290 | 200 | if (argc != 2) { |
universe@290 | 201 | fprintf(stderr, "Usage: %s <file>\n", argv[0]); |
universe@290 | 202 | return 1; |
universe@290 | 203 | } |
universe@290 | 204 | |
universe@290 | 205 | FILE* input = fopen(argv[1], "r"); |
universe@290 | 206 | if (!input) { |
universe@290 | 207 | perror("Canno read input"); |
universe@290 | 208 | return 1; |
universe@290 | 209 | } |
universe@290 | 210 | |
universe@290 | 211 | const size_t chunksize = 256; |
universe@290 | 212 | |
universe@290 | 213 | UcxBuffer* linebuf = |
universe@290 | 214 | ucx_buffer_new( |
universe@294 | 215 | NULL, /* the buffer should manage the memory area for us */ |
universe@294 | 216 | 2*chunksize, /* initial size should be twice the chunk size */ |
universe@294 | 217 | UCX_BUFFER_AUTOEXTEND); /* the buffer will grow when necessary */ |
universe@290 | 218 | |
universe@290 | 219 | size_t lineno = 1; |
universe@290 | 220 | do { |
universe@294 | 221 | /* read line chunk */ |
universe@290 | 222 | size_t read = ucx_stream_ncopy( |
universe@290 | 223 | input, linebuf, fread, ucx_buffer_write, chunksize); |
universe@290 | 224 | if (read == 0) break; |
universe@290 | 225 | |
universe@294 | 226 | /* handle line endings */ |
universe@290 | 227 | do { |
universe@290 | 228 | sstr_t bufstr = ucx_buffer_to_sstr(linebuf); |
universe@290 | 229 | sstr_t nl = sstrchr(bufstr, '\n'); |
universe@290 | 230 | if (nl.length == 0) break; |
universe@290 | 231 | |
universe@290 | 232 | size_t linelen = bufstr.length - nl.length; |
universe@290 | 233 | sstr_t linestr = sstrsubsl(bufstr, 0, linelen); |
universe@290 | 234 | |
universe@290 | 235 | printf("%zu: %" PRIsstr "\n", lineno++, SFMT(linestr)); |
universe@290 | 236 | |
universe@294 | 237 | /* shift the buffer to the next line */ |
universe@290 | 238 | ucx_buffer_shift_left(linebuf, linelen+1); |
universe@290 | 239 | } while(1); |
universe@290 | 240 | |
universe@290 | 241 | } while(1); |
universe@290 | 242 | |
universe@294 | 243 | /* print the 'noeol' line, if any */ |
universe@290 | 244 | sstr_t lastline = ucx_buffer_to_sstr(linebuf); |
universe@290 | 245 | if (lastline.length > 0) { |
universe@290 | 246 | printf("%zu: %" PRIsstr, lineno, SFMT(lastline)); |
universe@290 | 247 | } |
universe@290 | 248 | |
universe@290 | 249 | fclose(input); |
universe@290 | 250 | ucx_buffer_free(linebuf); |
universe@290 | 251 | |
universe@290 | 252 | return 0; |
universe@290 | 253 | } |
universe@290 | 254 | ``` |
universe@290 | 255 | |
universe@259 | 256 | ## List |
universe@259 | 257 | |
universe@390 | 258 | *Header file:* [list.h](api-2.1/list_8h.html) |
universe@259 | 259 | *Required modules:* [Allocator](#allocator) |
universe@259 | 260 | |
universe@259 | 261 | This module provides the data structure and several functions for a doubly |
universe@259 | 262 | linked list. Among the common operations like insert, remove, search and sort, |
universe@259 | 263 | we allow convenient iteration via a special `UCX_FOREACH` macro. |
universe@259 | 264 | |
universe@294 | 265 | ### Remove duplicates from an array of strings |
universe@294 | 266 | |
universe@294 | 267 | Assume you are given an array of `sstr_t` and want to create a list of these |
universe@294 | 268 | strings without duplicates. |
universe@340 | 269 | This is a similar example to the one [above](#array), but here we are |
universe@340 | 270 | using a `UcxList`. |
universe@294 | 271 | ```C |
universe@294 | 272 | #include <stdio.h> |
universe@294 | 273 | #include <ucx/list.h> |
universe@294 | 274 | #include <ucx/string.h> |
universe@294 | 275 | #include <ucx/utils.h> |
universe@294 | 276 | |
universe@294 | 277 | UcxList* remove_duplicates(sstr_t* array, size_t arrlen) { |
universe@294 | 278 | UcxList* list = NULL; |
universe@294 | 279 | for (size_t i = 0 ; i < arrlen ; ++i) { |
universe@310 | 280 | if (ucx_list_find(list, array+i, ucx_cmp_sstr, NULL) == -1) { |
universe@294 | 281 | sstr_t* s = malloc(sizeof(sstr_t)); |
universe@294 | 282 | *s = sstrdup(array[i]); |
universe@294 | 283 | list = ucx_list_append(list, s); |
universe@294 | 284 | } |
universe@294 | 285 | } |
universe@294 | 286 | return list; |
universe@294 | 287 | } |
universe@294 | 288 | |
universe@294 | 289 | /* we will need this function to clean up the list contents later */ |
universe@294 | 290 | void free_sstr(void* ptr) { |
universe@294 | 291 | sstr_t* s = ptr; |
universe@294 | 292 | free(s->ptr); |
universe@294 | 293 | free(s); |
universe@294 | 294 | } |
universe@294 | 295 | |
universe@294 | 296 | /* ... */ |
universe@294 | 297 | |
universe@294 | 298 | sstr_t* array = /* some array of strings */ |
universe@294 | 299 | size_t arrlen = /* the length of the array */ |
universe@294 | 300 | |
universe@294 | 301 | UcxList* list = remove_duplicates(array,arrlen); |
universe@294 | 302 | |
universe@294 | 303 | /* Iterate over the list and print the elements */ |
universe@294 | 304 | UCX_FOREACH(elem, list) { |
universe@294 | 305 | sstr_t s = *((sstr_t*)elem->data); |
universe@294 | 306 | printf("%" PRIsstr "\n", SFMT(s)); |
universe@294 | 307 | } |
universe@294 | 308 | |
universe@294 | 309 | /* Use our free function to free the duplicated strings. */ |
universe@294 | 310 | ucx_list_free_content(list, free_sstr); |
universe@294 | 311 | ucx_list_free(list); |
universe@294 | 312 | ``` |
universe@294 | 313 | |
universe@259 | 314 | ## Logging |
universe@259 | 315 | |
universe@390 | 316 | *Header file:* [logging.h](api-2.1/logging_8h.html) |
universe@259 | 317 | *Required modules:* [Map](#map), [String](#string) |
universe@259 | 318 | |
universe@259 | 319 | The logging module comes with some predefined log levels and allows some more |
universe@259 | 320 | customization. You may choose if you want to get timestamps or source file and |
universe@259 | 321 | line number logged automatically when outputting a message. |
universe@295 | 322 | The following function call initializes a debug logger with all of the above |
universe@295 | 323 | information: |
universe@295 | 324 | ```C |
universe@295 | 325 | log = ucx_logger_new(stdout, UCX_LOGGER_DEBUG, |
universe@295 | 326 | UCX_LOGGER_LEVEL | UCX_LOGGER_TIMESTAMP | UCX_LOGGER_SOURCE); |
universe@295 | 327 | ``` |
universe@295 | 328 | Afterwards you can use this logger with the predefined macros |
universe@295 | 329 | ```C |
universe@295 | 330 | ucx_logger_trace(log, "Verbose output"); |
universe@295 | 331 | ucx_logger_debug(log, "Debug message"); |
universe@295 | 332 | ucx_logger_info(log, "Information"); |
universe@295 | 333 | ucx_logger_warn(log, "Warning"); |
universe@295 | 334 | ucx_logger_error(log, "Error message"); |
universe@295 | 335 | ``` |
universe@295 | 336 | or you use |
universe@295 | 337 | ```C |
universe@295 | 338 | ucx_logger_log(log, CUSTOM_LEVEL, "Some message") |
universe@295 | 339 | ``` |
universe@295 | 340 | When you use your custom log level, don't forget to register it with |
universe@295 | 341 | ```C |
universe@295 | 342 | ucx_logger_register_level(log, CUSTOM_LEVEL, "CUSTOM") |
universe@295 | 343 | ``` |
universe@295 | 344 | where the last argument must be a string literal. |
universe@259 | 345 | |
universe@259 | 346 | ## Map |
universe@259 | 347 | |
universe@390 | 348 | *Header file:* [map.h](api-2.1/map_8h.html) |
universe@259 | 349 | *Required modules:* [Allocator](#allocator), [String](#string) |
universe@259 | 350 | |
universe@259 | 351 | This module provides a hash map implementation using murmur hash 2 and separate |
universe@259 | 352 | chaining with linked lists. Similarly to the list module, we provide a |
universe@259 | 353 | `UCX_MAP_FOREACH` macro to conveniently iterate through the key/value pairs. |
universe@259 | 354 | |
universe@298 | 355 | ### Parsing command line options |
universe@298 | 356 | |
universe@298 | 357 | Assume you want to parse command line options and record them within a map. |
universe@298 | 358 | One way to do this is shown by the following code sample: |
universe@298 | 359 | ```C |
universe@298 | 360 | UcxMap* options = ucx_map_new(16); |
universe@298 | 361 | const char *NOARG = ""; |
universe@298 | 362 | |
universe@298 | 363 | char *option = NULL; |
universe@298 | 364 | char optchar = 0; |
universe@298 | 365 | for(int i=1;i<argc;i++) { |
universe@298 | 366 | char *arg = argv[i]; |
universe@298 | 367 | size_t len = strlen(arg); |
universe@298 | 368 | if(len > 1 && arg[0] == '-') { |
universe@298 | 369 | for(int c=1;c<len;c++) { |
universe@299 | 370 | if(option) { |
universe@299 | 371 | fprintf(stderr, |
universe@299 | 372 | "Missing argument for option -%c\n", optchar); |
universe@299 | 373 | return 1; |
universe@299 | 374 | } |
universe@298 | 375 | switch(arg[c]) { |
universe@298 | 376 | default: { |
universe@298 | 377 | fprintf(stderr, "Unknown option -%c\n\n", arg[c]); |
universe@298 | 378 | return 1; |
universe@298 | 379 | } |
universe@298 | 380 | case 'v': { |
universe@298 | 381 | ucx_map_cstr_put(options, "verbose", NOARG); |
universe@298 | 382 | break; |
universe@298 | 383 | } |
universe@298 | 384 | case 'o': { |
universe@298 | 385 | option = "output"; |
universe@298 | 386 | optchar = 'o'; |
universe@298 | 387 | break; |
universe@298 | 388 | } |
universe@298 | 389 | } |
universe@298 | 390 | } |
universe@298 | 391 | } else if(option) { |
universe@298 | 392 | ucx_map_cstr_put(options, option, arg); |
universe@298 | 393 | option = NULL; |
universe@298 | 394 | } else { |
universe@298 | 395 | /* ... handle argument that is not an option ... */ |
universe@298 | 396 | } |
universe@298 | 397 | } |
universe@298 | 398 | if(option) { |
universe@298 | 399 | fprintf(stderr, |
universe@298 | 400 | "Missing argument for option -%c\n", optchar); |
universe@298 | 401 | return 1; |
universe@298 | 402 | } |
universe@298 | 403 | ``` |
universe@298 | 404 | With the following loop, you can access the previously recorded options: |
universe@298 | 405 | ```C |
universe@298 | 406 | UcxMapIterator iter = ucx_map_iterator(options); |
universe@298 | 407 | char *arg; |
universe@298 | 408 | UCX_MAP_FOREACH(optkey, arg, iter) { |
universe@298 | 409 | char* opt = optkey.data; |
universe@298 | 410 | if (*arg) { |
universe@298 | 411 | printf("%s = %s\n", opt, arg); |
universe@298 | 412 | } else { |
universe@298 | 413 | printf("%s active\n", opt); |
universe@298 | 414 | } |
universe@298 | 415 | } |
universe@298 | 416 | ``` |
universe@298 | 417 | Don't forget to call `ucx_map_free()`, when you are done with the map. |
universe@298 | 418 | |
universe@259 | 419 | ## Memory Pool |
universe@259 | 420 | |
universe@390 | 421 | *Header file:* [mempool.h](api-2.1/mempool_8h.html) |
universe@259 | 422 | *Required modules:* [Allocator](#allocator) |
universe@259 | 423 | |
universe@259 | 424 | Here we have a concrete allocator implementation in the sense of a memory pool. |
universe@259 | 425 | This pool allows you to register destructor functions for the allocated memory, |
universe@259 | 426 | which are automatically called on the destruction of the pool. |
universe@259 | 427 | But you may also register *independent* destructor functions within a pool in |
universe@302 | 428 | case some external library allocated memory for you, which should be |
universe@259 | 429 | destroyed together with this pool. |
universe@259 | 430 | |
universe@302 | 431 | Many UCX modules support the use of an allocator. |
universe@302 | 432 | The [String Module](#string), for instance, provides the `sstrdup_a()` function, |
universe@302 | 433 | which uses the specified allocator to allocate the memory for the duplicated |
universe@302 | 434 | string. |
universe@302 | 435 | This way, you can use a `UcxMempool` to keep track of the memory occupied by |
universe@302 | 436 | duplicated strings and cleanup everything with just a single call to |
universe@302 | 437 | `ucx_mempool_destroy()`. |
universe@302 | 438 | |
universe@302 | 439 | ### Read CSV data into a structure |
universe@302 | 440 | |
universe@302 | 441 | The following code example shows some of the basic memory pool functions and |
universe@302 | 442 | how they can be used with other UCX modules. |
universe@302 | 443 | ```C |
universe@302 | 444 | #include <stdio.h> |
universe@302 | 445 | #include <ucx/mempool.h> |
universe@302 | 446 | #include <ucx/list.h> |
universe@302 | 447 | #include <ucx/string.h> |
universe@302 | 448 | #include <ucx/buffer.h> |
universe@302 | 449 | #include <ucx/utils.h> |
universe@302 | 450 | |
universe@302 | 451 | typedef struct { |
universe@302 | 452 | sstr_t column_a; |
universe@302 | 453 | sstr_t column_b; |
universe@302 | 454 | sstr_t column_c; |
universe@302 | 455 | } CSVData; |
universe@302 | 456 | |
universe@302 | 457 | int main(int argc, char** argv) { |
universe@302 | 458 | |
universe@302 | 459 | UcxMempool* pool = ucx_mempool_new(128); |
universe@302 | 460 | |
universe@302 | 461 | FILE *f = fopen("test.csv", "r"); |
universe@302 | 462 | if (!f) { |
universe@302 | 463 | perror("Cannot open file"); |
universe@302 | 464 | return 1; |
universe@302 | 465 | } |
universe@302 | 466 | /* close the file automatically at pool destruction*/ |
universe@302 | 467 | ucx_mempool_reg_destr(pool, f, (ucx_destructor) fclose); |
universe@302 | 468 | |
universe@302 | 469 | /* create a buffer and register it at the memory pool for destruction */ |
universe@302 | 470 | UcxBuffer* content = ucx_buffer_new(NULL, 256, UCX_BUFFER_AUTOEXTEND); |
universe@302 | 471 | ucx_mempool_reg_destr(pool, content, (ucx_destructor) ucx_buffer_free); |
universe@302 | 472 | |
universe@302 | 473 | /* read the file and split it by lines first */ |
universe@302 | 474 | ucx_stream_copy(f, content, fread, ucx_buffer_write); |
universe@302 | 475 | sstr_t contentstr = ucx_buffer_to_sstr(content); |
universe@302 | 476 | ssize_t lc = 0; |
universe@302 | 477 | sstr_t* lines = sstrsplit_a(pool->allocator, contentstr, S("\n"), &lc); |
universe@302 | 478 | |
universe@302 | 479 | /* skip the header and parse the remaining data */ |
universe@302 | 480 | UcxList* datalist = NULL; |
universe@302 | 481 | for (size_t i = 1 ; i < lc ; i++) { |
universe@302 | 482 | if (lines[i].length == 0) continue; |
universe@302 | 483 | ssize_t fc = 3; |
universe@302 | 484 | sstr_t* fields = sstrsplit_a(pool->allocator, lines[i], S(";"), &fc); |
universe@302 | 485 | if (fc != 3) { |
universe@302 | 486 | fprintf(stderr, "Syntax error in line %zu.\n", i); |
universe@302 | 487 | ucx_mempool_destroy(pool); |
universe@302 | 488 | return 1; |
universe@302 | 489 | } |
universe@302 | 490 | CSVData* data = ucx_mempool_malloc(pool, sizeof(CSVData)); |
universe@302 | 491 | data->column_a = fields[0]; |
universe@302 | 492 | data->column_b = fields[1]; |
universe@302 | 493 | data->column_c = fields[2]; |
universe@302 | 494 | datalist = ucx_list_append_a(pool->allocator, datalist, data); |
universe@302 | 495 | } |
universe@302 | 496 | |
universe@302 | 497 | /* control output */ |
universe@302 | 498 | UCX_FOREACH(elem, datalist) { |
universe@302 | 499 | CSVData* data = elem->data; |
universe@302 | 500 | printf("Column A: %" PRIsstr " | " |
universe@302 | 501 | "Column B: %" PRIsstr " | " |
universe@302 | 502 | "Column C: %" PRIsstr "\n", |
universe@302 | 503 | SFMT(data->column_a), SFMT(data->column_b), SFMT(data->column_c) |
universe@302 | 504 | ); |
universe@302 | 505 | } |
universe@302 | 506 | |
universe@302 | 507 | /* cleanup everything, no manual free() needed */ |
universe@302 | 508 | ucx_mempool_destroy(pool); |
universe@302 | 509 | |
universe@302 | 510 | return 0; |
universe@302 | 511 | } |
universe@302 | 512 | ``` |
universe@302 | 513 | |
universe@302 | 514 | ### Overriding the default destructor |
universe@302 | 515 | |
universe@302 | 516 | Sometimes you need to allocate memory with `ucx_mempool_malloc()`, but the |
universe@302 | 517 | memory is not supposed to be freed with a simple call to `free()`. |
universe@302 | 518 | In this case, you can overwrite the default destructor as follows: |
universe@302 | 519 | ```C |
universe@302 | 520 | MyObject* obj = ucx_mempool_malloc(pool, sizeof(MyObject)); |
universe@302 | 521 | |
universe@302 | 522 | /* some special initialization with own resource management */ |
universe@302 | 523 | my_object_init(obj); |
universe@302 | 524 | |
universe@302 | 525 | /* register destructor function */ |
universe@302 | 526 | ucx_mempool_set_destr(obj, (ucx_destructor) my_object_destroy); |
universe@302 | 527 | ``` |
universe@304 | 528 | Be aware, that your destructor function should not free any memory, that is |
universe@302 | 529 | also managed by the pool. |
universe@302 | 530 | Otherwise you might be risking a double-free. |
universe@326 | 531 | More precisely, a destructor function set with `ucx_mempool_set_destr()` MUST |
universe@326 | 532 | NOT call `free()` on the specified pointer whereas a desructor function |
universe@326 | 533 | registered with `ucx_mempool_reg_destr()` MAY (and in most cases will) call |
universe@326 | 534 | `free()`. |
universe@302 | 535 | |
universe@259 | 536 | ## Properties |
universe@259 | 537 | |
universe@390 | 538 | *Header file:* [properties.h](api-2.1/properties_8h.html) |
universe@259 | 539 | *Required modules:* [Map](#map) |
universe@259 | 540 | |
universe@259 | 541 | This module provides load and store function for `*.properties` files. |
universe@259 | 542 | The key/value pairs are stored within an UCX Map. |
universe@259 | 543 | |
universe@277 | 544 | ### Example: Loading properties from a file |
universe@277 | 545 | |
universe@277 | 546 | ```C |
universe@294 | 547 | /* Open the file as usual */ |
universe@277 | 548 | FILE* file = fopen("myprops.properties", "r"); |
universe@277 | 549 | if (!file) { |
universe@277 | 550 | // error handling |
universe@277 | 551 | return 1; |
universe@277 | 552 | } |
universe@277 | 553 | |
universe@294 | 554 | /* Load the properties from the file */ |
universe@277 | 555 | UcxMap* myprops = ucx_map_new(16); |
universe@277 | 556 | if (ucx_properties_load(myprops, file)) { |
universe@294 | 557 | /* ... error handling ... */ |
universe@277 | 558 | fclose(file); |
universe@277 | 559 | ucx_map_free(myprops); |
universe@277 | 560 | return 1; |
universe@277 | 561 | } |
universe@277 | 562 | |
universe@294 | 563 | /* Print out the key/value pairs */ |
universe@277 | 564 | char* propval; |
universe@277 | 565 | UcxMapIterator propiter = ucx_map_iterator(myprops); |
universe@277 | 566 | UCX_MAP_FOREACH(key, propval, propiter) { |
universe@277 | 567 | printf("%s = %s\n", (char*)key.data, propval); |
universe@277 | 568 | } |
universe@277 | 569 | |
universe@294 | 570 | /* Don't forget to free the values before freeing the map */ |
universe@277 | 571 | ucx_map_free_content(myprops, NULL); |
universe@277 | 572 | ucx_map_free(myprops); |
universe@277 | 573 | fclose(file); |
universe@277 | 574 | ``` |
universe@295 | 575 | |
universe@259 | 576 | ## Stack |
universe@259 | 577 | |
universe@390 | 578 | *Header file:* [stack.h](api-2.1/stack_8h.html) |
universe@259 | 579 | *Required modules:* [Allocator](#allocator) |
universe@259 | 580 | |
universe@259 | 581 | This concrete implementation of an UCX Allocator allows you to grab some amount |
universe@259 | 582 | of memory which is then handled as a stack. |
universe@259 | 583 | Please note, that the term *stack* only refers to the behavior of this |
universe@301 | 584 | allocator. You may still choose to use either stack or heap memory |
universe@259 | 585 | for the underlying space. |
universe@259 | 586 | A typical use case is an algorithm where you need to allocate and free large |
universe@259 | 587 | amounts of memory very frequently. |
universe@259 | 588 | |
universe@301 | 589 | The following code sample shows how to initialize a stack and push and pop |
universe@301 | 590 | simple data. |
universe@301 | 591 | ```C |
universe@301 | 592 | const size_t len = 1024; |
universe@301 | 593 | char space[len]; |
universe@301 | 594 | UcxStack stack; |
universe@301 | 595 | ucx_stack_init(&stack, space, len); |
universe@301 | 596 | |
universe@301 | 597 | int i = 42; |
universe@301 | 598 | float f = 3.14f; |
universe@301 | 599 | const char* str = "Hello!"; |
universe@301 | 600 | size_t strn = 7; |
universe@301 | 601 | |
universe@301 | 602 | /* push the integer */ |
universe@301 | 603 | ucx_stack_push(&stack, sizeof(int), &i); |
universe@301 | 604 | |
universe@301 | 605 | /* push the float and rember the address */ |
universe@301 | 606 | float* remember = ucx_stack_push(&stack, sizeof(float), &f); |
universe@301 | 607 | |
universe@301 | 608 | /* push the string with zero terminator */ |
universe@301 | 609 | ucx_stack_push(&stack, strn, str); |
universe@301 | 610 | |
universe@301 | 611 | /* if we forget, how big an element was, we can ask the stack */ |
universe@301 | 612 | printf("Length of string: %zu\n", ucx_stack_topsize(&stack)-1); |
universe@301 | 613 | |
universe@301 | 614 | /* retrieve the string as sstr_t, without zero terminator! */ |
universe@301 | 615 | sstr_t s; |
universe@301 | 616 | s.length = ucx_stack_topsize(&stack)-1; |
universe@301 | 617 | s.ptr = malloc(s.length); |
universe@301 | 618 | ucx_stack_popn(&stack, s.ptr, s.length); |
universe@301 | 619 | printf("%" PRIsstr "\n", SFMT(s)); |
universe@301 | 620 | |
universe@301 | 621 | /* print the float directly from the stack and free it */ |
universe@301 | 622 | printf("Float: %f\n", *remember); |
universe@301 | 623 | ucx_stack_free(&stack, remember); |
universe@301 | 624 | |
universe@301 | 625 | /* the last element is the integer */ |
universe@301 | 626 | int j; |
universe@301 | 627 | ucx_stack_pop(&stack, &j); |
universe@301 | 628 | printf("Integer: %d\n", j); |
universe@301 | 629 | ``` |
universe@301 | 630 | |
universe@301 | 631 | |
universe@301 | 632 | |
universe@259 | 633 | ## String |
universe@259 | 634 | |
universe@390 | 635 | *Header file:* [string.h](api-2.1/string_8h.html) |
universe@259 | 636 | *Required modules:* [Allocator](#allocator) |
universe@259 | 637 | |
universe@259 | 638 | This module provides a safe implementation of bounded string. |
universe@259 | 639 | Usually C strings do not carry a length. While for zero-terminated strings you |
universe@259 | 640 | can easily get the length with `strlen`, this is not generally possible for |
universe@259 | 641 | arbitrary strings. |
universe@259 | 642 | The `sstr_t` type of this module always carries the string and its length to |
universe@259 | 643 | reduce the risk of buffer overflows dramatically. |
universe@259 | 644 | |
universe@267 | 645 | ### Initialization |
universe@267 | 646 | |
universe@267 | 647 | There are several ways to create an `sstr_t`: |
universe@267 | 648 | |
universe@267 | 649 | ```C |
universe@267 | 650 | /* (1) sstr() uses strlen() internally, hence cstr MUST be zero-terminated */ |
universe@267 | 651 | sstr_t a = sstr(cstr); |
universe@267 | 652 | |
universe@267 | 653 | /* (2) cstr does not need to be zero-terminated, if length is specified */ |
universe@267 | 654 | sstr_t b = sstrn(cstr, len); |
universe@267 | 655 | |
universe@267 | 656 | /* (3) S() macro creates sstr_t from a string using sizeof() and using sstrn(). |
universe@267 | 657 | This version is especially useful for function arguments */ |
universe@267 | 658 | sstr_t c = S("hello"); |
universe@267 | 659 | |
universe@325 | 660 | /* (4) SC() macro works like S(), but makes the string immutable using scstr_t. |
universe@325 | 661 | (available since UCX 2.0) */ |
universe@325 | 662 | scstr_t d = SC("hello"); |
universe@325 | 663 | |
universe@325 | 664 | /* (5) ST() macro creates sstr_t struct literal using sizeof() */ |
universe@325 | 665 | sstr_t e = ST("hello"); |
universe@267 | 666 | ``` |
universe@267 | 667 | |
universe@325 | 668 | You should not use the `S()`, `SC()`, or `ST()` macro with string of unknown |
universe@325 | 669 | origin, since the `sizeof()` call might not coincide with the string length in |
universe@325 | 670 | those cases. If you know what you are doing, it can save you some performance, |
universe@267 | 671 | because you do not need the `strlen()` call. |
universe@267 | 672 | |
universe@321 | 673 | ### Handling immutable strings |
universe@321 | 674 | |
universe@321 | 675 | *(Since: UCX 2.0)* |
universe@321 | 676 | |
universe@321 | 677 | For immutable strings (i.e. `const char*` strings), UCX provides the `scstr_t` |
universe@321 | 678 | type, which works exactly as the `sstr_t` type but with a pointer |
universe@321 | 679 | to `const char`. All UCX string functions come in two flavors: one that enforces |
universe@321 | 680 | the `scstr_t` type, and another that usually accepts both types and performs |
universe@321 | 681 | a conversion automatically, if necessary. |
universe@321 | 682 | |
universe@321 | 683 | There are some exceptions to this rule, as the return type may depend on the |
universe@321 | 684 | argument type. |
universe@321 | 685 | E.g. the `sstrchr()` function returns a substring starting at |
universe@321 | 686 | the first occurrence of the specified character. |
universe@321 | 687 | Since this substring points to the memory of the argument string, it does not |
universe@321 | 688 | accept `scstr_t` as input argument, because the return type would break the |
universe@321 | 689 | constness. |
universe@321 | 690 | |
universe@321 | 691 | |
universe@267 | 692 | ### Finding the position of a substring |
universe@267 | 693 | |
universe@267 | 694 | The `sstrstr()` function gives you a new `sstr_t` object starting with the |
universe@267 | 695 | requested substring. Thus determining the position comes down to a simple |
universe@267 | 696 | subtraction. |
universe@267 | 697 | |
universe@267 | 698 | ```C |
universe@267 | 699 | sstr_t haystack = ST("Here we go!"); |
universe@267 | 700 | sstr_t needle = ST("we"); |
universe@267 | 701 | sstr_t result = sstrstr(haystack, needle); |
universe@267 | 702 | if (result.ptr) |
universe@267 | 703 | printf("Found at position %zd.\n", haystack.length-result.length); |
universe@267 | 704 | else |
universe@267 | 705 | printf("Not found.\n"); |
universe@267 | 706 | ``` |
universe@267 | 707 | |
universe@267 | 708 | ### Spliting a string by a delimiter |
universe@267 | 709 | |
universe@267 | 710 | The `sstrsplit()` function (and its allocator based version `sstrsplit_a()`) is |
universe@267 | 711 | very powerful and might look a bit nasty at a first glance. But it is indeed |
universe@267 | 712 | very simple to use. It is even more convenient in combination with a memory |
universe@267 | 713 | pool. |
universe@267 | 714 | |
universe@267 | 715 | ```C |
universe@267 | 716 | sstr_t test = ST("here::are::some::strings"); |
universe@267 | 717 | sstr_t delim = ST("::"); |
universe@267 | 718 | |
universe@267 | 719 | ssize_t count = 0; /* no limit */ |
universe@267 | 720 | UcxMempool* pool = ucx_mempool_new_default(); |
universe@267 | 721 | |
universe@267 | 722 | sstr_t* result = sstrsplit_a(pool->allocator, test, delim, &count); |
universe@267 | 723 | for (ssize_t i = 0 ; i < count ; i++) { |
universe@267 | 724 | /* don't forget to specify the length via the %*s format specifier */ |
universe@267 | 725 | printf("%*s\n", result[i].length, result[i].ptr); |
universe@267 | 726 | } |
universe@267 | 727 | |
universe@267 | 728 | ucx_mempool_destroy(pool); |
universe@267 | 729 | ``` |
universe@267 | 730 | The output is: |
universe@267 | 731 | |
universe@267 | 732 | here |
universe@267 | 733 | are |
universe@267 | 734 | some |
universe@267 | 735 | strings |
universe@267 | 736 | |
universe@267 | 737 | The memory pool ensures, that all strings are freed. |
universe@267 | 738 | |
universe@325 | 739 | ### Disabling convenience macros |
universe@325 | 740 | |
universe@325 | 741 | If you are experiencing any troubles with the short convenience macros `S()`, |
universe@325 | 742 | `SC()`, or `ST()`, you can disable them by setting the macro |
universe@325 | 743 | `UCX_NO_SSTR_SHORTCUTS` before including the header (or via a compiler option). |
universe@325 | 744 | For the formatting macros `SFMT()` and `PRIsstr` you can use the macro |
universe@325 | 745 | `UCX_NO_SSTR_FORMAT_MACROS` to disable them. |
universe@325 | 746 | |
universe@325 | 747 | Please keep in mind, that after disabling the macros, you cannot use them in |
universe@325 | 748 | your code *and* foreign code that you might have included. |
universe@325 | 749 | You should only disable the macros, if you are experiencing a nasty name clash |
universe@325 | 750 | which cannot be otherwise resolved. |
universe@325 | 751 | |
universe@259 | 752 | ## Testing |
universe@259 | 753 | |
universe@390 | 754 | *Header file:* [test.h](api-2.1/test_8h.html) |
universe@259 | 755 | *Required modules:* None. |
universe@259 | 756 | |
universe@259 | 757 | This module provides a testing framework which allows you to execute test cases |
universe@259 | 758 | within test suites. |
universe@259 | 759 | To avoid code duplication within tests, we also provide the possibility to |
universe@259 | 760 | define test subroutines. |
universe@259 | 761 | |
universe@297 | 762 | You should declare test cases and subroutines in a header file per test unit |
universe@297 | 763 | and implement them as you would implement normal functions. |
universe@297 | 764 | ```C |
universe@297 | 765 | /* myunit.h */ |
universe@297 | 766 | UCX_TEST(function_name); |
universe@297 | 767 | UCX_TEST_SUBROUTINE(subroutine_name, paramlist); /* optional */ |
universe@297 | 768 | |
universe@297 | 769 | |
universe@297 | 770 | /* myunit.c */ |
universe@297 | 771 | UCX_TEST_SUBROUTINE(subroutine_name, paramlist) { |
universe@297 | 772 | /* ... reusable tests with UCX_TEST_ASSERT() ... */ |
universe@297 | 773 | } |
universe@297 | 774 | |
universe@297 | 775 | UCX_TEST(function_name) { |
universe@297 | 776 | /* ... resource allocation and other test preparation ... */ |
universe@297 | 777 | |
universe@297 | 778 | /* mandatory marker for the start of the tests */ |
universe@297 | 779 | UCX_TEST_BEGIN |
universe@297 | 780 | |
universe@297 | 781 | /* ... verifications with UCX_TEST_ASSERT() ... |
universe@297 | 782 | * (and/or calls with UCX_TEST_CALL_SUBROUTINE()) |
universe@297 | 783 | */ |
universe@297 | 784 | |
universe@297 | 785 | /* mandatory marker for the end of the tests */ |
universe@297 | 786 | UCX_TEST_END |
universe@297 | 787 | |
universe@297 | 788 | /* ... resource cleanup ... |
universe@297 | 789 | * (all code after UCX_TEST_END is always executed) |
universe@297 | 790 | */ |
universe@297 | 791 | } |
universe@297 | 792 | ``` |
universe@297 | 793 | If you want to use the `UCX_TEST_ASSERT()` macro in a function, you are |
universe@297 | 794 | *required* to use a `UCX_TEST_SUBROUTINE`. |
universe@297 | 795 | Otherwise the testing framework does not know where to jump, when the assertion |
universe@297 | 796 | fails. |
universe@297 | 797 | |
universe@297 | 798 | After implementing the tests, you can easily build a test suite and execute it: |
universe@297 | 799 | ```C |
universe@297 | 800 | UcxTestSuite* suite = ucx_test_suite_new(); |
universe@297 | 801 | ucx_test_register(suite, testMyTestCase01); |
universe@297 | 802 | ucx_test_register(suite, testMyTestCase02); |
universe@297 | 803 | /* ... */ |
universe@297 | 804 | ucx_test_run(suite, stdout); /* stdout, or any other FILE stream */ |
universe@297 | 805 | ``` |
universe@297 | 806 | |
universe@259 | 807 | ## Utilities |
universe@259 | 808 | |
universe@390 | 809 | *Header file:* [utils.h](api-2.1/utils_8h.html) |
universe@259 | 810 | *Required modules:* [Allocator](#allocator), [String](#string) |
universe@259 | 811 | |
universe@259 | 812 | In this module we provide very general utility function for copy and compare |
universe@259 | 813 | operations. |
universe@259 | 814 | We also provide several `printf` variants to conveniently print formatted data |
universe@259 | 815 | to streams or strings. |
universe@259 | 816 | |
universe@279 | 817 | ### A simple copy program |
universe@279 | 818 | |
universe@279 | 819 | The utilities package provides several stream copy functions. |
universe@279 | 820 | One of them has a very simple interface and can, for instance, be used to copy |
universe@279 | 821 | whole files in a single call. |
universe@279 | 822 | This is a minimal working example: |
universe@279 | 823 | ```C |
universe@279 | 824 | #include <stdio.h> |
universe@279 | 825 | #include <ucx/utils.h> |
universe@279 | 826 | |
universe@279 | 827 | int main(int argc, char** argv) { |
universe@279 | 828 | |
universe@279 | 829 | if (argc != 3) { |
universe@279 | 830 | fprintf(stderr, "Use %s <src> <dest>", argv[0]); |
universe@279 | 831 | return 1; |
universe@279 | 832 | } |
universe@279 | 833 | |
universe@294 | 834 | FILE *srcf = fopen(argv[1], "r"); /* insert error handling on your own */ |
universe@279 | 835 | FILE *destf = fopen(argv[2], "w"); |
universe@279 | 836 | |
universe@279 | 837 | size_t n = ucx_stream_copy(srcf, destf, fread, fwrite); |
universe@279 | 838 | printf("%zu bytes copied.\n", n); |
universe@279 | 839 | |
universe@279 | 840 | fclose(srcf); |
universe@279 | 841 | fclose(destf); |
universe@279 | 842 | |
universe@279 | 843 | |
universe@279 | 844 | return 0; |
universe@279 | 845 | } |
universe@279 | 846 | ``` |
universe@279 | 847 | |
universe@281 | 848 | ### Automatic allocation for formatted strings |
universe@279 | 849 | |
universe@281 | 850 | The UCX utility function `ucx_asprintf()` and it's convenient shortcut |
universe@281 | 851 | `ucx_sprintf` allow easy formatting of strings, without ever having to worry |
universe@281 | 852 | about the required space. |
universe@281 | 853 | ```C |
universe@281 | 854 | sstr_t mystring = ucx_sprintf("The answer is: %d!", 42); |
universe@281 | 855 | ``` |
universe@281 | 856 | Still, you have to pass `mystring.ptr` to `free()` (or the free function of |
universe@281 | 857 | your allocator, if you use `ucx_asprintf`). |
universe@281 | 858 | If you don't have all the information ready to build your string, you can even |
universe@281 | 859 | use a [UcxBuffer](#buffer) as a target with the utility function |
universe@281 | 860 | `ucx_bprintf()`. |
universe@281 | 861 | ```C |
universe@281 | 862 | UcxBuffer* strbuffer = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND); |
universe@281 | 863 | |
universe@281 | 864 | for (unsigned int i = 2 ; i < 100 ; i++) { |
universe@281 | 865 | ucx_bprintf(strbuffer, "Integer %d is %s\n", |
universe@281 | 866 | i, prime(i) ? "prime" : "not prime"); |
universe@281 | 867 | } |
universe@281 | 868 | |
universe@294 | 869 | /* print the result to stdout */ |
universe@281 | 870 | printf("%s", (char*)strbuffer->space); |
universe@281 | 871 | |
universe@281 | 872 | ucx_buffer_free(strbuffer); |
universe@281 | 873 | ``` |