14回目


makefikeについて
複数のソースファイルを組み合わせてプログラムを作る場合、コマンドラインで一つ一つ
命令を打ち込むのは大変面倒なのと、必要な情報を忘れる可能性があります。
そのため、makefileというマクロ集を作っておき、開発しているプログラムに関連するような
ファイルを明らかにしておく必要があります。
情報処理演習1ですでに学んでいるかもしれませんが、もう一度説明しておきます。

用意したmakefikeは以下のものがあります。書き方はいくらでもあるので参考程度に見て
ください。

CC = bcc32 
LINK = bcc32
TARGET = kadai15fft
OBJ = .obj fft_test.obj fft.obj lpf.obj

.SUFFIXES: .obj .c .h

$(TARGET).exe:$(OBJ)
$(LINK) -e $@ $^

.c.obj:
$(CC) -c $<

$(TARGET).obj: $(OBJ:%.obj=%.h)
fft_test.obj: fft_test.h
fft.obj:fft.h: fft_test.h
lpf.obj: lpf.h

clean:
$(RM) $(TARGET) $(OBJ) *.tds *.bak
#[1]
#[2]
#[3]
#[4]

#[5]

#[6]
#[7]

#[8]
#[9]

#[10]
#[11]
#[11]
#[11]

#[12]
#[13]

[1]はコンパイラとしてbcc32を使う。
[2]はリンカとしてbcc32を使う。
{3}は実行ファイル名としてkadai15fftとする。
[4]は生成されるオブジェクトファイル群を示している。
[5]はコンパイルに関連する拡張子を明言している.
[6]はターゲットのファイル名とそれに関するファイル名を示す。
[7]は実際のコマンドを示す。$@はターゲットの名前、$^はすべての関連するファイルの名前。
[8]はサフィックスルールにより、*.cから*.objファイルを生成することを示す。
[9]は実際のコマンドを示す。$<は関連するファイルを最初のファイルの名前。
[10]は実行のヘッダファイルの関連性を示す。(書かなくてもいいが,確実なリンクにはあった方がいい)
[11]はヘッダファイルの関連性を示す。(書かなくてもいいが,確実なリンクにはあった方がいい)
[11]は不要ファイルを消去することを示す。(書かなくてもいい)
[12]は実際のコマンドを示す。RMはすでに用意されたマクロ。(書かなくてもいい)

ソースファイルから実行ファイルができるまでを考えてみましょう。
ソースファイル一つだけであれば、ソースファイルがコンパイルされてオブジェクトファイルが
生成され、オブジェクトファイルとライブラリがリンクされて実行ファイルが生成されます。
これだけであれば、例えば,

bcc32 main.c

だけでもコンパイルからリンク、実行ファイル生成までやってくれますが、分割コンパイルには
対応できません.そこでmakefileに関連するファイルをすべて記述し,複数のファイルから構成
されるファイル群(プロジェクトといっても良い)から実行ファイルを作成します.これさえ作れば

make

とタイプすれば,実行ファイルを生成することが可能です.
もしmakefile以外のファイル名にしたければ,例えばmake_prg.mを作ったとしたら,

make -f make_prg.m

などと-fとファイル名を示せば,同様の処理が可能です.同一のディレクトリにいくつも開発する
プログラムを置いてしまう時には,こちらの方がいいかも知れません.

なお,前回説明したような,プリプロセッサ,プロトタイプ宣言との整合性には注意して下さい.
ソースファイルとしてmain.c sub1.c sub2.cがあるとして,また,sub1の関数をsub2で使うとしたら
以下のように関連付けをすれば,ソースファイル間でのやりとりができることも注目しておいて
ください.

sub1.c sub1.h
#include <math.h>
#include "sub1.h"

double fnc1(double x){

}
#ifndef _SUB1_H
#define _SUB1_H

double fnc1(double)

#endif
sub2.c sub2.h
#include <math.h>
#include "sub1.h"
#include "sub2.h"

double fnc2(double x){
double y;
y=fnc1(x)
}
#ifndef _SUB2_H
#define _SUB2_H


double fnc2(double)


#endif
main.c
#include <math.h>
#include "sub1.h"
#include "sub2.h"

main(){
 double・・・.
 int i;



}

ヘッダファイル内のプリプロセッサの初めの2行は,一度コンパイルしたコードについては,もう
一度コンパイルしないように,という意味合いです.sub1をコンパイルしてsub2のコンパイルを
始めたとき,#include "sub1.h"があるからといってもう一度コンパイルする意味はないので,
#ifndef _SUB2_Hという既に_SUB2_Hが定義されているなら,#endifに飛んで処理を止める,という
意味合いの行を設けています.

さて,練習のため次のようなアルゴリズムの関数を用いたプログラムを作ってみましょう.

連立一次方程式の解法
n元m立一次方程式は行列形式にして解くことができます.

a11 x1 + a12 x2 + ・・・ + a1n xn =b1
a21 x1 + a22 x2 + ・・・ + a2n xn =b2



am1 x1 + am2 x2 + ・・・ + amn xn =bn

のような方程式を解くことができます.ソースファイルは,simeq_mxinv_m.cにあります.

行列式
連立一次方程式のアルゴリズムを使って行列式を解くことができます.ソースファイルは同じ
所にあります.
数値計算は,数式そのものを解くわけではないので,このようにアルゴリズムが転用できて
しまうことがあります.

ソースファイル内に入っている関数は

となっています.


課題1
以下のファイルをダウンロードし,実行ファイルを作成せよ.概ね動作するが,細かいところを
調整し,ノイズの乗った波形をFFTしたものとFFTにウインドウを掛けたもの,それらにローパス
フィルタを掛けたものを比較してみよ.

ダウンロードするもの→E_fft.c E_fft.h E_lpf_m.c E_lpf_m.h E_rand.c E_rand.h
E_fft_test.c E_fft_test.h E_window.c E_window.h E_sin_with_noise.c makefile

提出するもの
実行ファイル,生成されたtxtファイル5種,結果の考察のテキストファイル

※ダウンロードしたものの定数が必ずしも適切ではないかもしれませんので適宜修正して下さい.

課題2
ファイルsimeq_mxinv_m.cをダウンロードし,必要なヘッダファイルsimeq_mxinv.hとメイン関数の
ソースファイル○○.cおよびmakefileを作り,3元一次5変数の方程式を解け.
例えば,
5 x1 + 3 x2 - 3 x3 - 2 x4 + 2 x5 = 2
2 x1 + 1 x2 + 2 x3 + 2 x4 + 3 x5 = 4
2 x1 - 2 x2 - 5 x3 + 3 x4 + 4 x5 = 1
であれば,行列はm=3 n=5+1となる.これを行列にすれば以下のものとなる.

| 5  3 -3 -2 2 2|
| 2  1  2  2 3 4|
| 2 -2  -5  3 4 1|

double simeq(double a[], int m, int n)を行った最後の[列]が解となる.

ヒント
上記は行列であるが,プログラム上は1次元配列で示す.約束事さえできていれば,
このような場合もあることを知っていてほしい.
3元なのでi = 0, 1, 2とし,5変数と1定数なので,j = 0, 1, 2, 3, 4, 5とすると
a[n * i + j]とすれば,重複なく表現できるのである.だから,行列上で(1行,2列)の変数は,
i = 0, j = 1と表現できるのでa[n * i + j] = a[6 * 0 + 1] = a[1]に格納されるのである.
行列に対応させれば,

| a[0]  a[1] a[2]  a[3]  a[4]  a[5] |
| a[6]  a[7] a[8]  a[9] a[10] a[11] |
|a[12] a[13] a[14] a[15] a[16] a[17] |

となると考えればよい.

提出するもの
ヘッダファイルsimeq_mxinv.h,メイン関数入ったのソースファイル,makefile(名前を変えて
もよい例えばmakefile1.m),結果の考察のテキストファイル

※ダウンロードしたものの定数が必ずしも適切ではないかもしれませんので適宜修正して下さい.

課題3
ファイルsimeq_mxinv_m.cをダウンロードし,以下の行列について,行列式を解け.

| 5  0 -3 -2 2 2|
| 2  1  2  2 3 4|
| 0 -2  -5  3 1 1|
| 1 -2  -1  1 4 1|
| 3 -4  2  3 2 1|
| 0 -1  -5  4 2 2|

なお,元の行列と逆行列を掛けてみて,確かに単位行列になるか確認もすること.

提出するもの
メイン関数入ったのソースファイル,makefile(名前を変えてもよい例えばmakefile2.m),
結果の考察のテキストファイル

※ダウンロードしたものの定数が必ずしも適切ではないかもしれませんので適宜修正して下さい.


前回の解答・解説


参考 動的配列

例えば,以下のような宣言があったとします.

double a[100000], b[100000], c[100000], d[100000], e[100000], f[100000];

これでもよいが,データ数の多い配列で闇雲にメモリを確保するのもプログラムのセンスから
あまり美しくはないといえます.使うときだけメモリを確保し,要らなくなったら解放するような
プログラムを作るのが良い場合があります.
そのようなとき,動的配列が有効です.double a[100000];であれば,以下のように,

int m=100000;
double *hp = malloc(m*sizeof(double));

とおくことができます.あたかも配列のように使いたいのであれば,以下のようにすれは良いのです.
不必要になったらfreeで解放してあげればいいのです.

#define M 100000
#define HP(a) hp[m]

int main(){
 double a;
 double *hp=malloc(M*sizeof(double));
 if (hp == NULL) exit(1);

 HP(0) = 1.0;・・・

 HP(a) =
 free(hp);
}

ついでに,2次元配列であれば,

int m=100, n=100;
double *hp = malloc(m*n*sizeof(double));

同様に,とすれば,配列のように使えます.

#define M 100
#define N 100
#define HP(a, b) hp[(a)*(M)+(b)]

int main(){
 double a, b;
 double *hp=malloc(M*N*sizeof(double));
 if (hp == NULL) exit(1);

 HP(0, 0)= 1.0;・・・

 HP(a, b) =
 free(hp);
}