next_inactive up previous


非常にラフな
C プログラミングの復習

佐々木 徹


目次

この文書は

この文書は, 過去に環境数理科学講座の 3 年生や 4 年生に非公式に行った C の勉強会において使った資料をひとつにしたものである.

あまりこ難しいことを書きたくなかった関係もあって, 説明が必ずしも 正確ではないが, この文書では, プログラム例は短いものを完全な形 (つまり, プログラムの一部だけではなく, コンパイルできるようにプログラム の全体)で載せるようにしたので, たぶんこれらの例は参考になるだろう.

また, 元が説明会の資料なので, 書くのが面倒なところは, 口頭で説明した りして, 文書としては完全なものではない.


始めに


一般的に注意した方がよいこと

字下げとコメント

プログラムのソースを見やすくするためには, 字下げ(インデント)と コメントを活用するとよい. 以下の例を見てみよう:

/**********************************************
 * sample.c
 * 単なる例
 * 某年某月某日
 * by 某
 *********************************************/
#include <stdio.h>
/*
 * main 関数
 */
void main()
{
    int i = 2, j = 3;
                            /* i と j の比較 */
    if (i > j) {
        printf("%d は %d より大きい.\n", i, j);
    }
    else {
        printf("%d は %d 以下である.\n", i, j);
    }
}

C に関する説明は後ほど行うが, ここで注目して欲しいことは, if や else を使った条件分岐がどの様に行われているかを字下げ によって見やすくしている点と, 適当にコメント(「/*」と 「*/」で 囲まれている部分)を入れてプログラムを見やすくしている点である. 特に, 字下げは, 単にプログラムを見やすくするだけでなく, エディター が自動的に字下げをしてくれる場合にはミスの発見にも役に立つ.

なお, この例は単純なので, コメントすべき点はほとんどないが, 例として 不要かもしれないコメントも入れてある. コメントは, 他人がソースを みたときなるべく苦労せずに読めるようにという方針で, 適宜入れるとよ いだろう. 私の個人的な意見は, 次の通りである.

その他の注意点

実際にプログラミングをする時には, 以下の点に考慮するように心がけると 後々のプログラミングが楽になるだろう.

参考書

  1. カーニハン, リッチー; プログラミング言語 C 第 2 版, 共立
  2. 椋田; 始めての C 改定第 3 版, 技術評論社
  3. L. Hancock 他, C 言語入門, アスキー
  4. S. Oualline, C 実践プログラミング, オライリー・ジャパン
  5. 山口他; The UNIX Super Text 上, 下, 技術評論社
  6. 小山他; Linux 入門, トッパン


プログラミング環境 - emacs, mule

ここでは, Linux 上でプログラミングする場 合を念頭に置いているが, UNIX 系の OS なら大体似たようなものである.


emacs, mule の C モード

エディターは emacs を用いる. (少し前は, 日本語を含む多国語用の GNU Emacs で mule と呼ばれるものがあったが, 今では mule は emacs に統合され ている)

emacs は少し前のバージョンから 1 X に対応して, マウスの操作で いろいろ出来るようになっている. しかし, 昔ながらの emacs 使いは, マウスやカーソルキーはあまり使わずに, キーボード操作のみで全てを行なって いる. その理由は

などが考えられる. 以上の点から, キーボード操作に慣れておいた方がよいと思 われる. キーボード操作については, The UNIX Super Text 上巻にまとめが付い ているし, 私の web page の mule のページにも(手短かに?)書いてある. 私の web page のコンピュータのページの URL は
http://www.ems.okayama-u.ac.jp/appl/sasaki/comp
である.

さて, mule で C のソースコードを書いているときには, コンパイルも mule の 中から行なった方が便利である. このようにすると, コンパイル時に, エラーや ウオーニングがあった場合, その行にカーソルを移動するのに便利である. (後で述べる (予定) ように, 複数のソースファイルを用いる時には, 更に便利 である.) コンパイル方法は, M-x compile である. エラー等にカーソルをジャンプされる には 「Ctrl+x `」を用いる.

mule で C のソースを書くときには, インデントに関する機能を用いる と便利である. Tab キーまたは, 「ctrl + i」は, カーソルがある行を字下げ する. また, 改行に「ctrl + j」を用いると, インデントも考慮して字下げ を行なってくれる.

mule は ホームディレクトリに 「.emacs 」という名前のファイルがあれば, 起動時にそのファイルを読み込む. mule の設定は, この 「.emacs」ファイル によって大きく変えることができる. ただし, このファイルに間違いがある と, 他の部分まで正しく設定されないかも知れないので注意が必要である. 「.emacs」を変更する時には, このファイルのコピーをあらかじめとっておいて (バックアップして)から行なう方が安全である. (なお, 最近の Vine Linux では, 「.emacs」の代わりに「.emacs.el」を 変更する.)

さて, 「.emacs」に

(global-set-key "\C-x@" 'compile)
という行を書いておくと, Ctrl+x @ でコンパイルをしてくれる.

また, コンパイルコマンドは, デファルトでは「make -k」である. make というコマンドは, 複数のソースファイルがある時に便利なコマンド である. cc (または gcc) コマンドでコンパイルするときには, コマンドを 書き換えて使うのだが, ソースコードの最後に

/*  Local Variables: */
/*  compile-command: "gcc -Wall sample.c -lm" */
/*  End: */
などのようにコンパイルコマンドを書いておくと, コンパイル時にこの コンパイルコマンドを用いるようになる.

また, 「.emacs」に

        (add-hook 'c-mode-hook
          '(lambda ()
             (c-set-style "bsd")))
と書いておくと, BSD 流のインデントをしてくれる.

なお, emacs 19 の初期のものでは, 上の「c-set-style」は「set-c-style」で あった. この場合は,

(setq c-mode-hook
      '(lambda()
         (set-c-style "K&R")
         ))
と書いておくと, Kernighan and Ritchie 流のインデントをしてくれる.

最後に, コメントを書く場合についてだが, 「Esc-;」を使うと便利である. コメントが次の行にわたる場合には, 「Esc-j」で改行できる.


emacs, mule (余計なお世話 ? 編)

セクション 3.1 で emacs の C モード の話をしたが, C モードにはエレクトリック機能というのがある. 例えば, auto-newline は, オンになっていると, タイピングの際に 勝手に改行してくれる. このオンオフは「c-c c-a」で切替える. (emacs の窓の下の黒いラインの表示が (C) や (C/a)となり, 現在の 状態を教えてくれる.) ここまでくると, もはや「余計なお世話」という 気もしないではない.

もしも, 物好きにもこのモードをデフォルトにしたければ, .emacs に

(c-toggle-auto-state)
を入れる. 先の c-set-style と合わせると以下の様になる (と, ものの本に書いてあったが, うまくいかないケースもあるようだ.):
        (add-hook 'c-mode-hook
          '(lambda ()
             (c-set-style "bsd")
             (c-toggle-auto-state)))

もう一つのエレクトリック機能は, hungry-delete-key というやつで (書いていて疲れてきた), 「c-c c-d」で切替える. このモードは, カーソルの左側を一気に削除するというものである. .emacs に

(c-toggle-hungry-state)
を書くと自動的にこのモードになる(かも知れない). また, 「c-c c-t」で両方 オンオフの切替えが出来, .emacs に
(c-toggle-auto-hungry-state)
を書くと両者がデフォルトでオンになる(かもね). (やれやれ...)


制御の基本の復習


条件


if 文

    if (条件) {
        文
    }

    if (条件) {
        文
    }
    else {
        文
    }

    if (条件) {
        文
    }
    else if (条件) {
        文
    }
    else {
        文
    }

問題 1. 0 以上 100 以下の整数をキーボードから入力する. その整数が 0 以上 60 未満の時は D, 60 以上 70 未満の時は C, 70 以上 80 未満の 時には B, 80 以上の時には A を画面に表示するプログラムを if 文を 用いて書け. だだし, これ以外の整数を入力した場合にも対処せよ.

問題 2. 実係数2次方程式 $x^2 + ax + b = 0$ の解を表示する プログラムを書け. ただし, $a$, $b$ はキーボードから入力する. また, 虚数解にも対応するようにせよ. (ここでは桁落ちについて気にしなくてよい.)

問題 3. 以下のプログラムを実行するとどのようになるか. 実行せずに コードを見て答えよ.

#include <stdio.h>

void main()
{
    int i, j, result;
    i = 1; j = 5;
    result = -1;

    if (i > 0) {
        if (j > 5) {
            result = 2;
        }
    }
    else {
        result = 1;
    }
    printf("%d\n", result);
}

問題 4. 以下のプログラムを実行するとどのようになるか. 実行せずに コードを見て答えよ. この プログラムを分かりやすくするために, 問題 3 のようにインデントをしたり, 括弧 { }を用いたりせよ. (ヒント. else は明示的に書かない時は その直前にある else なしの if に対応する.)

補足. これは, こういう書き方をしてはいけないという例.

#include <stdio.h>
void main()
{
int i, j, result;
i = 1; j = 5;
if (i > 0)
if (j > 5)
result = 2;
else
result = 1;
else
result = 3;
printf("%d\n", result);
}


while 文, for 文

while 文の基本パターン:

    while (条件) {
        文
    }

無限ループと break 文:

    while (1) {
        文
        if (条件) {
            文
            break;
        }
        文
    }

for 文:

    for (式 1; 条件; 式 2) {
        文
    }

上の for 文を while 文で表す:

    式 1
    while (条件) {
        文
    式 2
    }

問題 1. $0$ から $1000$ までの偶数の和を求めるプログラムを 書け. ただし, 整数変数 i を $0$ から $1000$ までひとつおきに動かし, それ それの i に対し, 和 sum に sum + i を代入するという方法で求める こと. なお, while 文を使うものと for 文を使うものの両方を書け.

問題 2. $0$ から $1000$ までの整数のうちで, 3 か 5 かのいずれかで 割り切れるものの総和を求めるプログラムを書け. ただし自然数 i を自然数 j で割った剰余は i % j で求められる. (ヒント. 例えば for 文で $i = 0$ から $i = 1000$ までループをし, そのループ中で, 「$i$ が 3 で割り切れる」または「$i$ が 5 で割り切れる」 時に, 和を加えていくという方法がある.)

問題 3. 問題 2 を解こうとして次のコードを書いたが答えが おかしかった. どこが悪いか述べよ.

int main()
{
    int i, sum;
    sum = 0;

    i = 0;
    while (i <= 1000) {
        if (i % 3 == 0) {
            sum += i;
        }
        if (i % 5 == 0) {
            sum += i;
        }
        ++i;
    }
    printf("Sum is %d\n", sum);
    exit(0);
}

問題 4. キーボードから正の整数を次々と入力して, それらの最大値を 求めるプログラムを while, break 文を用いて書け. ただし, キーボードからの入力が $0$ または負 のときに, ループから出て, いままでの最大値を表示しプログラムを終了 するものとする. また, 正の整数がひとつも入力されすに終了するときは, $0$ を 表示するものとする. なお, 実行は以下のようになるようにせよ.

~% ./a.out 
Input an integer: 5
Input an integer: 10
Input an integer: 21
Input an integer: 7
Input an integer: -1
Max is 21
(ヒント. 入力が正でない時に break する無限ループの中で, 入力が その段階の最大値 max より大きい時に max に その入力した数を代入する.)


二重ループ

問題 1. 以下のプログラムを実行するとどのような結果を得るか 述べよ. 実行せずにコードを見て答えよ.

void main()
{
    int i, j;
    for (i = 1; i <= 3; ++i) {
        for (j = 1; j <= 2; ++j) {
            printf("(%d, %d)\n", i, j);
        }
        printf("\n");
    }
}

問題 2. 以下のような九九の表:

  1       2       3       4       5       6       7       8       9
  2       4       6       8      10      12      14      16      18
  3       6       9      12      15      18      21      24      27
  4       8      12      16      20      24      28      32      36
  5      10      15      20      25      30      35      40      45
  6      12      18      24      30      36      42      48      54
  7      14      21      28      35      42      49      56      63
  8      16      24      32      40      48      56      64      72
  9      18      27      36      45      54      63      72      81
を作成するプログラムを書け. ただし, 二重ループを用いること. また, 汎用性もある程度考慮すること. 極端な話:
printf("%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
        1*1, 1*2, 1*3, 1*4, 1*5, 1*6, 1*7, 1*8, 1*9);
みたいなものは論外. (ヒント. printf 文の使い方に注意する. この辺が良く分からなかったら, とりあえず, プリントの見栄えがよくなくても, 正しい数字が出力されるプログラム を一旦書いてみよう. 見栄えを気にするのはその次の段階である.)


2 重ループ問題 2 の答

#include<stdio.h>
void main()
{
    int i, j;
    for (i = 1; i <= 9; ++i) {
        for (j = 1; j <= 9; ++j) {
            printf("%3d\t", i * j);
        }
        printf("\n");
    }
}


配列の使い方

配列はベクトルや行列を扱う時に便利であるばかりではなく, 文字列を 扱う時にも必要になる.

配列を本格的に扱うにはポインタを理解する必要があるが, とりあえず それ以前の初等的なことをここで述べる.


配列の復習

C で配列を使う場合の, 最重要点は,
int array[5];
と宣言した場合, array[0] から array[4] までが使えるということである. こ のとき array[5] 以降は使えないが, 仮に使ってしまった場合でも, コンパイル 時には普通はエラーが出ない. しかし, 実行時に segmentation fault が起こっ たり, メモリ管理がちゃんとしていない OS ではシステムがフリーズすることも ある) ので注意がいる.

/***********************************************
array_s1.c
配列の入力と表示
***********************************************/
#include <stdio.h>

#define SIZE 5
void main()
{
    double array[SIZE];         /* 配列 */
    double sum;                 /* 和 */
    int i;                      /* ループのカウンタ */

    fprintf(stderr, "配列のサイズは %d\n", SIZE);       /* サイズ表示 */
/*
* 配列に代入
*/
    for (i = 0; i < SIZE; i++) {
        fprintf(stderr, "第 %d 成分: ", i + 1);
        scanf("%lf", &array[i]);
    }
/*
* 表示
*/
    for (i = 0; i < SIZE; i++) {
        printf("%f ", array[i]); /* スペースで区切る */
    }
    printf("\n");               /* 最後に改行 */
}

問題. 標準入力(キーボード)から2つのベクトルを入力して, その和と 内積を計算して表示するプログラムを書け. だだし, 以下の条件を 満たすようにすること.

補足. マクロについて

上記の例において,
#define SIZE 5
という行があったが, これは, SIZE という文字列が現れたら 5 という文字(列) に置き換えるという意味である. これをマクロ展開という. マクロ展開は, そのとおり置き換えをするので, 次の問題のような点に注意が必 要である. 問題. 以下のプログラムの実行結果はどうなるか.
#include <stdio.h>
#define VALUE 1 + 2
void main()
{
     printf("%d\n", VALUE * 2);
}


二重配列

C では 二重配列や三重配列を使うことが出来る. このときは

    double matrix[3][2];
のように宣言する. matrix[3][2] は, 成分数が 2 つの横配列 3 つが 縦に並ぶ配列と考える. たとえば
matrix[0][0] = 1
matrix[0][1] = 2
matrix[1][0] = -1
matrix[1][1] = 1
matrix[2][0] = 3
matrix[2][1] = 5
は,
matrix[0] : {1, 2}
matrix[1] : {-1, 1}
matrix[2] : {3, 5}
と解釈すると, この matrix[][]
{
  {1,2},
  {-1,1},
  {3,5}
}
となる.(注. この記法で, 初期化のときにかぎり配列に代入できる.)


行列の計算

行列をキーボードから入力して, それを表示するプログラムを書いてみる.
#include <stdio.h>

#define GYO 3                   /* 行数 */
#define RETSU 2                 /* 列数 */

int main()
{
    double matrix[GYO][RETSU];  /* 行列 */
    int i,j;                    /* ループの変数 */
    char buff[100];             /* キーバッファ */

    fprintf(stdout, "%d 行 %d 列の行列の成分を1つづづ入力します.\n",
            GYO, RETSU);
/* 入力部分 */    
    for (i = 0; i < GYO; i++) { 
        for (j = 0; j < RETSU; j++) {
            fprintf(stdout, "(%d, %d) 成分:", i + 1, j + 1);
            gets(buff);
            sscanf(buff, "%lf", &matrix[i][j]);
        }
    }
/* 出力部分 */    
    fprintf(stdout, "入力した行列は:\n");
    for (i = 0; i < GYO; i++) { 
        for (j = 0; j < RETSU; j++) {
            fprintf(stdout, "%8.3f\t", matrix[i][j]); /* タブで分離 */
        }
        fprintf(stdout, "\n");  /* 1行を表示したら改行 */
    }
}


関数

C では FORTRAN のサブルーチンに対応するものにも関数を用いる. つまり, 値を返さない関数や, 引数の無い関数もある.

ANSI 準拠か否かによって, 関数やその引数の型の宣言の仕方等において異なるの で注意が必要である. 今は大抵のコンパイラが ANSI 準拠なので, ここでもそれ に従うものを解説する.


簡単な例

/*****************************************************
  funct1.c
  関数の使い方 1
*****************************************************/
#include<stdio.h>

/*------------プロトタイプ宣言---------------*/
int twice(int a);

/*------------メイン--------*/
int main()
{
    int a, b;

    a = 5;
    b = twice(a);                 /* 関数呼出  */
                                /* a の値は変わらない */
    printf("%d, %d\n", a, b);     /* 結果表示 */
    return 0;
}

/*----------2倍の値を返す--------*/
int twice(int a)
{
    a *= 2;               /* 2 倍する */
    return(a);
}

上の用に定義された場合, 関数 main における変数 a と 関数 twice における 変数 a とは互いに無関係である事に注意せよ(ローカル変数. 変数のスコープ). また, 関数 twice(a) を呼び出しても変数 a の値は変わらないこと (call by value)に注意せよ.


外部変数

関数の呼び出しに関して, 「Call by value (値による呼び出し)」では, プログラムのうち, (ある)配列にキーボードから値を代入する部分をサブルーチン(関数) にするのが難しいように思える2.

しかし, C においてもこれを解決する方法がある. 例を挙げれば, 外部変数を 用いる方法やポインタを関数の引数にする方法などがある.

ポインタについてはあとで述べるので, ここでは外部変数を使う方法について解 説する. (この方法は, 後で述べるように, あまり奨められない一面がある.)


簡単な例

ベクトルをキーボードから入力し, それを標準出力に表示する例を. 関数を使って書いてみる.
/**************************
gaibu.c
外部変数を用いて, 関数間で変数をやりとりする例.
あまり勧められない方法と言われているが, 便利な面もある.
**************************/
#include <stdio.h>
#define SIZE 5
/*----------外部変数--------------*/
double vector[SIZE];
/*--------プロトタイプ宣言--------*/
void input_vector(void);
void print_vector(void);

int main()
{
    input_vector();
    print_vector();
    return 0;
}
/*--------ベクトルの代入----------*/
void input_vector(void)
{
    int i;
    char buff[100];
    for (i = 0; i < SIZE; i++) {
        printf("ベクトルの第 %d 成分:", i + 1);
        gets(buff);
        sscanf(buff, "%lf", &vector[i]);
    }
}
/*--------ベクトルの表示--------*/
void print_vector(void)
{
    int i;
    for (i = 0; i < SIZE; i++) {
        printf("%f\n", vector[i]);
    }
}

この例は, 以下の点から推奨されない.

以上の点から, このような場合には, 後述するアドレス渡しによる変数の やりとりが勧められる.

問題. 正方行列 $A$, $B$ をキーボードから入力し, 積を出力する プログラムを書き, 幾つかの 2 次と 3 次の行列に対して実行してみよ. ただし, 以 下の条件をみたすようにすること:


アドレス渡し

データのアドレス

ここでは変数とアドレスに関して簡単に述べる. ここはポインタに 関する話を先取りすることになるが, ポインタに関する詳しい話は後で あらためて述べることにする.

ここでは, 以下のことのみ覚えておこう. data をデータ, address をアドレスとすると,

アドレス渡しの例

/***************************************************
fun1.c
関数の例. アドレスを使ってデータを渡す..
***************************************************/
#include <stdio.h>

void twice(int *num);

void main()
{
     int a;
     a = 5.0;
     printf("a = %d.\n", a);
     twice(&a);                 /* a のアドレスを渡す */
     printf("a = %d.\n", a);
}
/*
渡された値を 2 倍にする. アドレス渡し.
*/
void twice(int *num)            /* 引数 num はアドレス. そのアドレスの */
                                /* データ は int 型 */
{
     *num *= 2;                 /* この左辺はデータ */
}

配列のアドレス渡し

配列のアドレス渡しの場合は, その配列の最初の成分のアドレスを 渡す. 例えば, a[] という配列の最初の成分のアドレスは, &a[0], または, a とあらわす. 2 重以上の配列の場合も同様である.

/***************************************************
fun1.c
関数の例. 配列のアドレスを使ってデータを渡す..
***************************************************/
#include <stdio.h>

#define SIZE 5

void twice(int array[SIZE]);
void print_array(int array[SIZE]);

void main()
{
     int a[SIZE];
     int i;
                                
     for (i = 0; i < SIZE; i++) { /* 配列に値を代入 */
          a[i] = i;
     }
     printf("使用前\n");
     print_array(a);            /* a のアドレスを渡す */
     twice(a);                  /* a のアドレスを渡す */
     printf("使用後\n");
     print_array(a);            /* a のアドレスを渡す */
}
/*
* 渡された配列のすべての成分の値を 2 倍にする. アドレス渡し.
*/
void twice(int array[SIZE])     /* 引数 array はアドレス. そのアドレスの */
                                /* データ は int 型 */
{
     int i;

     for (i = 0; i < SIZE; i++) {
          array[i] *= 2;
     }
}
/*
* 配列の成分を出力する.
*/
void print_array(int array[SIZE])
{
     int i;
     for (i = 0; i < SIZE; i++) {
          printf("%d\n", array[i]);
     }
}

問題. 上の例を, 配列をキーボードから入力する様に変更せよ. ただし, 配列をキーボードから入力する部分は, 関数 input_array としてプログラムする事.


行列計算への応用

ここでは, 以上の応用として正方行列の計算をするプログラムを 作ってみよう.

/*****************************************
matrix1.c
正方行列の和の計算
*****************************************/
#include <stdio.h>

#define MAX_SIZE 100

void input_mat(int size, double matrix[MAX_SIZE][MAX_SIZE]);
void print_mat(int size, double matrix[MAX_SIZE][MAX_SIZE]);
void sum_mat(int size, double mat1[MAX_SIZE][MAX_SIZE],
             double mat2[MAX_SIZE][MAX_SIZE],
             double sum[MAX_SIZE][MAX_SIZE]);
void main()
{
     double mat1[MAX_SIZE][MAX_SIZE], mat2[MAX_SIZE][MAX_SIZE],
     sum[MAX_SIZE][MAX_SIZE];
     int size;

     printf("正方行列の和を計算します.\nサイズは:");
     scanf("%d", &size);

     if (size > MAX_SIZE) {     /* サイズが大きすぎる場合 */
          printf("行列のサイズは %d 以下.\n", MAX_SIZE);
          exit(-1);
     }
     printf("行列 1 を入力します.\n"); /* 入力 */
     input_mat(size, mat1);
     printf("行列 2 を入力します.\n");
     input_mat(size, mat2);
     sum_mat(size, mat1, mat2, sum); /* 計算 */
     printf("行列 1:\n");       /* 出力 */
     print_mat(size, mat1);
     printf("行列 2:\n");
     print_mat(size, mat2);
     printf("和:\n");
     print_mat(size, sum);
}
/*
* 大きさ size の行列を配列 matrix に代入.
*/
void input_mat(int size, double matrix[MAX_SIZE][MAX_SIZE])
{
     int i, j;
     for (i = 0; i < size; i++) {
          for (j = 0; j < size; j++) {
               printf("%d, %d 成分:", i + 1, j + 1); /* 添字がずれる */
               scanf("%lf", &matrix[i][j]);
          }
     }
}
/*
* 配列 matrix の大きさ size の行列を出力.
*/
void print_mat(int size, double matrix[MAX_SIZE][MAX_SIZE])
{
     int i, j;
     for (i = 0; i < size; i++) {
          for (j = 0; j < size; j++) {
               printf("%6.2f\t", matrix[i][j]);
          }
          printf("\n");
     }
}
/*
* 大きさ size の 2 つの行列の和を求める. 結果は配列 sum に代入.
*/
void sum_mat(int size, double mat1[MAX_SIZE][MAX_SIZE],
             double mat2[MAX_SIZE][MAX_SIZE],
             double sum[MAX_SIZE][MAX_SIZE])
{
     int i, j;
     for (i = 0; i < size; i++) {
          for (j = 0; j < size; j++) {
               sum[i][j] = mat1[i][j] + mat2[i][j];
          }
     }
}

問題 1. 同様に, 2 つの正方行列の積を求めるプログラムを書け.

問題 2. 同様に行列の掃き出しを行なうプログラムを書け. ただし, 以下の条件をみたすものとする.

  1. 正方行列に限らず, 一般の行列を扱えるようにする.
  2. 行の基本変形「第 $k$ 行を $a$ 倍する」
  3. 行の基本変形「第 $k$ 行に第 $l$ 行の $a$ 倍を加える」を 行なう関数を作る.
  4. 2, 3 の関数を用いて, 掃き出しの中心 $(i,j)$ 成分を 1 にし, それを 中心に列の掃き出しをする関数をつくる.
  5. 最初に行列をキーボードから入力する関数を呼び出し, 行列を出力 する関数を呼び出す. それ以降は以下のループを繰り返す:
    1. 掃き出しの中心の成分のインデックスをキーボードから入力.
    2. 掃き出しの中心を 1 にし, 列の掃き出しをする.
    3. 行列を表示する.


デバッガの使い方

ここでは, デバッガの使い方の基本的な部分について解説する.


コンパイル

デバッガを使ってプログラムをデバッグ (なんだかくどい!) するには, その プログラムをコンパイルするときに, デバッグ情報を生成するオプションを つけなくてはならない. 大抵の場合, -g がそのオプションである. 例えば
% gcc -g hogehoge.c
みたいな感じだ. (ひょっとしたら, 古い Slackware Linux には, -static オプションが必要なバージョンがあったかも知れないが, ちょっとよく覚えていない.)

なお, デバッグ情報がついている実行ファイルはサイズが大きいので, 完成 時(つまり, もうデバッグしなくてもいいと, あなたが判断した時だ. しかし, この判断は

往々にして誤りだったりする...
)には, strip コマンドで デバッグ情報を取り除くか, -g オプションを取り除いてコンパイル しなおした方がよい. (コンパイルしなおすなら, ついでに, 最適化オプションの -O2 とか, -O3 とか, -funroll-loops とかをつけると
しあわせなれるかも
しれない.)


起動

emacs から gdb を起動する方法は, Esc-x gdb である. これをすると, emacs の窓の 下に「Run gdb (like this): gdb」と表示されるので, これに続き, 実行ファイル名(./a.out など)を入力する. すると, emacs にはgdb の窓が表示される. この窓にgdb のプロンプト「(gdb)」があり, 基本的には gdb の命令は, この プロンプトに入力する. 以下 gdb のプロンプトに命令を入力するのを
(gdb) 命令
のように書きあらわすことにする. 実際にはカーソルをプロンプト「(gdb)」の 直後にあわせて命令をタイプする. もちろん最後に Enter キーを押す.

ところで, 大抵の場合, トレースしたいプログラムのソースを emacs で編集して いる状態で gdb を起動すると思う. この場合は, 目の前にあるソースの バッファが消えちゃってgdb のバッファが表示される. つまり,

ソースが見えない悲しい状態
になってしまう. これでも,
(gdb) list
とすると, ソース(の一部)が表示されるが, こんな事をしていては, 何のために
わざわざ emacs から
デバッガを起動しているのかわからない. ここは素直に, 新しいウインドウやらフレームやらにソースを表示しておこう. きっと
ご利益があるはず
である. 次のセクションでは, emacs のウインドウやフレームについて 説明する. デバッガの話からははずれるので, emacs に慣れている人は とばして構わない.


emacs

そういう訳で, emacs のウインドウやフレーム操作について補足しておこう.
ややこしくて恐縮
だが, emacs のウインドウは X のウインドウとは違う概念で, X でいうところのウインドウは emacs ではフレームと呼ぶ. emacs のフレーム(つまり X の言葉での emacs のウインドウ)を分割して利用する ときに, その分割した領域を(emacs の)ウインドウと呼ぶのである.

これでは

ふつーはわけがわからん
だろう. 試しに, emacs のフレームで 「ctrl+x 2」とやってみよう. すると, フレーム の中が上下に二つに分かれるはずである. この分かれた部分を emacs のウインドウ と呼ぶのである. X が使われるようになる前には, この機能を用いて幾つかの 作業を並行して行っていたのである. いわば,
元祖ウインドウ
である. 当然この機能は, X が使えない環境でも使える...というか, いまだに そのような場合には強力な機能である. 例えば, 遅い電話回線で, リモートの emacs を使う場合には, X クライアントとして emacs を使うと
遅くて遅くて使えん!
状態になる. こういう時には重宝する機能である...と言っても, 実感として納得してもらえないかも知れないので, とりあえず簡単に 操作法を説明して話をすすめることにしよう.

(emacs の)フレームを 2 分割して, ウインドウを増やすには,「ctrl+x 2」 とする. ウインドウを減らすには, 「ctrl+x 1」と 「ctrl+x 0」と いうのがある. これらは, どのウインドウを消すかが違うのであるが, どう違う のかは自分で試してみて欲しい.

ひとつのフレームのウインドウ間を移動するには, 「ctrl+x o」を使う. カーソルがある方が現在使っているウインドウである. ファイルオープン 「ctrl+x ctrl+f」をすると, このウインドウに新しいファイルが表示される.

既にオープンしているファイルを利用中のウインドウに表示するのは「ctrl+x b」 である. emacs の窓はバッファと呼ばれているメモリ上の領域を表示している. 既にオープンしているファイルのバッファを表示して, このバッファを編集する というわけである. バッファの切替え命令が「ctrl+x b」なのである. 似たような コマンドに「ctrl+x ctrl+b」がある. こちらはバッファのリストを表示する. 慣れないうちは結構間違えてしまいそうである.

なんだか,

とってもメンドウ
に思えるかもしれないが, これらは慣れてしまうと, 他のエディタを使う気に なれないという
一種の中毒症状?
になってしまう(笑). 3

さて, フレームの操作だが, これはマウス操作でメニューを使って 出来る. しかし, マウスなぞ 使わないのが,

正しい emacs 使い
であると思うのなら, メニューの項目の右側の括弧内の命令を使ってみる とよいだろう.


ブレークポイント

ううむ,
ずいぶん回り道をしてしまった
が, 本題に戻ろう.

gdb を起動したら, プロンプト (gdb) に run という命令を入力すれば プログラムは gdb の窓の中で走る...が,

問答無用でプログラムはつっ走って
しまう. どこかをブレークポイントにして, プログラムを一旦停止 しなければならないのだ.

これは, emacs の窓かフレームにプログラムソースを表示して, プログラムを止めたい行にカーソルを持ってくる. そうして, 「ctrl+x スペース」 とすると, その行がブレークポイントになる. こうしておいて, run する と, その行の手前まで実行してプログラムは一時停止する. おまけに 御丁寧にも, その行に

矢印までついて
いる.

こうなれば, しめたものだ. あとは, 1 ステップごとに実行しようが, 現在の 変数の値を見ようが, 次の ブレークポイントを設定してそこまで走らせようが,

まさに, 思うがまま
だ.


デバッグしてみる

ブレークポイントで止まっている状態で, 現在の変数や式の値を表示表示する には, print コマンドを用いる.

(gdb) print 変数名や式
とする.

ステップを進めるのは, next か step コマンドで行う. step コマンドは, 関数を呼び出した場合に, 関数の内部まで トレースを行なうという点が next と異なる. next コマンドを実行する時には,

(gdb) next
とする. step コマンドも同様である.

現在停止しているブレークポイントから次のブレークポイントまで実行するには, continue コマンドを用いる.

continue ってスペルが難しい
と思う人は, 「cont」でもいいし, なんだったら「c」でもいい. コマンド の最初の 1 文字でデバッガが判断出来る場合は,
頭 1 文字でオッケー
なのである. また, 同じ命令を繰り返す場合は, 単に enter でよい. この辺は, なかなか便利に出来ている. さすがに gdb の作者だって, 毎回毎回 continue やら print やら next やらタイプしてたらデバッグなんて
やってらんないよー
と思ったんだろう.

条件つきブレーク

例えば, 第 117 行で, 条件 x > 0 をみたしているときにブレークさせたければ,
(gdb) break 117 if x > 0
のように, ブレークポイントを設定する.


その他必要なこと

さて, デバッグもこみいってくると, いくつもブレークポイントを設定してしまって, なにがなにやら分からん状態になってくるかも知れない. おまけにソースを 変更して再コンパイルして run コマンドを実行した場合, 前に設定したブレーク ポイントを, ご丁寧にも覚えててくれていたりする. そうこうしていると, 色々なところでブレークしてくれて,
各駅停車世界の旅状態
になってしまいかねない.
これはこれで, のどか
かも知れないが, デバッガはのどかな状態でも, デバッグしている人の
ストレスは増すばかり
であろう. そこで, 現在のブレークポイントを表示したり, 不要になった ブレークポイントを解除したりする必要が出てくる. ブレークポイントに関する 情報は
(gdb) info b

で得られる. ブレークポイントは「delete 番号」で解除される. この番号は info で表示されるものだ.

さて, 本当に書くのに疲れてきたので, この辺で終ることにしよう. 更に いろいろ知りたければ, 「help」コマンドを使うとよい. おそらく help は 英語だろうけど,

フランス語やドイツ語の help よりよっぽどマシ
だと思ってあきらめて読んでくれ.

この文書について...

非常にラフな
C プログラミングの復習

この文書はLaTeX2HTML 翻訳プログラム Version 2002-2-1 (1.70)

Copyright © 1993, 1994, 1995, 1996, Nikos Drakos, Computer Based Learning Unit, University of Leeds,
Copyright © 1997, 1998, 1999, Ross Moore, Mathematics Department, Macquarie University, Sydney.

日本語化したもの( 2002-2-1 (1.70) JA patch-1.6 版)

Copyright © 1998, 1999, Kenshi Muto, Debian Project.

Copyright © 2000, Jun Nishii, Project Vine.

Copyright © 2001, 2002, Shige TAKENO, Niigata Inst.Tech.

Copyright © 2002, KOBAYASHI R. Taizo, Project Vine.

を用いて生成されました。

コマンド行は以下の通りでした。:
latex2html -split 0 intro.

翻訳は によって 平成17年5月17日 に実行されました。


脚注

... は少し前のバージョンから1
もはやかなり前になってしまった...
... にするのが難しいように思える2
これに関しては, セクション 7の例 を外部変数なしにどのように実現するかを考えるとわかると思う
... になってしまう(笑).3
mule for Windows なんていう奇怪なものが 出ているのがその証拠と言えようか?

next_inactive up previous
平成17年5月17日