lib/buffers.h describes Dovecot's buffer API. Unless your code happens to be VERY performance critical, you shouldn't handle writing to buffers/arrays manually, but instead use the buffer API's safe functions to guarantee that your code can't write past the buffer and cause a security hole.
There are a two different ways to create buffers: statically and dynamically allocated.
You can create statically allocated buffers with buffer_create_data(). Trying to write past the given buffer size will panic. The code to initialize this looks like:
unsigned char buf_data; buffer_t buf; buffer_create_data(&buf, buf_data, sizeof(buf_data));
Trying to write more than 1024 bytes to the buffer will cause an assert-crash, so these buffers shouldn't be used unless you know exactly what the maximum buffer size is.
To avoid accidental buffer overflows, don't use any more complex calculations in the size parameter of buffer_create_data(). It should always be sizeof(data_buffer).
You can also create non-writable buffers with buffer_create_const_data(). Static buffers don't need to be freed.
Dynamically growing buffers can be created with buffer_create_dynamic(pool, init_size). Memory for buffer is allocated from the given pool. When memory needs to be grown, it's grown exponentially (2^n), with some exceptions to avoid growing the given memory pool unless necessary. The initial buffer size is always a guess - try to make it large enough that buffer wouldn't be grown most of the time, but not so large that it wastes memory.
You should be careful with memory returned by buffer_get_space_unsafe() and buffer_append_space_unsafe(). This returned memory should be accessed immediately afterwards and it must not be accessed anymore after other buffer_*() calls, because they may reallocate the buffer and move it elsewhere in memory.
Buffers always look like they're filled with NUL bytes. If you write past the end of buffer, all the inserted bytes are filled with NULs. If you shrink the buffer with buffer_set_used_size() and again write past the end of used size, all the old data is again gone and filled with NULs. If you for some reason want to just temporarily shrink the buffer size and then change it back, you can use buffer_set_used_size() to grow it back to its original size (but no larger).