#C言語で構造体のメンバを隠蔽する(通常の方法)
C言語で複数のインスタンスを使いたい場合(マルチインスタンスモジュールを作成する場合)、構造体に必要な情報を格納し、関数にその構造体を渡すことで対象とするデータを自由に切り替えることができます。このとき、データを格納する構造体のメンバを他からいじられたり不要に公開したくない場合、カプセル化することがよくあります。大抵おそらく以下のようなコードで実装されるでしょう。(ここでは例として簡単なFIFOモジュールを例とします)
#ifndef FIFO_UINT8_H
#define FIFO_UINT8_H
#include <stdint.h>
typedef struct FifoUInt8_Instance FifoUInt8_Instance;
FifoUInt8_Instance * FifoUInt8_GetInstance(void);
void FifoUInt8_Init (FifoUInt8_Instance * instance);
void FifoUInt8_Discard (FifoUInt8_Instance * instance);
void FifoUInt8_Push (FifoUInt8_Instance * instance, uint8_t data);
uint8_t FifoUInt8_Pop (FifoUInt8_Instance * instance);
#endif // FIFO_UINT8_H
#include <stddef.h>
#include <stdlib.h>
#include "fifo_uint8.h"
#define FIFO_LENGTH 128
struct FifoUInt8_Instance
{
uint8_t fifoBuffer[FIFO_LENGTH];
size_t pushPosition;
size_t popPosition;
size_t numOfDataInBuffer;
};
FifoUInt8_Instance * FifoUInt8_GetInstance(void)
{
FifoUInt8_Instance * instance = (FifoUInt8_Instance *) malloc(sizeof(FifoUInt8_Instance));
FifoUInt8_Init(instance);
return instance;
}
void FifoUInt8_Discard(FifoUInt8_Instance * instance)
{
free(instance);
}
void FifoUInt8_Init(FifoUInt8_Instance * instance)
{
instance->pushPosition = 0;
instance->popPosition = 0;
instance->numOfDataInBuffer = 0;
}
void FifoUInt8_Push(FifoUInt8_Instance * instance, uint8_t data)
{
if(instance->numOfDataInBuffer > FIFO_LENGTH)
return;
instance->fifoBuffer[instance->pushPosition] = data;
instance->pushPosition++;
instance->numOfDataInBuffer++;
}
uint8_t FifoUInt8_Pop(FifoUInt8_Instance * instance)
{
if(instance->numOfDataInBuffer == 0)
return 0;
uint8_t popedData = instance->fifoBuffer[instance->popPosition];
instance->popPosition++;
instance->numOfDataInBuffer--;
return popedData;
}
#include<stdio.h>
#include"fifo_uint8.h"
void main(void)
{
FifoUInt8_Instance * fifo1 = FifoUInt8_GetInstance();
FifoUInt8_Instance * fifo2 = FifoUInt8_GetInstance();
FifoUInt8_Push(fifo1, 1);
FifoUInt8_Push(fifo1, 2);
FifoUInt8_Push(fifo2, 10);
FifoUInt8_Push(fifo2, 20);
for(int i = 0; i < 2; i++)
{
printf("fifo1 = %d, fifo2 = %d\n", FifoUInt8_Pop(fifo1), FifoUInt8_Pop(fifo2));
}
}
fifo = 1, fifo2 = 10
fifo = 2, fifo2 = 20
FifoUInt8_Instance
が不完全な型としてヘッダファイルで宣言されているため、他の翻訳単位からはこの構造体のメンバにアクセスすることはできません。このように、内部の詳細を隠蔽することでカプセル化します。
#mallocを使わずメンバが隠蔽された構造体を使いたい
組み込み用の8bitCPUなど非力(かつメモリが少ない)な環境のソフトを作成する場合、基本的にmalloc()
などで動的にヒープ領域のメモリを確保することは、避けるべきと言われています。そこで、上記の不完全な型を使用したカプセル化をmalloc()
無しで実現できないか、考えてみました。
#ifndef FIFO_UINT8_H
#define FIFO_UINT8_H
#include <stdint.h>
#include <stddef.h>
typedef struct FifoUInt8_Instance FifoUInt8_Instance;
extern const size_t FifoUInt8_SIZE_OF_INSTANCE;
#define FifoUInt8_AllocateAndSetInstance(POINTER) \
uint8_t POINTER ## _entity_in ## __func__ ## at ## __LINE__ ## _[FifoUInt8_SIZE_OF_INSTANCE]; \
POINTER = (FifoUInt8_Instance *)POINTER ## _entity_in ## __func__ ## at ## __LINE__ ## _; \
FifoUInt8_Init(POINTER);
void FifoUInt8_Init (FifoUInt8_Instance * instance);
void FifoUInt8_Push (FifoUInt8_Instance * instance, uint8_t data);
uint8_t FifoUInt8_Pop (FifoUInt8_Instance * instance);
#endif // FIFO_UINT8_H
#include "fifo_uint8.h"
#define FIFO_LENGTH 128
struct FifoUInt8_Instance
{
uint8_t fifoBuffer[FIFO_LENGTH];
size_t pushPosition;
size_t popPosition;
size_t numOfDataInBuffer;
};
const size_t FifoUInt8_SIZE_OF_INSTANCE = sizeof(FifoUInt8_Instance);
void FifoUInt8_Init(FifoUInt8_Instance * instance)
{
instance->pushPosition = 0;
instance->popPosition = 0;
instance->numOfDataInBuffer = 0;
}
void FifoUInt8_Push(FifoUInt8_Instance * instance, uint8_t data)
{
if(instance->numOfDataInBuffer > FIFO_LENGTH)
return;
instance->fifoBuffer[instance->pushPosition] = data;
instance->pushPosition++;
instance->numOfDataInBuffer++;
}
uint8_t FifoUInt8_Pop(FifoUInt8_Instance * instance)
{
if(instance->numOfDataInBuffer == 0)
return 0;
uint8_t popedData = instance->fifoBuffer[instance->popPosition];
instance->popPosition++;
instance->numOfDataInBuffer--;
return popedData;
}
#include "fifo_uint8.h"
void main(void)
{
FifoUInt8_Instance * fifo1;
FifoUInt8_Instance * fifo2;
FifoUInt8_AllocateAndSetInstance(fifo1);
FifoUInt8_AllocateAndSetInstance(fifo2);
FifoUInt8_Push(fifo1, 1);
FifoUInt8_Push(fifo1, 2);
FifoUInt8_Push(fifo2, 10);
FifoUInt8_Push(fifo2, 20);
for(int i = 0; i < 2; i++)
{
printf("fifo1 = %d, fifo2 = %d\n", FifoUInt8_Pop(fifo1), FifoUInt8_Pop(fifo2));
}
}
fifo = 1, fifo2 = 10
fifo = 2, fifo2 = 20
という感じになりました。
インスタンスをヒープ領域ではなく、スタック領域に確保するために、構造体と同じ大きさのuint8_t
の配列を宣言し、その先頭番地を構造体のポインタにキャストするという多少乱暴?な方法で実装してみました。
確保する領域として宣言する配列の名前は、DeclareInstance()
を呼ぶ関数名と行番号から生成しているため、forループのなかで複数インスタンスを生成するなどはできません(不便)。
もっといい方法があるかと考えたのですが、私にはこれが精一杯でした...
ひとまず動的メモリ確保を使わず、構造体のメンバを隠蔽するという目標は達成できました。
#参考にした情報
オライリー テスト駆動開発による組み込みプログラミング https://www.oreilly.co.jp/books/9784873116143/
シングルインスタンスモジュール・マルチインスタンスモジュールの考え方をここでしりました。
また、この記事のコードの動作確認を本で紹介されているUnityで行いました。