C言語で自作ヘッダファイルを使おう

### 注意
~~本稿はわかりやすさを重視したため，ヘッダファイルでの関数の定義を行なっています．~~
(2018/5/6 13:16 修正しました)
ヘッダファイルでの関数の定義はコンパイル時間やファイルの実行速度などの観点から推奨されていません．
詳しいヘッダファイルの書き方は次の記事が参考になりますのでご参照ください．
https://qiita.com/MoriokaReimen/items/7c83ebd0fbae44d8532d

記載通りのコンパイル方法でコンパイルが通ることは執筆時に確認していますが，
環境に依存した書き方かもしれません．

```sh:環境
$ sw_vers
ProductName:	Mac OS X
ProductVersion:	10.12.6
BuildVersion:	16G29
$ gcc --version
gcc-7 (Homebrew GCC 7.3.0) 7.3.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
````

本稿で掲載しているコードはあくまで説明用なので効率的ではない・冗長である場合が多いです．
例えば，配列の初期化なら本稿のようにポインタを渡して初期化するのではなく，
単に初期化したものをポインタで返す関数の方が良さそうです．

# 本題

例えば以下のようなソースコード`arrayplus.c`があったとします．

```c:arrayplus.c
#include <stdio.h>

int main(){

  int a[5] = { 1,  2,  3,  4,  5}; // このコードが通らない場合は
  int b[5] = { 1,  0, -1, -2, -3}; // "for文を用いた初期化"へ
  int c[5];  

  for(int i = 0; i < 5; i++){
     c[i] = a[i] + b[i];
  }

  printf("c = [");
  for(int i = 0; i < 5; i++){
     printf("%d,", c[i]);
  }
  printf("\b]\n");

  return 0;
}
```
```:コンパイル，実行
$ gcc -O2 -o arrayplus.out arrayplus.c 
$ ./arrayplus.out
c = [2,2,2,2,2]
```
上記を例に変数，関数，ヘッダファイルの利用について解説します．

## 変数を使う
まず，配列の要素数が全て同じなので変数`size`で定義できるようにしましょう．

```c:arrayplus.c
#include <stdio.h>

int main(){

  int size; size = 5; //これは私の習慣であってint size = 5;でもOKです

  int a[size] = { 1,  2,  3,  4,  5};
  int b[size] = { 1,  0, -1, -2, -3};
  int c[size];  

  for(int i = 0; i < size; i++){
     c[i] = a[i] + b[i];
  }

  printf("c = [");
  for(int i = 0; i < size; i++){
     printf("%d\n", c[i]);
  }
  printf("\b]\n");

  return 0;
}
```
これで一箇所変えるだけで全ての配列の宣言における要素数及び`for`文を書き換える手間を省けました．
初期化の際，このように配列の要素数などはできるだけ数値をそのまま`5`などと打つのではなく変数を使う方が良いです．

また，`int a[size*4%3]`のように`[]`内で演算を行うのも避けた方が身のためです[^1]．
これは要素数を負で宣言してしまうなどのバグの温床になりやすいからです．

## 関数を使う
#### 配列を初期化する：等差数列
まず，配列`a`, `b`ですが，等差数列で初期化しています．

```c
int a[size] = { 1,  2,  3,  4,  5}; //初項1, 交差1の等差数列
int b[size] = { 1,  0, -1, -2, -3}; //初項1, 交差-1の等差数列
```
これは，そのまま`main`関数内で`for`文で回すことがいちばん簡単です．

```c:for文を用いた初期化
int a[size];
int b[size];

for(int i = 0; i < size; i++){
  a[i] = 1 + i * 1;
  b[i] = 1 + i * (-1);
}
```
ただしこれは配列`a`,`b`が両方同じ要素数を持つためであって，
例えばそれぞれが異なる要素数`size`, `size2`を持つなら

```c:for文を用いた初期化2
int a[size];
int b[size2];

for(int i = 0; i < size; i++){
  a[i] = 1 + i * 1;
}

for(int i = 0; i < size2; i++){
  b[i] = 1 + i * (-1);
}
```
のように`for`文を2つ書かなければいけません．
そこで，配列を等差数列に初期化してくれる関数を考えます．

```c:range
void range(int *a, int size, int start, int step){
  // 等差数列で初期化
  for(int i = 0; i < size; i++){
    a[i] = start + i*step;
  }
}
```

これにより，配列の初期化は

```c
int a[size]; range(a, size, 1, 1);
```
となり，たとえ配列の要素数が異なっていても

```c
int a[size];  range(a, size,  1, 1);
int b[size2]; range(b, size2, 1, -1);
```
のようにシンプルに記述することが可能になりました．
#### 配列を初期化する：全部を同じ要素に
次に配列`c`ですが，初期化していない配列を扱うのはなにかと危険なので，大抵の場合は`0`などで初期化しておくと良いとされています．
そこでもう少し一般化して「与えられた`int`型の数字で初期化する」関数を定義しましょう．

```c:init
void init(int *a, int size, int num){
  // 全ての要素を特定の数字で初期化
  for(int i = 0; i < size; i++){
    a[i] = num;
  }
}
```

もちろん，全零配列に特化して作るのも有効です．

```c:zeros
void zeros(int *a, int size){
  // 全ての要素を0で初期化
  for(int i = 0; i < size; i++){
    a[i] = 0;
  }
}
```

これにより，汎用性は失われましたが関数の引数を減らすことができます．
また，全部0で初期化するには

```c
int a[size] = {0};
```
とすればよいそうです．

##### `range`，`zeros`，`init`について
お気づきの方もいらっしゃるかと思いますが，`zeros`と`init`は`range`で実現できます．

```c:zeros

//zerosと同じ
int a[size];  range(a, size,  0, 0);

//initと同じ
num = 1
int b[size];  range(a, size,  num, 0);
```
本稿では`numpy`などを参考にして関数の例として示させていただきました．
実装の際にはまとめてしまうのも良い手だと思います．

#### 配列を出力する
これで本当に`range`，`init`/`zeros`を用いて`a`,`b`,`c`は所望の要素を持つ配列に初期化されたのでしょうか？
それを確かめる方法としてよく用いられるのは配列の中身を全て出力するものです．

```c:print
#include <stdio.h>

void print(int *a, int size){
  // 配列の中身を出力
  printf("[");
  for(int i = 0; i < size; i++){
    printf("%d,", a[i]);
  }
  printf("\b]\n");
}
```
この関数はデバッグ目的でよく使います．

#### 動作確認
さて，これまでを踏まえて初期化及び配列の要素と動作の確認をしてみましょう．

```c:arrayplus.c
#include <stdio.h>

void range(int *a, int size, int start, int step){
  // 等差数列で初期化
  for(int i = 0; i < size; i++){
    a[i] = start + i * step;
  }
}

void init(int *a, int size, int num){
  // 全ての要素を特定の数字で初期化
  for(int i = 0; i < size; i++){
    a[i] = num;
  }
}

void zeros(int *a, int size){
  // 全ての要素を0で初期化
  for(int i = 0; i < size; i++){
    a[i] = 0;
  }
}

void print(int *a, int size){
  // 配列の中身を出力
  printf("[");
  for(int i = 0; i < size; i++){
    printf("%d,", a[i]);
  }
  printf("\b]\n");
}

int main(){

  int size; size = 5;

  int a[size]; range(a, size,  1,  1);
  int b[size]; range(b, size,  1, -1);
  int c[size]; init(c, size, 0); //zeros(c, size);でも可

  printf("a = ");print(a, size);
  printf("b = ");print(b, size);
  printf("c = ");print(c, size);

  for(int i = 0; i < size; i++){
     c[i] = a[i] + b[i];
  }

  printf("\n");
  printf("c = ");print(c, size);

  return 0;
}
```
```:コンパイル，実行
$ gcc -O2 -o arrayplus.out arrayplus.c;./arrayplus.out
a = [1,2,3,4,5]
b = [1,0,-1,-2,-3]
c = [0,0,0,0,0]

c = [2,2,2,2,2]
```

どうやらきちんと`range`,`init`,`print`は動作してくれたようです．
そして，関数を定義したことにより`main`関数のなかがスッキリしました．

一方で，`main`関数の記述がソースコードの中で下の方になってしまいました．
今回はまだマシですが，更に関数を増やすと`main`に辿り着くのが大変になりそうです．
解決手段の一つはプロトタイプ宣言です．

#### プロトタイプ宣言
関数を宣言だけしておいて中身を後で書くものです．

```c:arrayplus.c
#include <stdio.h>

// 等差数列で初期化
void range(int *a, int size, int start, int step);
// 全ての要素を特定の数字で初期化
void init(int *a, int size, int num);
// 全ての要素を0で初期化
void zeros(int *a, int size);
// 配列の中身を出力
void print(int *a, int size);

int main(){
  //省略
}

void range(int *a, int size, int start, int step){
  // 等差数列で初期化
  for(int i = 0; i < size; i++){
    a[i] = start + i*step;
  }
}

void init(int *a, int size, int num){
  // 全ての要素を特定の数字で初期化
  for(int i = 0; i < size; i++){
    a[i] = num;
  }
}

void zeros(int *a, int size){
  // 全ての要素を0で初期化
  for(int i = 0; i < size; i++){
    a[i] = 0;
  }
}

void print(int *a, int size){
  // 配列の中身を出力
  printf("[");
  for(int i = 0; i < size; i++){
    printf("%d,", a[i]);
  }
  printf("\b]\n");
}
```

ただ幾分かマシになっただけで根本的な解決には至ってません．

## ヘッダファイルを使う
そこで本稿が推奨するのがもう一つの方法であるヘッダファイルへの関数の切り出しです．
### 関数の切り出し
新たに`array.h`,`array.c`を作成します．
全てのプロトタイプ宣言を`array.h`に，全ての関数の記述を`array.c`に移行します．

```c:array.h
#ifndef ARRAY_H //二重でincludeされることを防ぐ
#define ARRAY_H

// 等差数列で初期化
void range(int *a, int size, int start, int step);
// 全ての要素を特定の数字で初期化
void init(int *a, int size, int num);
// 全ての要素を0で初期化
void zeros(int *a, int size);
// 配列の中身を出力
void print(int *a, int size);

#endif
```
```array.c
#include <stdio.h>
#include "array.h" // ここで必ず"array.h"をincludeする

void range(int *a, int size, int start, int step){
  // 等差数列で初期化
  for(int i = 0; i < size; i++){
    a[i] = start + i*step;
  }
}

void init(int *a, int size, int num){
  // 全ての要素を特定の数字で初期化
  for(int i = 0; i < size; i++){
    a[i] = num;
  }
}

void zeros(int *a, int size){
  // 全ての要素を0で初期化
  for(int i = 0; i < size; i++){
    a[i] = 0;
  }
}

void print(int *a, int size){
  // 配列の中身を出力
  printf("[");
  for(int i = 0; i < size; i++){
    printf("%d,", a[i]);
  }
  printf("\b]\n");
}

```
移行した関数を使うには`array.h`を`include`します．

```c:arrayplus.c
#include <stdio.h>
#include "array.h" // <>ではなく ""で囲う

int main(){
  // 省略
}
```
```:ファイル構成
.
├── array.h
├── array.c
└── arrayplus.c
```
早速コンパイルして実行してみましょう．
コンパイルするには新たに`array.c`を指定する必要があります．

```:コンパイル
$ gcc -O2 -o arrayplus.out arrayplus.c array.c
```
切り出す前と同じ結果が得られるはずです．

```:実行
$ ./arrayplus.out
 a = [1,2,3,4,5]
 b = [1,0,-1,-2,-3]
 c = [0,0,0,0,0]

 c = [2,2,2,2,2]
```

### 利点
改めて，関数及びヘッダファイルへの切り出しの利点について解説します．

#### 1. `main`関数の処理がみやすくなる

例えば等差数列で初期化された100個の要素をもつ配列が欲しい場合，関数がない場合は

```c
int a[100] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100};
```

あるいは

```c
int a[100];
for(int i = 0; i < 100; i++){
  a[i] = i + 1;
}
```

と書かなければいけなかったものが，

```c:
int a[100]; range(a, 100, 1, 1);
```

この1行で終わります，
このように配列の初期化を簡潔に指定して行うことができます．
初期化の仕方に合わせて関数を予め作っておけば，
あらゆる配列の初期化が簡単に行えるわけです．

#### 2. 他のソースコードでも`include`すれば定義されている関数が使える

例えば新たに`arraysub.c`というファイルを書いたとします．

```c:arraysub.c
#include <stdio.h>
#include "array.h"

int main(){

  int size; size = 5;

  int a[size]; range(a, size,  1,  1);
  int b[size]; range(b, size, -1, -1);
  int c[size]; init(c, size, 0); 

  for(int i = 0; i < size; i++){
    c[i] = a[i] - b[i];
  }

  printf("c = ");print(c, size);

  return 0;
}
```
```コンパイル，実行
$ gcc -O2 -o arraysub.out arraysub.c array.c; ./arraysub.out
c = [0,2,4,6,8]
```
このように`array.h`を`include`するだけで他のプログラムでも再利用ができます．
つまり，車輪の再発明をしなくても済むわけです．
#### 3. `main`関数をいじることなくパラメータを変える

前準備において配列の要素数を`size`として`main`関数内で定義してました．
よく使われるのは`main`関数の外側で`#define`で定義する方法です．

新たに`array_test.c`を用意します．
`main`関数内で初期化される配列`a`の大きさ，初項，等差を全て`#define`で定義します．

```c:array_test.c
#include <stdio.h>
#include "array.h"

#define ARRAY_SIZE 5
#define ARRAY_START 1
#define ARRAY_STEP 1

int main(){

  //私の場合は習慣としてローカル変数へ代入してから使います．
  int size;  size  = ARRAY_SIZE; 
  int start; start = ARRAY_START;
  int step;  step  = ARRAY_STEP;

  int a[size]; range(a, size,  start,  step); //引数が全て変数に

  printf("a = ");print(a, size);

  return 0;
}
```
さらに，`define.h`を作成し全ての`#define`構文を切り出します．

```c:define.h
#ifndef DEFINE_H
#define DEFINE_H

#define ARRAY_SIZE 5
#define ARRAY_START 1
#define ARRAY_STEP 1

#endif
```
```array_test.c
#include <stdio.h>
#include "define.h"    // `define.h`をinclude
#include "array.h"

int main(){
  //省略
}
```
```:ファイル構成
.
├── array_test.c
├── array.c
├── array.h
└── define.h
```

コンパイルして実行してみましょう．

```:コンパイル，実行
$ gcc -O2 -o array_test.out array_test.c array.c; ./array_test.out
a = [1,2,3,4,5]
```
これにより，`define.h`の数値を書き換えてるだけで配列`a`の初期化方法を定義できるようになりました．

ソースコードに手を加えるというのはそれだけでバグを生む要因となります．
なので完成してうまく動作するものにはできるだけ手を触れない方が良いです．
そこで，関数として切り出して隔離する手段が有効なわけです．

### 欠点とその回避法
#### 1. 命名
自作ヘッダファイルを用いる場合，自作した関数の振る舞いを
`main`関数を読んだだけでは(未来の自分を含む)他人には把握しづらいかもしれません．
例えば，初めて見た人に下記のコードが配列`a`を等差数列で初期化しているとはわからないはずです
 (`Python`に馴染みのある方なら察しがつくかもしれないですが)．

```c
int a[10]; range(a, 10, 1, 1);
```
そのため，`main`関数で初出の関数についてはコメントなどで補足することが手助けとなります．

```c
// range：配配列を等差数列で初期化する関数
// 引数：列のポインタ，要素数，初項，差
// 定義しているヘッダファイル：array.h
int a[10]; range(a, 10, 1, 1);
```
たいていの場合は1文目だけで十分です.
他人が見る場合があるのであれば関数を定義しているヘッダも記載しておくと親切でしょう．
また，ヘッダファイル内の記述もコメントを多く残しておくことが大切です．
あるいはもっと明示的な命名，例えば上記の例でしたら`initArithmeticSequence()`とするのも良いです．

#### 2. ヘッダファイルの読み込み順
これは`header.h`をうまく使うことである程度緩和できます．
また，`header.h`にプロトタイプ宣言を記載しておくと個々のヘッダファイルにアクセスせずとも
関数の一覧が把握できるため有用です．

```c:header.h
#ifndef HEADER_H
#define HEADER_H

#include "define.h"

// 等差数列で初期化
void range(int *a, int size, int start, int step);
// 全ての要素を特定の数字で初期化
void init(int *a, int size, int num);
// 全ての要素を0で初期化
void zeros(int *a, int size);
// 配列の中身を出力
void print(int *a, int size);

#endif
```

### 番外編：`Makefile`で一括コンパイル


この先どんどんヘッダが増えていった場合，コンパイルで指定するのも億劫になります．

```sh
$ gcc -O2 -o main.out main.c array.c dist.c func.c ... (続)
```
そこで`Makefile`を使って管理しましょう．
例として，`headerfiles`ディレクトリ内に`array.c`, `dist.c`, `func.c`があり，
親フォルダにそれらをまとめる`header.h`があるとします．

```:ファイル構成
.
├── Makefile
├── header.h
├── headerfiles
│   ├── array.c
│   ├── dist.c
│   └── func.c
└── main.c
```

`array.c`, `dist.c`, `func.c`の中身は以下の通りです．

```c:array.c
//このコード内で用いるヘッダのみinclude
#include <stdio.h>
#include <math.h>

//必ずinclude
#include "../header.h"

void range(int *a, int size, int start, int step){
   // 省略
}

void init(int *a, int size, int num){
   // 省略
}

void zeros(int *a, int size){
   // 省略
}

void print(int *a, int size){
   // 省略
}
```

```c:dist.c
//このコード内で用いるヘッダのみinclude
#include <stdlib.h>
#include <math.h>

//必ずinclude
#include "../header.h"

double binom(){
   // 省略
}

double normal(){
   // 省略
}
```

```c:func.c
//このコード内で用いるヘッダのみinclude
#include <stdio.h>

//必ずinclude
#include "../header.h"

void printout(){
   // 省略
}
```



`header.h`の中身は`array.c`, `dist.c`, `func.c`の関数を列挙します．

```c:header.h
#ifndef HEADER_H
#define HEADER_H

// define.hの内容をここに書く
#define ARRAY_SIZE 5
#define ARRAY_START 1
#define ARRAY_STEP 1

// from array.c //出自を書く
// 等差数列で初期化
void array_range(int *a, int size, int start, int step);
// 全ての要素を特定の数字で初期化
void array_init(int *a, int size, int num);
// 全ての要素を0で初期化
void array_zeros(int *a, int size);
// 配列の中身を出力
void array_print(int *a, int size);

// from dist.c //出自を書く
// 二項分布
double dist_binom();
// 正規分布
double dist_normal();

// from func.c //出自を書く
// 出力
double func_printout();

#endif
```

あとは`main.c`で`header.h`を`include`します．

```c:main.c
#include <stdio.h>
#include <stdlib.h>

#include "header.h"

int main(){
   // 省略
}
```

準備が出来たら`Makefile`を作成します[^2]．


```make:Makefile
PROGRAM = main
OBJS = headerfiles/array.o headerfiles/dist.o headerfiles/func.o
CC = gcc
CFLAGS = -O2

$(PROGRAM): $(PROGRAM).o $(OBJS)
   $(CC) -o $(PROGRAM) $(PROGRAM).o $(OBJS) -lm

$(OBJS) $(PROGRAM).o: header.h

clean:
   rm -f *.o headerfiles/*.o
```

`PROGRAM`, `OBJS`, `CC`, `CFLAGS`は`C`で言うところの`#DEFINE`に近いです．
これで準備は整ったので，`make`を実行しましょう．

```sh
$ make
gcc -O2    -c -o main.o main.c
gcc -O2    -c -o headerfiles/array.o headerfiles/array.c
gcc -O2    -c -o headerfiles/dist.o headerfiles/dist.c
gcc -O2    -c -o headerfiles/func.o headerfiles/func.c
gcc -o main main.o headerfiles/array.o headerfiles/dist.o headerfiles/func.o -lm
```

これで実行ファイル`main`が生成されます．
`.o`ファイルが邪魔になったら`make clean`で消せます．

##### 補足
関数名に出自をつけるとわかりやすいかもしれないです．
その場合の`header.h`は以下のようになります．

```c:header.h
#ifndef HEADER_H
#define HEADER_H

// define.hの内容をここに書く
#define ARRAY_SIZE 5
#define ARRAY_START 1
#define ARRAY_STEP 1

// from array.c //出自を書く
// 等差数列で初期化
void array_range(int *a, int size, int start, int step);
// 全ての要素を特定の数字で初期化
void array_init(int *a, int size, int num);
// 全ての要素を0で初期化
void array_zeros(int *a, int size);
// 配列の中身を出力
void array_print(int *a, int size);

// from dist.c //出自を書く
// 二項分布
double dist_binom();
// 正規分布
double dist_normal();

// from func.c //出自を書く
// 出力
double func_printout();

#endif
```

##### 補足2
インクルードガードには`#pragma once` を使う方法があります．
先ほどの`header.h`の例でいうと，
ファイル頭の`#ifndef HEADER_H`,`#define HEADER_H`,ファイル末の`#endif`を消して
`#pragma once`をファイルの1行目に書くだけです．

```c:header.h
#pragma once
...
```

[^1]: こういうときは`int size2 = size*4%2; int a[size2];`などとするのが吉
[^2]: 今時は自動生成するらしいです