はじめに
私は初心者である。この備忘録を参考にされようとしている他の初心者の方は、ここに書かれていることを鵜呑みになさらぬようお願いしたい。何か間違いを見つけた方はコメント欄でご報告頂きたい。
変数の初期化
変数に代入される値を変更する予定のない場合、変数の初期化をすることができる。
変数の初期化とは、その変数の領域が確保された時に値を入れることだ。
前回の税のコードを例にする
extern double rate;
double rate = 8.0; // 初期化
int tax(int price){
return price*rate / 100.0;
}
初期化の時に代入される右辺を初期値という。初期化は定義と共に行う。宣言(extern
のついてる方)で行うのは良くない。
初期化を使って、前回の税込料金を求めるプログラムを書くとこうなる。
#include "tax.c"
int tax(int);
int main(void){
int price, pay;
printf("price? ");
scanf("%d", &price);
pay = price + tax(price);
printf("You pay %d\n", pay);
return 0;
}
double rate = 8.0; // 初期化
int tax(int);
int tax(int price){
return price*rate / 100.0;
}
静的変数
上記で扱ったtax.c
は税率も計算式も内部に持っている。こうすると、税率が変わったり、計算式が変わっても、main.c
に影響がでない。この性質を情報隠蔽と呼ぶらしい。
しかし、rate
を外部変数としてextern double rate;
をmain.c
に書いてしまうと、main
関数からrate
にアクセスできてしまい、情報隠蔽が破られてしまう。
これを防ぐものとして、静的変数があるらしい。javaでいうprivate
みたいな?
静的変数は外部変数と同じく、プログラムの実行開始時に領域が確保され、プログラムの実行終了と共に領域が解放される。初期化のタイミングと制限も外部変数と同じとのこと。
しかしこの変数が使える範囲は、定義されたファイル内、あるいはブロック内に限られる。
tax.c
に静的変数を適用すると、、、
static double rate = 8.0; //staticで静的変数へ
int tax(int);
int tax(int price){
return price*rate / 100.0;
}
静的変数やstatic
付きの関数は複数のファイル間での名前の衝突を避けるのにも有効だ。使いまわせる汎用のファイルを作るときによく使われ、自分でライブラリを作る場合にも便利な機能となる。
インライン関数
関数呼び出しのオーバーヘッドを省く
オーバーヘッドとは実引数の値を仮引数にコピーしたり、制御を関数に移したりするときにかかる(無駄な)時間のことだ。本体の小さな関数が何度も呼び出されると、オーバーヘッドが積み重なり、性能が悪くなることがある。このオーバーヘッドは関数を呼び出す代わりにその場で展開してしまえば削減できる。
下記のコードは円の面積が約10になる半径を繰り返しを使って求める。
まずは関数を呼び出す例
double circarea(double);
double circarea(double r){
return 3.14*r*r;
}
int main(void){
double r, area;
r = 0.0;
while(circarea(r) < 10.0)
r += 1e-9;
area = circarea(r);
printf("r = %f, area = %f\n", r, area);
return 0;
}
これが関数を展開した例
int main(void){
double r, area;
r = 0.0;
while((3.14*r*r) < 10.0)
r += 1e-9;
area = (3.14*r*r);
printf("r = %f, area = %f\n", r, area);
return 0;
}
オーバーヘッドは除去できたが、汚くなった。
インライン展開
こんなときに使うのはコンパイラによるインライン展開だ。展開して欲しい関数に予約語inline
をつけて定義する。
使用例は以下
static inline double circarea(double);
static inline double circarea(double r){
return 3.14*r*r;
}
int main(void){
double r, area;
r = 0.0;
while(circarea(r) < 10.0)
r += 1e-9;
area = circarea(r);
printf("r = %f, area = %f\n", r, area);
return 0;
}
inline
をつけた関数をインライン関数というらしい。
まずcircarea
をインライン関数として宣言して、その後関数定義している。こうするとコンパイラは、circarea
の呼び出しを内部的にインライン展開してコンパイルし、その結果のオブジェクトコードを生成するそうだ。static
がついている理由は後で説明されるそうだ。
コンパイラによるインライン展開では、元のソースコードは変わらず、circarea
は関数のままだが、関数呼び出しのオーバーヘッドが除去されるそうだ。
書籍の筆者の環境によると、プログラムの実行時間が3秒も短縮されたそうだが、私の環境では特に変化は感じられなかった。
コンパイラはそれぞれの呼び出しをインライン展開するかどうか選択できるらしい。
だから変わらなかったのかな?
インライン関数の使用条件として、インライン関数の定義と呼び出しが同じファイル内でなくてはならない。
複数のファイルで同じインライン関数を使う
インライン関数も普通の関数と同様に、関数の宣言を各ファイルで行い、関数定義はどこか一つのファイルで行う。
関数宣言は以下
inline double circarea(double);
関数定義は以下
inline double circarea(double r){
return 3.14*r*r;
}
以前やったようにコンパイラはソースファイルを一つずつ処理する。このままでは、関数定義のないファイルでは関数の内容が分からず展開できない。
そこで関数定義のないファイルには、関数定義と同じものを書く。
これをインライン定義というらしい。関数を定義し直したいのではなく、関数定義をコンパイラに見せるためにやるものだ。
だからと言って全てのファイルに同じ関数定義をぶち込むと、どれがインライン定義か分からなくなる、
だから関数定義のあるファイルでは、関数宣言にextern
をつける。
extern inline double circarea(double); // これが関数定義の場合だ
この書籍では、複数のファイルで共有する場合のインライン関数を外部的インライン関数と呼び、
static
付きの、一つのファイルのみで使うインライン関数を内部的インライン関数と呼んでいる。
補足すると、規格ではinline
という予約語は「インライン展開せよ」ではなく、
「できるだけこの関数の呼び出しを早くしてくれ」らしいから、どう扱うかは本当にコンパイラによるそうだ。
眠い!
今回の内容は富永和人氏の新しいC言語の教科書を参考に書き留めてます。