こんにちは、めのんです!
前回予告したように今回はヘッダファイルの書き方について解説することにします。
今回はヘッダファイルを単に作成するだけでなく、分割コンパイルについての補足説明もする予定です。
ライブラリはもちろん、本格的なアプリケーションを作成する際にも必ず必要になる知識ですので、しっかり理解するようにしてください。
そもそもヘッダファイルとは?
「ヘッダファイル」というのは規格の用語ではないので厳密な定義はありません。
私はヘッダのように使うソースファイルの意味で「ヘッダファイル」という表現を使っています。
このブログでもそのつもりで書いていきますね。
「ヘッダ」と「ヘッダファイル」は似て非なるものです。
「ヘッダ」は次の形式の#include指令で読み込むコードで、原則として処理系によって提供されます。
#include <ヘッダ>:
ヘッダはソースファイルとして実装されているかもしれませんし、それ以外の方法で実装されているかもしれません。
#include指令を書くことで、へっっで宣言定義される内容が有効になるだけでもかまわないのです。
一方で「ヘッダファイル」はソースファイルの一種で、次の形式で取り込まれます。
#include "ヘッダファイル"
上の二重引用符で囲む形式の#include指令では、ヘッダファイルが見つからなければ山形括弧で囲む形式の#include指令と同様にヘッダを探索します。
ヘッダもヘッダファイルも探索する場所は処理系定義ですが、GCCの場合はヘッダの探索場所は「-I」オプションで、ヘッダファイルの探索場所は「-iquote」オプションで指定します。
gcc -I /usr/local/include -iquote . sample.c
上の例では、ヘッダの探索先として /usr/local/include ディレクトリを、ヘッダファイルの探索先としてカレントディレクトリを追加しています。
ヘッダファイルが取り込まれれば、単純にヘッダファイルの内容が#include指令のあった箇所に埋め込まれます。
PHPのincludeやrequireのように、それを記述したスコープの影響を受けるようなことは一切ありません。
固定長メモリプールのソースコードを改善する
以前紹介したの固定長メモリプールのソースコードを題材に実際にヘッダファイルを作っていきます。
まずは2つに分解したソースコードを再掲載します。
// main.c
#include <stdio.h>
void fmp_setup(void);
void *fmp_alloc(void);
void fmp_free(void* p);
int main(void)
{
void *array[0x10];
fmp_setup();
// 16回メモリブロックを割り付ける。
for (int i = 0; i < 0x10; i++)
{
printf("i = %d\n", i);
void *p = fmp_alloc();
array[i] = p;
printf("p = %p\n", p);
}
// 16回メモリブロックを解放する。
for (int i = 0; i < 0x10; i++)
{
printf("i = %d\n", i);
void *p = array[i];
fmp_free(p);
printf("p = %p\n", p);
}
return 0;
}
// fmp.c
#include <stddef.h>
// 処理系の最大境界調整要求を持つ型
union max_align_type
{
long long a;
double b;
long double c;
void* d;
};
// 処理系の最大調整要求
const size_t max_align_size = offsetof(struct { char a; union max_align_type b; }, b);
union node
{
union max_align_type aligner;
char array[256]; // 固定長配列(ここでは256バイト)
union node* next;
};
// 固定長メモリプールの管理領域
union node memory[256];
union node *top;
// 固定長メモリプールの初期設定
void fmp_setup(void)
{
const size_t n = sizeof(memory)/sizeof(memory[0]);
for (size_t i = 0; i < n - 1; i++)
memory[i].next = &memory[i + 1];
memory[n - 1].next = NULL; top = &memory[0];
}
// 固定長メモリプールからメモリブロックを割り付ける。
void *fmp_alloc(void)
{
if (top == NULL)
return NULL;
void *r = top->array;
top = top->next;
return r;
}
// 固定長メモリプールから割り付けたメモリブロックを解放する。
void fmp_free(void* p)
{
if (p == NULL)
return;
((union node*)p)->next = top;
top = p;
}
このソースコードを題材に、実際にどんな風にヘッダファイルを作っていくのか見ていくことにします。
ソースコードの改善
ヘッダファイルを作る前に既存のソースコードを改善しておくことにします。
翻訳単位の外に見せたくない識別子は内部結合にして、メモリブロックのサイズとメモリブロックの個数はこれまで直値だったのでマクロにすることにしましょう。
// fmp.c
#include <stddef.h>
// メモリブロックサイズ
#define FMP_BLOCK_SIZE 256
// メモリブロック数
#define FMP_BLOCK_COUNT 256
// 処理系の最大境界調整要求を持つ型
union max_align_type
{
long long a;
double b;
long double c;
void* d;
};
// 処理系の最大調整要求
const size_t max_align_size = offsetof(struct { char a; union max_align_type b; }, b);
union node
{
union max_align_type aligner;
char array[FMP_BLOCK_SIZE ]; // 固定長配列
union node* next;
};
// 固定長メモリプールの管理領域
static union node memory[FMP_BLOCK_COUNT];
static union node *top;
// 固定長メモリプールの初期設定
void fmp_setup(void)
{
const size_t n = sizeof(memory)/sizeof(memory[0]);
for (size_t i = 0; i < n - 1; i++)
memory[i].next = &memory[i + 1];
memory[n - 1].next = NULL;
top = &memory[0];
}
// 固定長メモリプールからメモリブロックを割り付ける。
void *fmp_alloc(void)
{
if (top == NULL)
return NULL;
void *r = top->array;
top = top->next;
return r;
}
// 固定長メモリプールから割り付けたメモリブロックを解放する。
void fmp_free(void* p)
{
if (p == NULL)
return;
((union node*)p)->next = top;
top = p;
}
ヘッダフィル
おまたせしました!
これからいよいよヘッダファイルを作っていきます。
まずはヘッダファイルに何を入れるかを考えていきます。
今回は、これまでmain.cで直接書いていたfmp_系関数の宣言をヘッダファイルに移すことにしましょう。
// fmp.h
void fmp_setup(void);
void *fmp_alloc(void);
void fmp_free(void* p);
Cのヘッダファイルはヘッダと同じように拡張子(サフィックス)を「.h」にするのが一般的です。
このヘッダファイルを#include指令で取り込めば、簡単に固定長メモリプールを再利用することができます。
これだけでもいいのですが、メモリブロックのサイズや個数もヘッダファイルに入れておくと何かと便利でしょうね。
こんな感じです。
// fmp.h
// メモリブロックサイズ
#define FMP_BLOCK_SIZE 256
// メモリブロック数
#define FMP_BLOCK_COUNT 256
void fmp_setup(void);
void *fmp_alloc(void);
void fmp_free(void* p);
ヘッダファイルの利用
今度はさきほど作ったヘッダファイルを実際に使ってみましょう。
固定長メモリプールを利用する側のソースファイルだけでなく、固定長メモリプールを実装する側のソースファイルでもこのヘッダファイルを#include指令で取り込むと便利です。
// fmp.c
#include <stddef.h>
#include "fmp.h"
// 処理系の最大境界調整要求を持つ型
union max_align_type
{
long long a;
double b;
long double c;
void* d;
};
// 処理系の最大調整要求
const size_t max_align_size = offsetof(struct { char a; union max_align_type b; }, b);
union node
{
union max_align_type aligner;
char array[FMP_BLOCK_SIZE ]; // 固定長配列
union node* next;
};
// 固定長メモリプールの管理領域
static union node memory[FMP_BLOCK_COUNT];
static union node *top;
// 固定長メモリプールの初期設定
void fmp_setup(void)
{
const size_t n = sizeof(memory)/sizeof(memory[0]);
for (size_t i = 0; i < n - 1; i++)
memory[i].next = &memory[i + 1];
memory[n - 1].next = NULL;
top = &memory[0];
}
// 固定長メモリプールからメモリブロックを割り付ける。
void *fmp_alloc(void)
{
if (top == NULL)
return NULL;
void *r = top->array;
top = top->next;
return r;
}
// 固定長メモリプールから割り付けたメモリブロックを解放する。
void fmp_free(void* p)
{
if (p == NULL)
return;
((union node*)p)->next = top;
top = p;
}
次は利用する側のソースファイル、ここではmain.cです。
// main.c
#include <stdio.h>
#include "fmp.h"
int main(void)
{
void *array[0x10];
fmp_setup();
// 16回メモリブロックを割り付ける。
for (int i = 0; i < 0x10; i++)
{
printf("i = %d\n", i);
void *p = fmp_alloc();
array[i] = p;
printf("p = %p\n", p);
}
// 16回メモリブロックを解放する。
for (int i = 0; i < 0x10; i++)
{
printf("i = %d\n", i);
void *p = array[i];
fmp_free(p);
printf("p = %p\n", p);
}
return 0;
}
ヘッダファイルを使った場合のコンパイル方法
最後に、自作のヘッダファイルを使った場合のコンパイル方法をご紹介します。
まとめてコンパイルする場合は次のようになります。
gcc -iquote . main.c fmp.c
分割コンパイルするなら次のようにします。
gcc -c -iquote . main.c
gcc -c -iquote . fmp.c
gcc main.o fmp.o
上の例ではヘッダファイル「fmp.h」がmain.cやfmp.cといっしょにカレントディレクトリに配置していることを前提としています。
GCCの場合はデフォルトでヘッダファイルを取り込むソースファイルと同じディレクトリが探索対象になりますので、「-iquote .」オプションは省略してかまいません(普通は省略します)。
これでヘッダファイルを作成して、使うことができるようになりました!
今回の解説は以上となります。
ちょっと長くなってしまったのでいったん切りましたが、明日はヘッダファイルの書き方についてもう少し補足することにします。
どうぞご期待ください!