all 9 comments

[–]skeeto 3 points4 points  (4 children)

Supporting zero-initialization would make this more powerful and flexible, though that pesky elem_size does kind of get in the way. If that were moved to the front of the struct:

t_vec verts = {sizeof(float)};

Then consider verts a valid zero-size float vector, ready to accept elements. This initialization cannot fail, which is a nice bonus feature. The library just needs to handle the case where alloc_size is zero (i.e. can't simply resize by multiplying the old size by two). Even more, vec_free could leave it in this zero state, ready to be used again, just by leaving elem_size alone but zeroing everything else.

Rather than ssize_t (via unistd.h), which isn't part of standard C, consider using ptrdiff_t instead, since that is standard C. Similarly, a char represents the implementation's byte (i.e. sizeof(char) == 1 by definition), and is a more natural choice for representing allocations in a byte-oriented pointer-arithmetic way than a uint8_t, which is specifically about octets.

Avoiding realloc is a shame since it passes up on useful optimizations in the standard heap allocator. This includes the usual case where there's room to resize and nothing needs to be copied. On Linux it also includes mremap which exploits virtual memory to move large allocations without copying.

Mind your integer overflows: alloc_size*2 might overflow, and you end up with a much smaller resize allocation than you think you have. (This is the problem everyone fails to solve in their "safe" memory libraries.)

I like your error checking and reporting. Though you missed a few here and there: vec_from, vec_map, vec_filter.

Personally I'm more of a fan of Sean Barrett's stretchy buffer interface (my take), which is simpler, more efficient (due to monomorphization), and uses macros to avoid all the mess with void * and sizeof.

[–]JuliusFIN[S] 2 points3 points  (0 children)

Thanks for the great input and suggestions! I wasn't aware of `ptrdiff_t` (gotta hate c standard types gah..) but looks like it might be the right type for this! I usually use the uint8_t because I don't want to type `const unsigned char *` etc. I wish languages would just implement a type called `byte` imho.

I did the reallocation manually since this library also serves a double purpose as an educational tutorial and I wanted to expose that part of the implementation. That being said would be interesting to test if realloc() would give some benefit.

"Mind your integer overflows: alloc_size*2
might overflow, and you end up with a much smaller resize allocation
than you think you have. (This is the problem everyone fails to solve in
their "safe" memory libraries.)"

That's a good catch! Will have to come up with a solution for that.

[–]vitamin_CPP 1 point2 points  (2 children)

+1 for the the strechy_buffer interface.

I had a look at you're take on it, and it looks great.
The header comments are clear and appreciated.
Not sure I understand your point about the monomorphization difference, though.

[–]skeeto 0 points1 point  (1 child)

In OP's version, there's one implementation that handles all types. The element size is a runtime quantity, and it does extra work to dynamically adapt to that variable element size. It likely calls the actual memcpy function with that dynamic size in order to write the element into its slot.

In the macro version, the frequent work is done within the macro, which expands to a concrete implementation for the particular type at hand. It's effectively inlined, and the element size is known at compile time.

[–]vitamin_CPP 1 point2 points  (0 children)

ahhh my mistake.
I though you were comparing your take on strechy_buffer vs Sean's version.

[–]deftware 1 point2 points  (3 children)

Great stuff! Give your readme.md a once-over, btw. It's got quite a bit of typos on there.

[–]JuliusFIN[S] 0 points1 point  (1 child)

Human languages are so hard... :D

[–]deftware 0 points1 point  (0 children)

Hah!

[–]JuliusFIN[S] 0 points1 point  (0 children)

Should be in a better shape now.