前置き
大学での初心者に対するプログラミング講義ではC言語を使うべきでない
というお話がありまして、「たしかになぁ」と思いました。
で、Qiitadon にいろいろとトゥートしてまして、なんか微妙に語弊がある部分もあったりしたのでまとめてみよう、という話です。
結論
- C言語はたしかに初学者には難しい。
- プログラミング講義ではプログラミングに特化した言語を使用すべき
- C言語はコンピューターを理解する意味では適切。でもコンピューターの動きの基礎を理解してからやるべき。
ということです。
Qiitadonのトゥートとしては以下のあたりです。
「プログラミングを覚える」と「コンピューターの動きを覚える」は別問題だとは僕も思う。少なくともC言語を入門にす必然性は無いだろうし、カリキュラムの問題なんだろうなあ、とか考えたりはした。
20年ならC言語一択だったけど、今ならPythonだよねぇ。
昔はそれこそ、「覚えやすくてつぶしが利く」からC言語だったわけでしょ。
その位置にあるのは、今ならPythonかなと。
アセンブリ言語を学習すればポインタはすぐわかるよ、という話
C言語の最大の難関といえば、ポインタですよね。
これは、いきなりC言語から始めるのでわかりづらいので、アセンブリ言語、機械の気持ちになった言葉で考えればすぐわかる話なのです。
すごく簡易的な例を挙げてみます。
void main() {
int a;
int b;
b = 1;
a = b;
}
こんなC言語のプログラムがあったとしましょう。
これを、gcc -S test.c
とやります。
-Sは、
-S Compile only; do not assemble or link
という意味です。C言語をコンパイルはするけどアセンブラで機械語にするところまではやらないよ、と。
つまりアセンブリ言語で出力されます。
出力される拡張子は.sです。
.arch armv6
.eabi_attribute 27, 3
.eabi_attribute 28, 1
.fpu vfp
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 2
.eabi_attribute 30, 6
.eabi_attribute 34, 1
.eabi_attribute 18, 4
.file "test.c"
.text
.align 2
.global main
.type main, %function
main:
@ args = 0, pretend = 0, frame = 8
@ frame_needed = 1, uses_anonymous_args = 0
@ link register save eliminated.
str fp, [sp, #-4]!
add fp, sp, #0
sub sp, sp, #12
mov r3, #1
str r3, [fp, #-8]
ldr r3, [fp, #-8]
str r3, [fp, #-12]
sub sp, fp, #0
@ sp needed
ldr fp, [sp], #4
bx lr
.size main, .-main
.ident "GCC: (Raspbian 4.9.2-10) 4.9.2"
.section .note.GNU-stack,"",%progbits
はい、キモいですね
実際には、「main:」の前までは設定をしているだけなのであまり意識する必要はありません。
@はコメントなので、実際には10行程度のプログラムです。
続いて、ポインタを使ったプログラムも用意します。
void main() {
int *a;
int b;
b = 1;
a = &b;
}
アセンブリ言語としてはこうです。
.arch armv6
.eabi_attribute 27, 3
.eabi_attribute 28, 1
.fpu vfp
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 2
.eabi_attribute 30, 6
.eabi_attribute 34, 1
.eabi_attribute 18, 4
.file "test2.c"
.text
.align 2
.global main
.type main, %function
main:
@ args = 0, pretend = 0, frame = 8
@ frame_needed = 1, uses_anonymous_args = 0
@ link register save eliminated.
str fp, [sp, #-4]!
add fp, sp, #0
sub sp, sp, #12
mov r3, #1
str r3, [fp, #-12]
sub r3, fp, #12
str r3, [fp, #-8]
sub sp, fp, #0
@ sp needed
ldr fp, [sp], #4
bx lr
.size main, .-main
.ident "GCC: (Raspbian 4.9.2-10) 4.9.2"
.section .note.GNU-stack,"",%progbits
ざっくり、相違点だけ抜き出してみますと、
mov r3, #1
str r3, [fp, #-8]
ldr r3, [fp, #-8]
str r3, [fp, #-12]
と、
mov r3, #1
str r3, [fp, #-12]
sub r3, fp, #12
str r3, [fp, #-8]
になります。
ここだけ解説してみましょう。
前提としては、
- 最初の英字(3文字)は命令
- 命令毎に受け取れる項目数は違う
- CPU毎にも使える命令はまちまち。
- ARMの場合は[ARM コンパイラ armasm リファレンスガイド] (http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0802aj/Cigcceih.html)あたりに載っているかと。
とします。
こんな説明じゃわからないとは思いますが、正直多くの人には必要ないので壮大に割愛します。
では、相違点の解説です。
mov r3, #1
数字の1をr3にコピー(mov)します。
このr3というのは、レジスタと呼ばれるもので、CPUが大抵持ってる「記録できる場所」です。
str r3, [fp, #-8]
r3にコピーした値を、fpの8バイト手前に代入します。
ldr r3, [fp, #-8]
今代入した値をもういっかいr3に読みとります。
str r3, [fp, #-12]
r3にコピーした値を、fpの12バイト手前に代入します。
ということをやっているわけです。
対して、ポインタを使用した場合はこうなります。
mov r3, #1
数字の1をr3にコピー(mov)します。
str r3, [fp, #-12]
r3にコピーした値を、fpの12バイト手前に代入します。
sub r3, fp, #12
fpアドレスから12バイトを引いたアドレスをr3に代入します。
str r3, [fp, #-8]
r3にコピーした値をfpの8バイト手前に代入します。
つまり、
今代入した値をもういっかいr3に読みとります。
か、
fpアドレスから12バイトを引いたアドレスをr3に代入します。
かの違いなわけです。
ただ変数を代入したプログラムでは、「値を読んで値を書き込む」という処理だったのが、
ポインタを使うと「アドレスを読んでアドレスを書き込む」という処理に変わるわけです。
そのあたりの勘所さえつかめれば、C言語のポインタも理解できると思いますし、
C言語でポインタを使うときは「メモリの気持ちになって考える」ことが大事だよ、ということがわかりますね。
以上のレベルのことまで教えた上でC言語を学習するのと、いきなりプログラミング講義でC言語を使用するのとでは理解度も変わってくるのではないでしょうか。
世の大学講師には、是非ともPythonでのプログラミング講義を実施していただきたいと思います!(唐突)