- The most idiomatic and elegant 'dynamic array in C' solution is stb_ds.h, it's as simple as that :)
The 'public handle' is a pointer to the array elements so it has the same semantics as a regular C array, the meta-data (capacity and length) are stored directly in front of the array items. Growing the array has the same behaviour as realloc (e.g. you may get a new pointer back).
- No structs, just an array that accomplishes the same thing, without field names or other niceties. Enjoy the pleasure of not using a struct when you inevitably add/reduce/reorder fields later.
by procaryote
1 subcomments
- Why would you want to avoid using a struct? Add a macro that declares the appropriate struct and get at least a tiny bit of type checking.
With some clever use of _Generic you could even build specialised functions for that type and get pretty good type checking
- The C standard doesn’t guarantee that arbitrary integer values converted to a pointer and back result in the same integer values again. It only guarantees the other direction, that a valid pointer to void, when converted to uintptr_t and back again, will result in a pointer that compares equal to the original. The conversion from uintptr_t to pointer may for example clear or truncate some of the bits of the integer value, or normalize it in some other way.
- Clever!
I don't like the use of uintptr_t, though. Why not storing the array begin in v[0] and a pointer to the first free item in v[1]? You avoid casts by defining len as ptrdiff_t and calculating it as v[1]-v[0].
by userbinator
3 subcomments
- capacity isn't stored at all. Instead, it's computed on demand when the length of the vec is either zero or a power of two.
Brilliant insight. This is the first time I've seen this observation in over 3 decades of working with C.
- https://github.com/gritzko/libabc/blob/main/Sx.h
https://github.com/gritzko/libabc/blob/main/S.md
ABC uses s[2] for slices, g[3] for gauges, b[4] for (ring) buffers. Also containers on top of those (heaps, hash sets, etc etc)
by dwroberts
1 subcomments
- > First of all, structs aren't used so you don't have to invent names for them (e.g. there is no IntVec)
But since it’s storing a void pointer any way, they wouldn’t need separate names right? You could use one struct everywhere regardless of the type of the items
Which IMO is a better idea than using an array here because the fields can be properly named and typed to prevent accidental misuse
- Enjoy the annoying-to-debug errors when someone inevitably mixes arr[0] with arr[1] and tramples the heap (this could be mitigated by accessing the fields with macros), or writes arr[3] because they forgot this is not a regular array.
- That's pretty clever code. Too clever for my tastes.
- Strictly speaking, the capacity is still stored internally to the allocation (it needs to be, in order to implement realloc)
by senderista
1 subcomments
- This is just silly. You can't even reserve capacity because you only store size and capacity is implicitly the next power of 2 >= size.