初心者にC言語を教える時に気をつけたいこと

# C言語を学ぶ理由

私の所属している早稲田大学基幹理工学部ではC言語の授業が必修となっていることもあり、普段、プログラミングが初めての人にC言語を教える機会が多くあります。これは一般に同意を得られない事なのですが、私は最初に学ぶ言語としてC言語は適しているのではないかと考えています。その理由は

- 仕様が変化することが少なく、一度学んだ文法が多くの環境で動く

C言語はプログラミング言語会では比較的長い歴史を持つ言語であり、その文法は最初期(K&amp;R C)はかなり流動的であったものの、1989年のANSI Cと呼ばれる規格以降はほとんど変化していません。よって、現在の主流規格であり大学でも広く教えられているC99やC11等の標準に従うコードであれば、未来にわたって多くのCコンパイラで動くという確証が持てます。また、すでに成熟した言語であるため、今学んだ知識が近い将来古くなるという事もそう簡単には起こらないと考えられます。よって、C言語を習得すれば（大げさに言えば）生涯に渡って活用し続けられる知識を得たといって良いのではないかと思います。

- コンピュータのアーキテクチャやUNIXシステムとの深い結びつきがある

プログラミング言語を学ぶ必然性が広く叫ばれている昨今ですが、プログラムが動く舞台であるコンピュータのアーキテクチャやOSを学ぶという動機づけは(プログラミング言語に比べて)一般に乏しい傾向にあると思います。言うまでもなく、これらの知識は研究分野によっては欠かすことの出来ない内容であり、研究の前段階としての教養課程では確実に習得したいところです。実際、早稲田大学のCプログラミングの授業でもこれらの学習が一つのテーマとなっています。C言語はファイルやメモリ管理など、OSと密に関わっているため、このような部分を入り口としてOS（特にUNIX風なシステム）について理解を深めることができます。
また、C言語をある程度使いこなすためには、改めて指摘するまでもなくコンピュータのアーキテクチャに関する深い理解が必須です。よってC言語を学べば必然的にこういった知識も身につきます。

# 1からプログラムを組み立てる

プログラミング言語を身につける最短の方法は、実際にプログラムを書くことです。しかし、基礎的な内容(`printf`, `scanf`, 配列, 関数など)について一通りレクチャーされた後でも、自分で1からプログラムを書くとなると手が止まってしまう人が多いです。それを手助けし、何とか（間違っても良いので）プログラムを書いてもらう、というのが最初のステップになります。

## 問題を明確にする

「何をすればいいか分からない」という人は、まず問題をしっかり理解出来ていないことが多いです。何を入力し、どのような操作をして何を出力したいのかを明確にします。大抵の場合は、解きたい課題を学生自身に自分の言葉で説明してもらうことで、自ずと理解の曖昧さに気づくため、そこをフォローすればいいでしょう。

## 問題を分割する

多少複雑なアルゴリズムの場合は初学者の手に負えないことが多いので、サポートが必要になります。込み入った問題でも、複数の小さな問題に分割することで、学生が自力で解くことができる場合があります。
例えば、行列積を計算するアルゴリズムの場合、経験者であれば躊躇わずに3重ループを書き出すことができますが、これは初めての人にとっては難しいため、この問題を「行列A、Bから、A・Bの(i, j)番目の数値を計算する関数」「その関数を用い、行列A・Bの全ての値を計算するプログラム」のように分割してあげます。こうすることで、一つ目の問題は1次元配列の計算の問題に帰着できるため、「このアルゴリズムは前の授業で習った」などと過去の知識に結びつけることが出来ます。
また、問題の分割の仕方によっては、2重ループから抜けるといった若干面倒な内容を避ける(親のループを別に作った関数内に移動することで、`return`を用いて抜けることができる)ことができます(最近の授業では`goto`を扱わない)。このようなうまい問題の切り分け方を初学者が考えるのは少々むずかしいので、基本的に教えてあげる必要があります。

## コンピュータの気持ちになって考える

初学者が十分理解できる規模の問題であり、問題を正確に理解しているにもかかわらず、手が動かない場合は、「コンピュータの気持ちになって考える」ことが重要です。例えば、「1次元配列に格納された数値を合計する」という問題を考える場合、「1列の表に書き込まれた数値を合計したい時、人間ならどうするか」を考えてもらいます。一瞬見ただけで合計値が分かるという人は稀で、普通は(我々のようにLeft-to-Rightな言語の世界に生きる人は)左の数字から順に足していくでしょう。この「足していく」<=>」足す、という操作を、繰り返す」という部分はループ文に対応し、「それまで足した数値を一時的に記憶しておく」という行為は変数に対応するわけです。

# プログラムの間違いを直す

自力でプログラムを最後まで記述することができれば、第一段階はクリアです。しかし、初学者の書いたプログラムはまずコンパイルを通らないことも多いです。もちろんコンパイルエラーを読むことでエラーを修正できるわけですが、これも初心者には敷居が高いように思えます。エラー解消の定石は

- エラーが複数出た時は、最初（一番上）に表示されるものから順に確認する
- 行番号からエラーの発生した位置を探す
- 一つのエラーを修正したら次のエラーの修正に取り掛かる前に再度コンパイルを行う

ということを知っていれば、たとえエラーメッセージの内容を理解できなくてもある程度は自力でエラーを解決できるようになります。しかし、それでも解決できない場合、指導者が介入することになります。

## C言語の文法誤り

プログラミング言語には、プログラムの動作を中心とする見方(意味論)とプログラムの文法を中心とする見方(構文論)の2通りの見方があります。多くのプログラミングの授業では意味論から入っているので、もう一つの見方である構文論の理解が最初の障壁となると考えられます。

構文論にこだわって教えるのか、もしくは構文論を最低限にとどめるかによって指導者がどこまで教えるかが変わってきますが、私の場合、構文論はある程度プログラミングに慣れた段階で十分に教える必要があると考えています。具体的には、プログラムは「文」によって構成され、それが上から順番に実行されていくこと、文の種類や式について教えることから始めます。

## 実行時エラー

文法的に正しく、コンパイルを通過できた場合でも、プログラムの実行時にエラーが起こる場合があります。C言語の場合、その多くはメモリアクセスに関するものであり、主に以下のような部分を点検します。

- 配列へのアクセス時の添字が配列の範囲に収まっていること(ループ変数の範囲が正しいか、インデックス変数が初期化されているか、配列のサイズが正しいか等)
- 誤った値をアドレスとして解釈しメモリアクセスしていないこと(ポインタ変数にアドレス以外のものを代入している、等)
- `printf`のフォーマット文字列中の"%s"に対応する引数が配列を指していること
- `scanf`の各引数に変数のアドレスが指定されていること(文字列以外では変数の前に`&`をつけている等)

## アルゴリズムの誤り

プログラムを書く上で重要なことの一つに、「今書いているプログラムは何をするものなのか」を常にプログラマーが理解している、というものがあります。自分が今どのようなプログラムを書こうとしているのか、という点を学習者に説明してもらい、すべきことを正しく理解できているかを確認します。また、それを実現するためにどのような構文を用いればよいのかを考えてもらいます。

# さいごに

プログラミング教育は学習者にとって一定の負担が必要であると共に、指導者にも質が求められるため、消して簡単なものではないと思います。しかし、プログラミングは具体的に自分で手を動かした結果が、コンパイル・実行によって即座に確認でき、また練習を繰り返すことによってプログラミングの背後にある概念を理解できる、すなわち「具体」と「抽象」の両面を自然に学ぶことのできる科目でもあります。大学で学生は多くのことを学びますが、その最初の一歩として「学ぶ姿勢」を身につけさせるためにも、プログラミング教育は推進してゆくべきなのではないでしょうか。