Return Styles: Pseud0ch, Terminal, Valhalla, NES, Geocities, Blue Moon.

Pages: 1-

Circular Buffer

Name: Anonymous 2011-01-06 10:01

Hi /prog/

Some time ago I started to write a well designed library of useful algorithms in C. As long as I needed more stuff, I simply added pieces.

Today I'm pretty bored, so I'd like to add an implementation for circular buffers. Since however I don't really need it (it's just for fun), I'm wondering which is the most useful semantics for a circular buffer.

For instance:
[list]
[*] Is it worthy to write a primitive which applies a function on every availabe element from the oldest to the most recent?
[*] What if the buffer is full and I want to insert a new element? Should I overwrite the oldest one or should I make the request fail instead?
[/list]

Which are, in general, the kind of functionalities you'll prefer?

Name: Anonymous 2011-01-06 10:01

(also /prog/'s bbcode is pretty lame)

Name: Anonymous 2011-01-06 10:13

If by buffer you mean an array of bytes, then all that one would need to do is access it like this buff[index%length], where index is inputted, and length may be part of the structure holding the pointer to the buffer.

If you mean something else by "circular buffer", you're going to have to define it. Is it a linked list, a stack or queue or something else? All these have specific names and different behaviours and implementations. Decide exactly what semantics you want.

Name: Anonymous 2011-01-06 10:15

I'm surprised you didn't use ``noko''.

Name: Anonymous 2011-01-06 10:25

>>3
Yes, by circular buffer I mean a lightweight fixed-length array of void *. So you think about something like

void * ValueAt (Buffer *buf, int idx)
{
  return buf->data[idx % buf->size];
}

in which the user simply needs to manage one incremental integer?

>>4
There's no point in using noko on a board which is so slow...

Name: Anonymous 2011-01-06 10:30

[*] Is it worthy to write a primitive which applies a function on every availabe element from the oldest to the most recent?

You are speaking of the Visitor pattern. Visitors are useful, but in C, you'd have to do this using function pointers, and the overhead of calling a function through indirection is slow. C doesn't lend itself to this type of thing very well. So I probably wouldn't bother. Instead, I'd implement some C99 inline functions to quickly allow someone to iterate over the values and pop values out of the circular buffer.

[*] What if the buffer is full and I want to insert a new element? Should I overwrite the oldest one or should I make the request fail instead?

You can implement both.


// pushes the value pointed to by value forcefully overwriting old values.
void cbuffer_push(cbuffer_t *cb, void* value);

// pushes a value into the buffer if space is available. returns 1 if successful, 0 if no space is available
int cbuffer_try_push(cbuffer_t *cb, void* value);

Name: Anonymous 2011-01-06 10:44

>>5
I'd also store the index of the head and tail nodes in the data structure. Leaving that for the user of the data structure is making it more difficult for your users, making their code more error-prone. Should look something like this:


typedef struct
{
    void **data;
    size_t size;
    size_t head;
    size_t tail;
}
cbuffer_t;


The push operation inserts an item at tail and increments tail, and head pops an item at head an increments head.

When either head or tail go beyond the size of the buffer, they're wrapped around via modulus.

When head reaches tail, the buffer is empty and you can't pop anything more out of it.

Name: Anonymous 2011-01-06 10:44

>>6
Yes, it's Visitor Pattern, however it shouldn't get more overhead than calling a regular function: the function pointer is just a pointer, indeed.

Good idea for the both-implementation. Let's the user decide what he (or she, but probably he) needs.

Name: Anonymous 2011-01-06 10:45

>>6
Wouldn't it be best if he just used a linked list if all he wanted was to push/pop elements? No overwriting needed there, and requests won't fail, unless you run out of RAM.

Name: >>9 2011-01-06 10:50

Of course, if you need random access, a circular buffer which doubles/increases its size each time it runs out is a better choice.

Name: Anonymous 2011-01-06 10:54

>>1

#ifdef __STDC_VERSION__
#if (__STDC_VERSION__ >= 199901L)
#define _inline inline
#else
#define _inline
#endif
#else
#define _inline
#endif

#ifdef __GNUC__
#define _pure __attribute__ ((pure))
#define _pure_inline __attribute__ ((pure) (always_inline))
#else
#define _pure
#define _pure_inline
#endif

typedef struct cbuf_t {
   int *buffer;
   int length;
} cbuf_t;

cbuf_t *cbuf_new(int);
_inline int cbuf_at(cbuf_t *, int) _pure_inline;
int cbuf_ref(cbuf_t *, int) _pure;
void cbuf_set(cbuf_t *, int, int);
void cbuf_destroy(cbuf_t *);

cbuf_t *cbuf_new(int length) {
   cbuf_t *buf = malloc(sizeof(buf));
   buf->buffer = malloc(sizeof(int)*length);
   buf->length = length;
   return buf;
}
int cbuf_at(cbuf_t *buf, int i) { //
   return (i>=buf->length)?(i-buf->length):i;
int cbuf_ref(cbuf_t *BUFFA, int index) {
   return BUFFA->buffer[cbuf_at(index)];
}
void cbuf_set(cbuf_t *BUFFA, int index, int val) {
   BUFFA->buffer[cbuf_at(index)] = val;
}
void cbuf_destroy(cbuf_t *buf) {
   free(buf->buffer);
   free(buf);
}

Name: Anonymous 2011-01-06 10:57

>>9
Sometimes you need something quick and without memory allocation. By example, in real timing memory allocation can be an issue, because of the time unpredictability.

Name: Anonymous 2011-01-06 11:00

>>8
Well, calling a function once isn't a problem. Having to call a function for each node in the data structure is. CPU has to perform a branch, create a stack frame, etc. You might be end up causing a cache miss on a each function call. Much faster to do:

for (size_t i = 0, end = cbuffer_size(cb); i < end; ++i) {
   void *value = cbuffer_at(pos);
   // do something with value
}


Where cbuffer_value_at is just:

inline size_t cbuffer_size(cbuffer_t *cb) {
    return (cb->head > cb->tail) ? (cb->size - cb->head + cb->tail) : (cb->tail - cb->head);
}

inline void *cbuffer_at(cbuffer_t *cb, size_t offset) {
    assert(cb->offset < cbuffer_size(cb));
    return cb->data[(offset + cb->head) % cb->size];
}


Since they're inline functions, they get inlined, and you don't have any call overhead per node in the circular buffer.

>>9
Then it wouldn't be called a circular buffer then, would it?

Name: Anonymous 2011-01-06 11:03

>>13
Sorry, should be:

for (size_t i = 0, end = cbuffer_size(cb); i < end; ++i) {
   void *value = cbuffer_at(cb, i);
   // do something with value
}

Name: Anonymous 2011-01-06 11:06

>>11
People who still use ints for addressing memory offsets should be shot on sight. Also, that code leaves it up to the user of the data structure to manage head and tail indices. Shitty code.

Name: Anonymous 2011-01-06 11:12

>>13 >>14
Oh, you definitely have a point! I'll take this advice!

Name: Anonymous 2011-01-06 11:14

>>15
Yeah, about that I agree on how good and more clear is having a clean API, but is there any guy so dumb to mess with someone else's data structs?

Name: Anonymous 2011-01-06 11:31

>>15
People who use unsigned ints such as size_t for anything except bitwise operations, but including addressing memory offsets, should be hunted down and murdered.

Think about it.

Name: Anonymous 2011-01-06 11:37

>>18
size_t is not necessarily unsigned int

http://www.open-std.org/JTC1/SC22/wg14/www/docs/n1124.pdf
page 266

You should be hunted down for not reading the standards documentation.

Name: Anonymous 2011-01-06 12:04

>>19
Actually it has to be unsigned, int meaning an integral type including long int etc.

Name: Anonymous 2011-01-06 12:09

>>20
Of course it's unsigned.

Name: OP 2011-01-08 7:19

So, /prog/, thanks for your advice. A new piece of library is now implemented. You've gained a respectable place in my THANKS file.

Name: Anonymous 2011-01-08 7:24

$ bbcat ~/text/THANKS.bbc
/prog/
$

Name: Anonymous 2011-01-08 7:34

BeeeeeeBeeeeeeCat

Name: Anonymous 2011-01-08 7:47

$ bbcat ~/text/THANKS.bbc
bash: bbcat: command not found
$

Name: Anonymous 2011-01-08 7:55

$ sudo bbcat ~/text/THANKS.bbc
sudo: This is not ubuntu, faggot.
$

Name: Anonymous 2011-01-08 8:01

>>25
$ sudo apt-get install bbcat

Name: Anonymous 2011-01-08 8:06


$ sudo apt-get install bbcat
bash: sudo: command not found
$

Name: Anonymous 2011-01-08 8:21

$ sudo why are you using code tags
sudo: Fuck off, Rand(all).
$

Name: Anonymous 2011-01-08 8:25

>>27
$ sudo apt-get install bbcat
sudo: This is not ubuntu, faggot.
$ cat ~/bin/sudo
#!/bin/sh
echo "sudo: This is not ubuntu, faggot"
$

Name: Anonymous 2011-01-08 9:17

>>30 Really?
$ cat `which sudo`
#!/bin/sh
tail -1 $0
exit 1
sudo: This is not ubuntu, faggot
$

Name: Anonymous 2011-01-08 9:26

>>31
GNUSudo detected!

Name: Anonymous 2011-01-08 9:27

>>31
#!/bin/sh
echo `basename $0`: `tail -1 $0` && exit 1
This is not ubuntu, faggot

Name: Anonymous 2011-01-08 11:12

>>25-33
fucking fags i hate you

Name: Anonymous 2011-01-08 12:17

>>33
sed "3s/faggot/\`\`faggot''/"

Name: Anonymous 2011-02-04 18:44

Don't change these.
Name: Email:
Entire Thread Thread List