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 streams to the buffer and vice versa.
More features for convenient use of the buffer can be enabled, like automatic memory management, automatic resizing of the buffer space, or automatic flushing of contents.
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.
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 always returns the actual number of items successfully written. This equals the number of bytes if and only if size=1
.
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 stdlib strlen()
to compute the length of str
and then invokes cxBufferWrite()
.
All of the above functions advance the buffers 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, effectively creating a zero-terminated string whose size equals the buffer size.
The function cxBufferAppend()
writes the data to end of the buffer (given by its size) regardless of the current position, and it also does not advance the position. If the write operation triggered a flush, however, the position will be shifted left alongside the shifted buffer contents. In case the data at which the current position points gets flushed, the new position will be zero.
If you already (roughly) know how many bytes you will be writing to a buffer, you can save some allocations during auto-extension when you invoke cxBufferMinimumCapacity()
before writing the data. Usually you do not need to do this, unless you have many subsequence writes which impose the risk of multiple unnecessary reallocations.
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 sufficiently large 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 a 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 size and position of the buffer to zero, and cxBufferClear()
additionally uses memset()
to set every byte in the buffer to zero.
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 EOVERFLOW
.
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 case of a right shift also the size, by the same offset.
Data that is shift to the left, is always discarded when the new position of a byte would be smaller than zero. If the new position would be smaller than zero, it is set exactly to zero.
When data is shift 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 does not make much sense on a 64-bit platform where off_t
is already large enough to represent any reasonable offset. You may, however, still use those function to express more explicitly in your code in which direction you want the contents to be shifted.
Flushing
With the function cxBufferEnableFlushing()
you can configure a flushing strategy for the contents of the buffer.
Flushing, once enabled, may happen in the following cases:
when data is written to the buffer, the capacity is insufficient, and
CX_BUFFER_AUTO_EXTEND
is not enabledwhen data is written to the buffer, and the required capacity exceeds the
threshold
configurationwhen
cxBufferFlush()
is called explicitly
Flushing happens by invoking the wfunc
up to blkmax
times, writing up to blksize
bytes from the buffer to the target
with each call. The target might not accept all bytes (i.e. the wfunc
return value indicates that fewer items have been written than requested), in which case the remaining data remains in the buffer. That means, the buffer is effectively shifted left by the number of successfully flushed bytes.