Buffer
This buffer implementation can be used to read from or write to memory like you would do with a stream.
This allows the use of cx_stream_copy() (see Data Streams) to copy contents from one buffer to another, or from a file or network stream to the buffer and vice versa.
More features for convenient use of the buffer can be enabled, like automatic memory management, or automatic resizing of the buffer space.
The functions cxBufferRead() and cxBufferWrite() are cx_read_func and cx_write_func compatible, which in turn have a compatible signature to fread() and fwrite(). However, due to the different pointer type the function pointers do not type check out of the box. For convenience, the macros cxBufferReadFunc and cxBufferWriteFunc are defined, which perform the necessary cast.
Example
In the following example we use a CxBuffer, the bprintf() macro from the UCX printf header, and a UCX map to create a simple HTTP GET request.
Create
For creating a UCX buffer, you have two options: initialize a pre-allocated structure, or allocate and initialize a new structure in one call.
For the first option, you can call cxBufferInit() with the buffer argument pointing to an uninitialized CxBuffer structure. You can pass a pre-allocated space, the desired capacity, and an allocator. If space is NULL, the allocator is used to allocate enough space to match the desired capacity.
The flags argument is a bit-wise-or combination of the constants listed below.
The function cxBufferCreate() is equivalent, except that it uses the allocator to allocate a new CxBuffer structure, instead of initializing an existing one.
If any allocation fails, cxBufferInit() returns non-zero, or cxBufferCreate() returns NULL, respectively.
Flag | Description |
|---|---|
CX_BUFFER_DEFAULT | Alias for zero, meaning no flags are set. |
CX_BUFFER_FREE_CONTENTS | When the buffer structure is destroyed, the memory for the contents is also automatically deallocated. |
CX_BUFFER_AUTO_EXTEND | When a write operation would exceed the buffers capacity, the buffer is grown automatically with a call to |
CX_BUFFER_COPY_ON_WRITE | Any write operation will result in allocating a copy of the data, first. This is particularly useful, when a buffer is initialized with read-only memory. |
CX_BUFFER_COPY_ON_EXTEND | Only when a write operation would exceed the buffers capacity, the copy-on-write operation is performed. This is useful, when a buffer is initialized with read-write memory which is allocated on the stack. |
Destroy
The above functions destroy the buffer and deallocate the buffer's memory when the CX_BUFFER_FREE_CONTENTS flag is set.
The function cxBufferDestroy() is to be used when the buffer was initialized with cxBufferInit(), and the function cxBufferFree() is to be used when the buffer was created with cxBufferCreate(). The only difference is that cxBufferFree() additionally deallocates the memory of the CxBuffer structure.
Capacity Management
The function cxBufferMaximumCapacity() specifies an upper limit for auto-growing the buffer's capacity. If the new threshold is smaller than the current capacity, this function fails and returns non-zero.
The function cxBufferMinimumCapacity() guarantees a buffer capacity of at least capacity. It will grow the capacity in powers of two until the system's page size is reached. Then, the new capacity will be a multiple of the page size. The function returns non-zero if increasing the capacity was attempted unsuccessfully; that includes attempts to specify a minimum capacity that exceeds the maximum capacity.
The function cxBufferReserve(), on the other hand, will reallocate the buffer's space to match exactly the requested capacity and the contents are truncated if required.
You should use cxBufferReserve() when you know precisely the required capacity beforehand and cxBufferMinimumCapacity() when it is likely that the buffer needs to regrow soon.
The function cxBufferShrink() can be used to shrink the capacity of the buffer to its current size plus a number of reserve bytes. If the current capacity is not larger than the size plus the reserve bytes, the function will do nothing.
Write
The primary function for writing to a buffer is cxBufferWrite() which writes up to nitems with size bytes each from the memory pointed to by ptr to the buffer.
When the capacity of the buffer is not sufficient and the CX_BUFFER_AUTO_EXTEND is not set in the buffer, items that do not fit into the buffer are discarded. The function then returns the actual number of items successfully written. This equals the number of bytes if and only if size=1. If CX_BUFFER_AUTO_EXTEND is set, the buffer is grown to its maximum capacity (see cxBufferMaximumCapacity() in Capacity Management). In case the allocation for auto-extension fails, the function immediately returns zero and does not write any data.
The function cxBufferPut() is a putc()-like wrapper for cxBufferWrite() which writes the character c, converted to an unsigned char to the buffer. Just like putc() this function returns the written character on success, and EOF on failure, but it does not set errno on failure.
The function cxBufferPutString() is a convenience function that uses cx_strcast() to convert any supported string type to a cxstring and then invokes cxBufferWrite(). Similarly, cxBufferAppendString() performs the same operation with cxBufferAppend().
All the above functions advance the buffer position by the number of bytes written and cause the size of the buffer to grow, if necessary, to contain all written bytes. On the other hand, cxBufferTerminate() writes a zero-byte at the current position and shrinks the buffer, effectively creating a zero-terminated string whose size equals the buffer size.
The function cxBufferAppend() writes the data to the end of the buffer (given by its size) regardless of the current position, and it also does not advance the position.
Read
The function cxBufferRead() reads nitems number of items of size bytes each from the buffer and stores them into the memory pointed to by ptr, which must be large enough to hold the contents. The function returns the actual bytes read, which might be lower if the desired number of items is not available.
The function cxBufferGet() is an fgetc()-like function which returns the next byte in the buffer converted to an int. Similar to fgetc() it returns EOF when there are no more bytes in the buffer.
When all bytes from the buffer have been read, the function cxBufferEof() returns true.
All read functions advance the position in the buffer by the number of read bytes.
Reset and Clear
The function cxBufferReset() sets both the size and position of the buffer to zero, and cxBufferClear() additionally uses memset() to set every byte in the buffer to zero.
The function cxBufferPop() erases the last nitems with the given size and returns the number of items that could be erased. When the buffer contains fewer bytes than requested and the number of bytes is not divisible by the item size, the remainder will stay in the buffer.
Random Access
The function cxBufferSeek() is a fseek()-like function that sets the current position in the buffer relative to the start (when whence is SEEK_SET), the current position (when whence is SEEK_CUR), or end (when whence is SEEK_END) of the buffer.
If the resulting position is negative, or larger than the size (i.e., the first unused byte), this function returns non-zero and sets errno to EINVAL.
Shift Contents
The function cxBufferShift() moves the contents within the buffer by the specified shift offset, where a negative offset means a shift to the left, and a positive offset means a shift to the right. It also adjusts the current position within the buffer, and in the case of a right shift also the size, by the same offset.
Data shifted to the left is always discarded when the new position of a byte would be smaller than zero. When bytes are discarded, the new position of the buffer is set to zero.
When data is shifted to the right, the behavior depends on the CX_BUFFER_AUTO_EXTEND flag. If set, the function extends the buffer's capacity before moving the data. Otherwise, the function discards all data that would exceed the buffer's capacity, and both the size and the position are equal to the capacity (which means, cxBufferEof() returns true after the operation).
The functions cxBufferShiftRight() and cxBufferShiftLeft() accept a larger (but in both cases positive) shift offset, which usually makes little sense on a 64-bit platform where off_t is already large enough to represent any reasonable offset. You may, however, still use those functions to express more explicitly in your code in which direction you want the contents to be shifted.