Smiley Flower
Smiley Flower

Reputation: 100

Using Memcpy to append some Char* to a buffer containing Char*

I am writing a method to append some data to a buffer.

The Buffer looks like:

Buffer *new_buffer(size_t reserved) {
    Buffer* buffer = malloc(sizeof(Buffer));
    buffer->data   = (char*)calloc(reserved, sizeof(char));
    buffer->length = reserved;
    return buffer;
}

I would like to append data to the buffer, the method I have written is as follows:

void append_buffer(Buffer *buffer, char *data, size_t length) {
    if( buffer->length <= length ) {
        buffer->length = buffer->length + length;
        buffer->data   = realloc( buffer->data, buffer->length * 2 );
    }
    memcpy( &buffer->data[ strlen( buffer->data ) - 1 ], data, length );

}

Note that the size parameter is just the size of the array to append.

i.e.

append_buffer(buffer, data, len_data);

I believe there is errors with my memcpy;

The thought process is to use memcpy to place the data into memory for the last character in the buffer; so that we can avoid the terminating character.

Running this just doesn't append any data and I'm not really sure why!

Upvotes: 0

Views: 1156

Answers (1)

Dai
Dai

Reputation: 154995

When using buffers in C that contain arbitrary binary data you need to store two length fields: the capacity of the buffer as-allocated, and the amount of data currently stored in the buffer.

(For comparison to C#/.NET and Java, this is like how List<T> (ArrayList in Java) has both an internal T[].Length and a separate Count value).

I've rewritten your code to show how I would do it. Note that:

  • The Buffer struct stores both capacity and length.
  • The code uses assert() to validate parameters and verify that allocations succeeded. This is only for demonstration purposes because assert is removed from the program if NDEBUG is defiend, whereas memory allocations should always be verified.
  • I separated the logic for appending (append_buffer) from the logic for reallocating the buffer (expand_buffer).

Like so:

#undef NDEBUG // So `assert` is always compiled.

struct Buffer {
    char*  data;
    size_t capacity; // Size of the actual allocated space
    size_t length;   // Amount of space currently used
};

struct Buffer* new_buffer( size_t initialCapacity ) {
    struct Buffer* info = malloc( sizeof(Buffer) ); // Allocate space for the metadata
    assert( info ); // Always ensure allocation succeeded!
    info->data = calloc( initialCapacity, sizeof(char) );
    assert( info->data );
    info->capacity = initialCapacity;
    info->length   = 0;
    return info;
}

void append_buffer( struct Buffer* buffer, char* data, size_t dataLength ) {
    assert( buffer ); // Parameter argument validation
    assert( data );
    if( dataLength == 0 ) return;

    size_t remainingCapacity = buffer->capacity - buffer->length;
    if( remainingCapacity < dataLength ) {
        size_t desiredCapacity = buffer->capacity + dataLength; // You might want to add a growth factor here
        expand_buffer( buffer, desiredCapacity );
    }

    char* start = buffer->data + buffer->length;
    memcpy( start, data, dataLength );
    buffer->length += dataLength;
}

void expand_buffer( struct Buffer* buffer, size_t desiredCapacity ) {
    assert( buffer );
    assert( buffer->data );
    char* newData = realloc( buffer->data, desiredCapacity );
    assert( newData );
    buffer->data     = newData;
    buffer->capacity = desiredCapacity;
    // buffer->length remains unchanged
}

void destroy_buffer( struct Buffer* buffer ) {
    assert( buffer );

    if( buffer-> data ) {
        free( buffer->data );
        buffer->data = NULL;
    }

    free( buffer );
}

Upvotes: 1

Related Questions