Working with C-Style Strings

Implementing a Circular Buffer with C-Strings

How can I implement a circular buffer using C-style strings?

Abstract art representing computer programming

Implementing a circular buffer (also known as a ring buffer) using C-style strings can be useful for scenarios where you need to store a fixed number of recent messages or log entries. Here's an example of how to implement a simple circular buffer:

#include <iostream>
#include <cstring>

// Custom strlcpy implementation
size_t strlcpy(
  char* dst, const char* src, size_t dstsize
) {
  size_t srclen = std::strlen(src);
  size_t copylen =
      (srclen >= dstsize) ? dstsize - 1 : srclen;
  if (dstsize > 0) {
    std::memcpy(dst, src, copylen);
    dst[copylen] = '\0';
  }
  return srclen;
}

class CircularBuffer {
 private:
  static const int MAX_ENTRIES{5};
  static const int MAX_STRING_LENGTH{50};
  char buffer[MAX_ENTRIES][MAX_STRING_LENGTH];
  int head{0};
  int count{0};

 public:
  void add(const char* str) {
    strlcpy(buffer[head], str, MAX_STRING_LENGTH);
    head = (head + 1) % MAX_ENTRIES;
    if (count < MAX_ENTRIES) count++;
  }

  void print() const {
    for (int i = 0; i < count; ++i) {
      int index{(head - count + i + MAX_ENTRIES) %
                MAX_ENTRIES};
      std::cout << buffer[index] << '\n';
    }
  }
};

int main() {
  CircularBuffer cb;

  cb.add("First message");
  cb.add("Second message");
  cb.add("Third message");

  std::cout << "After adding 3 messages:\n";
  cb.print();

  cb.add("Fourth message");
  cb.add("Fifth message");
  cb.add("Sixth message");

  std::cout << "\nAfter adding 3 more messages:\n";
  cb.print();
}
After adding 3 messages:
First message
Second message
Third message

After adding 3 more messages:
Second message
Third message
Fourth message
Fifth message
Sixth message

Let's break down this implementation:

  1. We define a CircularBuffer class with a fixed-size 2D array of characters to store our strings.
  2. The head variable keeps track of where to insert the next string.
  3. The count variable tracks how many strings are currently in the buffer.
  4. The add() method:
    • Copies the input string to the current head position.
    • Uses strncpy() to prevent buffer overflows.
    • Ensures null-termination.
    • Updates head and count.
  5. The print() method iterates through the buffer, starting from the oldest entry.

This implementation has some limitations:

  • It has a fixed maximum number of entries and maximum string length.
  • It doesn't provide direct access to individual entries.

We can enhance this implementation to make it more flexible and powerful:

#include <iostream>
#include <memory>
#include <algorithm>
#include <cstring>

class CircularBuffer {
 private:
  std::unique_ptr<char[]> buffer;
  size_t bufferSize;
  size_t maxEntries;
  size_t head{0};
  size_t count{0};

  size_t getIndex(size_t position) const {
    return (head - count + position + maxEntries) %
           maxEntries;
  }

 public:
  CircularBuffer(size_t entries, size_t entrySize)
  : buffer(
    std::make_unique<char[]>(entries * entrySize)
    ),
    bufferSize(entrySize),
    maxEntries(entries) {}

  void add(const char* str) {
    std::fill(&buffer[head * bufferSize],
      &buffer[head * bufferSize + bufferSize],
      '\0');
    std::copy_n(str,
      std::min(bufferSize - 1, std::strlen(str)),
      &buffer[head * bufferSize]);
    head = (head + 1) % maxEntries;
    if (count < maxEntries) count++;
  }

  const char* get(size_t position) const {
    if (position >= count) return nullptr;
    return &buffer[
      getIndex(position) * bufferSize];
  }

  void print() const {
    for (size_t i = 0; i < count; ++i) {
      std::cout << get(i) << '\n';
    }
  }

  size_t size() const { return count; }
  size_t capacity() const { return maxEntries; }
};

int main() {
  // 5 entries, each up to 50 characters
  CircularBuffer cb(5, 50);

  cb.add("First message");
  cb.add("Second message");
  cb.add("Third message");
  cb.add("Fourth message");
  cb.add("Fifth message");
  cb.add("Sixth message");

  std::cout << "Buffer contents:\n";
  cb.print();

  std::cout << "\nThird oldest message: "
    << cb.get(2) << '\n';
  std::cout << "Buffer size: "
    << cb.size() << '\n';
  std::cout << "Buffer capacity: "
    << cb.capacity() << '\n';
}
Buffer contents:
Second message
Third message
Fourth message
Fifth message
Sixth message

Third oldest message: Fourth message
Buffer size: 5
Buffer capacity: 5

This enhanced version:

  • Uses dynamic memory allocation for flexibility.
  • Provides a get() method for accessing individual entries.
  • Includes size() and capacity() methods for querying the buffer state.

Remember, while this implementation uses C-style strings for demonstration, in real-world C++ applications, using std::string and std::vector or std::array might be more appropriate and safer.

Answers to questions are automatically generated and may not have been reviewed.

A computer programmer
Part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access

This course includes:

  • 124 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Free, Unlimited Access

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved