简体   繁体   中英

circular buffer implementation

Yes again, I come again with that very straight forward implementation which is something like this:

    // write data always! if buffer is already full, overwrite old data!
    void Put( const CONTENT_TYPE &data )
    {
        buffer[ inOffset++] = data;
        inOffset%=size;

        // was data overwritten, skip it by increment read offset
        if ( inOffset == outOffset ) 
        {
            outOffset++;
            outOffset%=size;
            std::cout << "Overwrite" << std::endl;
        }
    }

    CONTENT_TYPE Pull()
    {
        CONTENT_TYPE data = buffer[ outOffset++ ];
        outOffset %= size;
        return data;
    }

But this simple algorithm utilizes only size-1 one elements of the buffer!

If I want to avoid that, I only found a solution with adding another counter variable, which wastes me sizeof(counter_var) - sizeof(element) bytes.

Q: Is there a solution which did not waste memory? It looks so terrible simple but I can't catch it :-)

Remark: There are some more lines of code to protect for empty reads and other stuff, but this is not important to the question. And it is not tagged c++ because the algorithm did not depend on the language, also if I give a c++ code example.

You can use two integers and fill all slots if one is an index and the other an element count, then convert to find the second index on the fly:

void put(const ELEMENT& element) {
  if (nElements == size) throw "put: buffer full";
  buffer[(start + nElements++) % size] = element;
}

ELEMENT get() {
  if (nElements == 0) throw "get: buffer empty";
  ELEMENT& value = buffer[start];
  start = (start + 1) % size;
  --nElements;
  return value;
}

Of course you can replace the mod operations with if (foo > size) foo -= size; if you like.

You'd just deal with that by using different points in time at which you do the modulo operation; assume we increase the read and write pointers after every access. If we now do the read pointer's modulo instantly after increasing, and the write pointer's modulo just right before reading, the |write-read| of a full buffer would be the length of the buffer, without any special case handling. For that to work, your write pointer should always be used % buffer_length , but stored % (2 * buffer_length) .

I don't especially like Mark's answer, because handling things as special cases is usually not a good idea, as little as introducing negative sentinel values is in a place where you'd typically used size_t (ie an unsigned integer).

You could use a special sentinel value for one of the offsets, such as -1, to indicate that the buffer is full or empty. This will complicate your code for checking and modifying the offset.

// write data always! if buffer is already full, overwrite old data!
void Put( const CONTENT_TYPE &data )
{
    buffer[ inOffset++] = data;
    inOffset%=size;

    // was data overwritten, skip it by setting read offset to sentinel
    if ( inOffset == outOffset || outOffset == -1 ) 
    {
        outOffset = -1;
        std::cout << "Overwrite" << std::endl;
    }
}

CONTENT_TYPE Pull()
{
    if (outOffset == -1)
        outOffset = inOffset;
    CONTENT_TYPE data = buffer[ outOffset++ ];
    outOffset %= size;
    return data;
}

bool IsEmpty()
{
    return outOffset == inOffset;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM