リングバッファー | C言語

目次

リングバッファーとは

リングバッファーは、固定サイズのメモリー領域を循環的に利用するデータ構造です。配列は直線的であるため実際にリング状になっているわけではありません。これを書き込みポインターと読み出しポインターという2つのポインターを使用し、配列の末尾に達すると先頭に戻ることで循環的な動作を実現します。

主に通信データのバッファーとして用いられます。

リングバッファーの仕組み

リングバッファーはFIFO(First In First Out、先入れ先出し)の特性を持ちます。FIFOではデータを入れることをエンキュー、データを取り出すことをデキューと呼びます。

リングバッファーへの書き込み(エンキュー)が発生すると、書き込みポインターの位置にデータを格納し、書き込みポインターを1つ進めます(下図2、3、4、6)。そして読み出し(デキュー)が発生すると、読み出しポインターの位置にあるデータを取り出し、読み出しポインターを1つ進めます(下図5、7、8)。これを繰り返すのがリングバッファーです。書き込みポインターと読み出しポインターが同じ値のとき、データが空なのかフルデータなのかを識別する手段にさえ気を付けておけば、永遠にデータを循環処理できます。

データの保持方法として、FIFOの他にスタックで用いられるLIFO(Last In First Out、後入れ先出し)があります。

リングバッファーの注意点

  • バッファーサイズ以上のデータは保持できません。
  • 読み出しより書き込みの方が速い場合、データを消失します。

サンプルコード (C言語)

ringbuffer.h

#ifndef RING_BUFFER_H
#define RING_BUFFER_H

#ifdef __cplusplus
extern "C" {
#endif

//------------------------------------------------------------------------------
// include
//------------------------------------------------------------------------------
#include <stdbool.h>
#include <stdint.h>

//------------------------------------------------------------------------------
// define
//------------------------------------------------------------------------------
#define RING_BUFFER_SIZE (128)  //!< リングバッファーのサイズ

//! リングバッファー
typedef struct
{
  char buffer[RING_BUFFER_SIZE];  //!< バッファー

  uint16_t head;   //!< 記録データの先頭位置
  uint16_t tail;   //!< 記録データの終了位置
  uint16_t count;  //!< 記録データ数
} RING_BUFFER;

//------------------------------------------------------------------------------
// function
//------------------------------------------------------------------------------
void initRingBuffer(RING_BUFFER *rb);
bool isFull_RingBuffer(const RING_BUFFER *rb);
bool isEmpty_RingBuffer(const RING_BUFFER *rb);

uint8_t enqueueRingBuffer(RING_BUFFER *rb, char data);
uint8_t dequeueRingBuffer(RING_BUFFER *rb, char *data);

#ifdef __cplusplus
}
#endif

#endif

ringbuffer.c

/**
 * @file ringbuffer.c
 * @brief リングバッファー処理
 */

//------------------------------------------------------------------------------
// include
//------------------------------------------------------------------------------
#include "ringbuffer.h"

/** ----------------------------------------------------------------------------
 * @brief リングバッファーの初期化
 *
 * @param [in,out] *rb : リングバッファー
 */
void initRingBuffer(RING_BUFFER *rb)
{
  rb->head  = 0;
  rb->tail  = 0;
  rb->count = 0;
}

/** ----------------------------------------------------------------------------
 * @brief バッファーが満杯かの確認
 *
 * @param [in] *rb : リングバッファー
 * @retval true : 満杯
 * @retval false : 空きあり
 */
bool isFull_RingBuffer(const RING_BUFFER *rb)
{
  if (rb->count == RING_BUFFER_SIZE) {
    return true;
  } else {
    return false;
  }
}

/** ----------------------------------------------------------------------------
 * @brief バッファーが空かの確認
 *
 * @param [in] *rb : リングバッファー
 * @retval true : 空
 * @retval false : データあり
 */
bool isEmpty_RingBuffer(const RING_BUFFER *rb)
{
  if (rb->count == 0) {
    return true;
  } else {
    return false;
  }
}

/** ----------------------------------------------------------------------------
 * @brief エンキュー (リングバッファーへの追加)
 *
 * @param [in,out] *rb : リングバッファー
 * @param data : 追加データ
 * @retval 0 : OK
 * @retval 1 : NG (フル)
 */
uint8_t enqueueRingBuffer(RING_BUFFER *rb, char data)
{
  if (isFull_RingBuffer(rb) == true) {
    return 1;
  }

  rb->buffer[rb->tail] = data;
  rb->tail             = (rb->tail + 1) % RING_BUFFER_SIZE;
  rb->count++;

  return 0;
}

/** ----------------------------------------------------------------------------
 * @brief デキュー (リングバッファーからの取得)
 *
 * @param [in,out] *rb : リングバッファー
 * @param [out] *data : 取得データ
 * @retval 0 : OK
 * @retval 1 : NG (空)
 */
uint8_t dequeueRingBuffer(RING_BUFFER *rb, char *data)
{
  if (isEmpty_RingBuffer(rb) == true) {
    return 1;
  }

  *data    = rb->buffer[rb->head];
  rb->head = (rb->head + 1) % RING_BUFFER_SIZE;
  rb->count--;

  return 0;
}

サンプルコードの使用例

使用例代わりに、CppUTestによるテストコードを掲載しておきます。

#include "CppUTest/TestHarness.h"

#include "ringbuffer.h"

// clang-format off
TEST_GROUP(ringbuffer){
  TEST_SETUP(){
  }

  TEST_TEARDOWN(){
  }
};
// clang-format on

TEST(ringbuffer, Test_RingBuffer)
{
  char        data;
  char        buffer[50] = {0};
  uint8_t     ret;
  uint16_t    i;
  RING_BUFFER rb;

  initRingBuffer(&rb);

  // 書き込んで読み出すテスト
  data = 'A';
  for (i = 0; i < 16; i++) {
    ret = enqueueRingBuffer(&rb, data);
    UNSIGNED_LONGS_EQUAL(ret, 0);
    data++;
  }

  for (i = 0; i < 16; i++) {
    ret = dequeueRingBuffer(&rb, &data);
    UNSIGNED_LONGS_EQUAL(ret, 0);
    buffer[i] = data;
  }

  STRNCMP_EQUAL(buffer, "ABC", 3);

  // リングバッファーを1周させる
  data = 'A';
  for (i = 0; i < RING_BUFFER_SIZE; i++) {
    ret = enqueueRingBuffer(&rb, data);
    UNSIGNED_LONGS_EQUAL(ret, 0);
  }

  for (i = 0; i < RING_BUFFER_SIZE; i++) {
    ret = dequeueRingBuffer(&rb, &data);
    UNSIGNED_LONGS_EQUAL(ret, 0);
  }

  // 1周後の書き込み/読み出しテスト
  data = 'a';
  for (i = 0; i < 16; i++) {
    ret = enqueueRingBuffer(&rb, data);
    UNSIGNED_LONGS_EQUAL(ret, 0);
    data++;
  }

  for (i = 0; i < 16; i++) {
    ret = dequeueRingBuffer(&rb, &data);
    UNSIGNED_LONGS_EQUAL(ret, 0);
    buffer[i] = data;
  }

  STRNCMP_EQUAL(buffer, "abc", 3);
}

TEST(ringbuffer, Test_FullRingBuffer)
{
  char        data;
  uint8_t     ret;
  uint16_t    i;
  RING_BUFFER rb;

  initRingBuffer(&rb);

  data = 'A';
  for (i = 0; i < RING_BUFFER_SIZE; i++) {
    ret = enqueueRingBuffer(&rb, data);
    UNSIGNED_LONGS_EQUAL(ret, 0);
  }

  ret = enqueueRingBuffer(&rb, data);
  UNSIGNED_LONGS_EQUAL(ret, 1);
}

TEST(ringbuffer, Test_EmptyRingBuffer)
{
  char        data;
  uint8_t     ret;
  uint16_t    i;
  RING_BUFFER rb;

  initRingBuffer(&rb);

  data = 'A';
  for (i = 0; i < 16; i++) {
    ret = enqueueRingBuffer(&rb, data);
    UNSIGNED_LONGS_EQUAL(ret, 0);
  }

  for (i = 0; i < 16; i++) {
    ret = dequeueRingBuffer(&rb, &data);
    UNSIGNED_LONGS_EQUAL(ret, 0);
  }

  ret = dequeueRingBuffer(&rb, &data);
  UNSIGNED_LONGS_EQUAL(ret, 1);
}

コメント

コメントする

CAPTCHA


目次