Live data from Hacker News

Show HN: Embedded Ring Buffer C++98

github.com

1–10 of 41 posts

Re: Show HN: Embedded Ring Buffer C++98

#4
> memset(data, 0, LENGTH);

>// ...

> T data[LENGTH];

I'm not sure how important it is in practice, but I'm pretty sure you don't zero out the whole array for sizeof(T) > 1.

Anyhow, memsetting to 0 a complex type is... not something I'd recommend in most cases.

Re: Show HN: Embedded Ring Buffer C++98

#5
post #2

Looking for CC, advice and bugs!

Looks reasonable! On a system with virtual memory, one popular trick is the "virtual ring buffer," which lets the reader and writer always access a contiguous region of the full requested length. The idea is to map the same backing store twice sequentially in virtual memory. It leads to a much simpler implementation, because you don't have to handle the edge cases that relate to wrapping around.

Sample implementation in C++17 here:

https://github.com/stanford-stagecast/audio/blob/main/src/ut...

https://github.com/stanford-stagecast/audio/blob/main/src/ut...

Re: Show HN: Embedded Ring Buffer C++98

#6

Don't use "NULL" in C++[1]. [1] https://cpp4arduino.com/2018/10/26/why-cpp-programmers-dont-...

Eh. It's fine in my experience... of all the things I've gotten bitten by in C++, this has not been one of them. It's actually a bit more readable IMO... nullptr doesn't scream "null" like it should. And it's best not to overload int with pointers anyway, because other people using your code may still use NULL even if you personally don't. The one thing to watch out for is usage in template call sites, where you'd want to cast it to the correct type first, but at that point you'd want to cast the nullptr too.

Re: Show HN: Embedded Ring Buffer C++98

#7

> memset(data, 0, LENGTH); >// ... > T data[LENGTH]; I'm not sure how important it is in practice, but I'm pretty sure you don't zero out the whole array for sizeof(T) > 1. Anyhow, memsetting to 0 a complex type is... not something I'd recommend in most cases.

Ouch, yeah. They need std::fill(). This is the kind of thing that makes you lose faith in a C++ library. (Also, what's with the volatile private variables?!)

Re: Show HN: Embedded Ring Buffer C++98

#9
post #2

Looking for CC, advice and bugs!

Looks reasonable! On a system with virtual memory, one popular trick is the "virtual ring buffer," which lets the reader and writer always access a contiguous region of the full requested length. The idea is to map the same backing store twice sequentially in virtual memory. It leads to a much simpler implementation, because you don't have to handle the edge cases that relate to wrapping around. Sample implementation…

That is a very good idea.

You don't even need to double-map memory. Just make the ring buffer smaller than your actual buffer, and treat any overrun as if it had wrapped around, but really just write past the (official) end. You might waste a bit of space at the front when you do, just to keep the code simple, but if you didn't have some room to waste, you wouldn't be using a ring buffer.

Another way to simplify management is to give the ring buffer a power-of-two size, and make the head and tail counters 64 bits, masking off the high bits when actually looking at the buffer. They only ever increase.

Re: Show HN: Embedded Ring Buffer C++98

#10

> memset(data, 0, LENGTH); >// ... > T data[LENGTH]; I'm not sure how important it is in practice, but I'm pretty sure you don't zero out the whole array for sizeof(T) > 1. Anyhow, memsetting to 0 a complex type is... not something I'd recommend in most cases.

N.B. I'm writing my comments as I am reading your code. Please, don't take offense from my criticism, it is meant to be constructive, albeit concise.

/* * @brief Retrieve a continuous block of * valid buffered data. * @param num_reads_requested How many reads are required. * @param skip Whether to increment the read position * automatically, (false for manual skip * control) * @return A block of items containing the maximum * number the buffer can provide at this time. / Block Read(unsigned int num_reads_requested)

Where is skip?

> if (buffer_full) > { > / > * Tried to append a value while the buffer is full. > / > overrun_flag = true; > } > else > { > / > * Buffer isn't full yet, write to the curr write position > * and increment it by 1. > / > overrun_flag = false; > data[write_position] = value; > write_position = (write_position + 1U) % LENGTH; > }

You don't write in the case of an "overrun". Isn't that one of the most interesting property of a ringbuffer? It seems that your implementation is specific to your use case (buffering to sd cards?). I don't think your _current_ implementation is apropriate for a _general_ purpose ring buffer mostly because of api concerns. It may be interesting to emphasis this part in your doc.

> reads_to_end = LENGTH - read_position; > req_surpasses_buffer_end = num_reads_requested > reads_to_end; > > if (req_surpasses_buffer_end) > { > / > * If the block requested exceeds the buffer end. Then > * return a block that reaches the end and no more. > / > block.SetStart(&(data[read_position])); > block.SetLength(reads_to_end); > } > else > { > / > * If the block requested does not exceed 0 > * then return a block that reaches the number of reads required. > */ > block.SetStart(&(data[read_position])); > block.SetLength(num_reads_requested); > }

Maybe : > reads_to_end = LENGTH - read_position; > eff_reads = (num_reads_requested > reads_to_end) ? reads_to_end : num_reads_requested; > //or : eff_reads = std::min(num_reads_requested, reads_to_end) > block.SetStart(&(data[read_position])); > block.SetLength(eff_reads);

Still, I understand the need to be very explicit in an embedded context.

Same principle for ``if (!bridges_zero)`` (the ``else`` case)

Post reply on HN