Short answer: There isn't a good way. The best bet is to stick with having the API return an object that the caller frees.
Long answer: Here is an alternative, allowing stack allocation of an opaque object. There are caveats:
- You need to know the size of the actual object
- You need to understand alignment requirements for your machine and toolkit (a little)
- alignment of fields
- end-padding of structs, to achieve field alignment
Caveat 1 can be handled by having a utility function to print the size (not exported to the user API), along with assertions to catch errors.
Caveat 2 can be handled by adding an element of the type with the strictest alignment requirements in the user-visible definition (though it needn't be in the same place.)
Maintaining alignment avoids the need to use serialization as used in @2501's answer.
In the example below, you can ignore code below "// trivial implementation" comments. They're simply to provide a complete working example, but the algorithms are irrelevant to the OP.
map.h
#include <stdlib.h>
#define MAP_ITER_SIZE 16
typedef struct {
void *p; // force alignment to match implementation
char space[MAP_ITER_SIZE-sizeof(void*)];
} map_iterator;
typedef struct map map;
map *map_new(void);
void map_iterator_init(map_iterator *iter, map *m);
int map_iterator_next(map_iterator *iter, int *p_key);
map_user.c
#include <stdlib.h>
#include <stdio.h>
#include "map.h"
int main(int argc, char * argv[])
{
map_iterator it;
int key;
map *m = map_new();
map_iterator_init(&it, m);
while (map_iterator_next(&it, &key)) {
printf("%d\n", key);
}
}
map.c
#include <stdlib.h>
#include <assert.h>
#include "map.h"
#define INITIAL_KEY (-1)
struct map {
int key_count;
int first_key;
};
// Keep struct size consistent with MAP_ITER_SIZE in map.h
typedef struct {
map *m;
int cur_key;
} map_iterator_impl;
map *map_new(void) {
map *m = malloc(sizeof(struct map));
// trivial implementation for example only
m->key_count = 2;
m->first_key = 10;
}
void map_iterator_init(map_iterator *iter, map *m)
{
map_iterator_impl *iter_impl = (map_iterator_impl *)iter;
assert(sizeof(map_iterator) == sizeof(map_iterator_impl)); // optimizes out
// trivial implementation for example only
iter_impl->m = m;
iter_impl->cur_key = INITIAL_KEY; // not a valid key
}
int map_iterator_next(map_iterator *iter, int *p_key)
{
map_iterator_impl *iter_impl = (map_iterator_impl *)iter;
// trivial implementation for example only
if (iter_impl->cur_key == INITIAL_KEY) {
iter_impl->cur_key = iter_impl->m->first_key;
} else {
++iter_impl->cur_key;
}
if (iter_impl->cur_key - iter_impl->m->first_key >= iter_impl->m->key_count) {
return 0;
}
*p_key = iter_impl->cur_key;
return 1;
}
unsigned int get_impl_size()
{
return (unsigned int) sizeof(map_iterator_impl);
}
Pundits will argue against this and they'll have good points. The main argument is that the code isn't portable without jumping through hoops to get the SIZE constant correct for all supported (processor,compiler) cases. You also need to know what data type has the biggest alignment requirement, for each case.
new
andfree
once instead of once for every loop. – Elberfeldmalloc
andfree
in the first place. – Virtunext()
will be called in a loop andmemcpy()
on each loop iteration is far worse than a singlemalloc()
+free()
, unfortunately. Maybe struct reinterpretation will work, but I'm not sure if that's guaranteed by the C standard. – Virtu