JSとC言語を比べてみる
この記事は
N予備校プログラミングコース Advent Calendar 2021 19日目の記事です。
・書いた人、魏晋南北朝時代が大好きなプログラミングにわか。
JSもにわか。Cはちょっとだけやってた。
・書いた動機、座談会の時などちょいちょい「お堅い言語」みたいな話が出てきますがJSしかやったことのないN予備生だとピンと来ない人が多いのでは?と思ったのでお堅い(?)言語筆頭のC言語とJSを比べてみる。
JSとC言語の違いその1:アドレスをいじれる/いじれない
このアドレスに直に触れる、触れないというのはC言語(あとC++などC派生の言語)とJSなどを始めとしたその他のプログラミング言語を分かつ特徴ではないだろうか。
そしておそらくC言語最大のメリットであり最大のデメリットでもある。
その前に。
ちょっと待ってよアドレスって?
例えばPCが何かデータを保存する時、そのデータは一体どこに保存しているだろうか?
……メモリかな?
というのはなんとなくイメージがつくのではないだろうか。
とはいえメモリと一口にいってもやれストレージだのレジスタだの種類があってややこしくなるので、ここではひとまずデータはメモリというデータを保存しておく場所に書き込んだり読み出すものとする。
で、このメモリにデータを保存する時、PCはアドレスというメモリに振られた通し番号を目印にデータを書き込んだり読み出したりしているのだ。
どういうこと?
例えばディズニーランドやらのだだっ広い駐車場、あれの車を停める場所や区画に番号が振られていなかったら一体どうなるだろうか?
一度停めたら最後、どこに停めたっけ?と迷子になるのは間違いない。
というのはあくまでたとえ話ではあるが、PCも何の目印もなくデータを書き込んだり読み出したりはできないということだ。
だからアドレスという番号(番地と言ったりもする)を使ってメモリの1000番目にAというデータを保存しよう!とか、2000番目からデータを取ってこよう!といった処理をしているのである。
へー。初耳ー。でもよくわかんなーい。
そのくらいで全然良いと思う。
というかそもそもそういったアドレスというものを意識しないでプログラミングができるというのがJSなどのプログラミング言語の長所である。
では改めてアドレスを直に参照できる/できないメリットデメリットについて触れていこうと思う。
・C言語:アドレス、メモリに振られている番号を直に指定してデータを書き込んだり読み出したりできる
#define TEKITOU (*(unsigned char *)4000)
TEKITOU = 100;
これで直に4000番目のメモリに対してバイト単位で100というデータを書き込むという操作が書ける。
(バイト単位ってなんぞ?という話は一旦置いておく)(またそもそもソフト的に書き込み不可である領域の場合もあるので必ず100が書き込まれるというわけでもない。あくまで書き込むという処理を記述できた、というのが正確である)
・JS:基本的にはアドレスを直に指定してデータを書き込んだり読み出したりはできない
…と思う。実はこの環境を使えば!みたいなのがありそうで何とも言えない。
状況整理。
アドレスを直に指定できるメリットとは?
→ソフトからハードウェアに直に触れる
それの使いどころっていつだよ?
というのはもうJSや普通のC言語をやっている人間からするとかなり想像しにくいと思うのでふんわりとしか書かないがマイコン(家電とか車とかによく搭載されているCPUやメモリ、周辺機能がオールインワンで積まれてたりそうじゃなかったりするチップ)の周辺機能をいじったり、自分でOS作ったりOSいじったりするくらいじゃないだろうか?
アドレスを直に指定できるデメリット
→分かりにくい
いやもう分かりにくい。メモリのイメージからしてつかみにくい。Cでつまずく原因ナンバーワンかもしれない。
あとは書き込んではいけないメモリに間違って書き込んじゃったー!的な操作をしてもソフト的なエラーとしては検出できないという問題もある。
小まとめ
・アドレスをいじれる/いじれないという違いがある
以上のことはそもそもハードをいじる必要があるかないかに起因するのではないだろうか。
C言語はそもそもOSを書き換えるために生まれた言語であるためハードを操作できる必要が求められた。
JSはブラウザに動きをつけるために生まれた言語であるため、そもそもハードを操作する必要性が薄い。
こういった生まれの違いはかなり影響していると思われる。
この時点でだいぶ重たい話題で息切れしてきたがせっかくなのでもう一つだけ触れようと思う。
JSとC言語の違いその2:型の一致を求められる/られない
今回は話題を説明のしやすさの都合上、変数の型と限定する。
型ってなんぞ?
実はメモリに変数というあるデータを保存しておく場所を用意する際、何バイトを確保しておく必要があるのか、どういった種類(整数なのか、小数点なのか、あるいは文字なのか)を扱う変数なのかという情報が必要なのだ。
なのでC言語で変数を宣言する際には
int a;
と宣言すると「あ、これはマイナスを含む整数のデータ、それも2バイト以上のデータを扱うことができる変数なんだな!」という扱いになり
unsigned int b;
と宣言すれば、「これは正の数のみの整数のデータ、それも2バイト以上のデータを扱うことができる変数なんだな!」という扱いになる。
マイナスの数を扱えるか扱えないかまで気にして指定しなきゃいけないのかよ!?と思うかもしれない。ここら辺が「お堅い言語」みたいな感じを受けやすいのかと思う。
JSのターン。
JSの場合は変数といったらconst、再代入不可能な変数かlet、再代入可能な変数という二種類しかない(varは脇に置いておく)。
let a = 10;
const b = 10;
はもちろん
let a = function kansu(){console.log(`Hello!`)};
a();//Hello!
なんてこともできる。数字だろうが関数だろうが入れ放題。めっちゃ自由。
C言語のターン。
int a;
これはint型のデータ、つまるところ整数を扱うことができるよ!という変数aである。
int * b;
そしてこれはint型のデータのアドレスを扱うことができる変数bである。
そしてC言語で単に
10
と書いた場合はint型の10という意味になる。
なので
int a = 10;
はOKだが
int * b = 10;
というのは実は文法的にはダメなのである(コンパイラによってはエラーにならない場合もあるが)。
Cをやってない人からすると「は?」という感じだと思う。
「なんで?」
「っていうかint とint *で何が違うんだよ?」
ごもっとも。
でもC言語だと別物という扱いになるのである。
ざっくりまとめるとC言語は左側にある変数の型と右側にあるデータの型の一致が一応は求められる言語なのだ。
だから上記の例は
int * b = (int *)10;
と、こうすれば文法的にはOKである。なんとなく揃ってるなー感が伝われ…伝われ…。
またJSと違ってCでは関数は普通の変数に代入することはできない。ポインタ型の変数が必要になる。
ちなみに先ほど出てきたこれ、関数を変数に入れてから呼び出すということを
let a = function kansu(){console.log(`Hello!`)};
a();//Hello!
試しにCで書こうとするとこんなことになる。
#include <stdio.h>
void kansu (void);
int main(void){
void (*a)() = kansu;
a();//Hello!
}
void kansu (void){
printf("Hello!");
}
書いていて分かりにくいな~と改めて思った。
小まとめ
・JSは型を気にせず変数やデータを扱うことができる。
・Cは変数と扱うデータの型の一致を求められる。
おそらくJSからプログラミングに触れた人的にはアドレスって概念だけでも「は?」と思うのにさらに型なんて概念ぶっこまれて倍で「は???」という感じだと思う。
まああくまで余談程度に、世の中にはこういう型という概念があったりメモリという概念を気にかけなきゃいないしちめんどくさい言語もあるんだなー!へー!というのをふわっと知識として知っておくだけでも良いのかなと思った次第。
あとがきという名の反省
なんかもうちょっと綺麗にまとめられたらよかったなとここまで書いて思った。おまけ話が長すぎた感。
あとお堅いとは言われるけどぶっちゃけCはアドレスいじれるとか危ないことできるわりにポインタ周りの型チェックは結構ゆるゆるだし処理系依存や未定義動作が多いしでなんか本当、書くの面倒なわりに安全性の低い言語だなあとこの記事を書いて改めて思った。硬いというか、古いというか。
のわりには車載、医療機器、はては宇宙開発までいわゆるwindowsのようなOSのない環境では未だにバリバリ現役なので一体人類はいつになったらCから卒業できるんだろうなと思う。