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:型の一致を求められる/られない

今回は話題を説明のしやすさの都合上、変数の型と限定する。

型ってなんぞ?

実はメモリに変数というあるデータを保存しておく場所を用意する際、何バイトを確保しておく必要があるのか、どういった種類(整数なのか、小数点なのか、あるいは文字なのか)を扱う変数なのかという情報が必要なのだ。

えっでもJSは必要ないじゃん?
プログラマが型を指定するか、JS側で型を指定するかの違いである。
前者は静的型付け、後者は動的型付けの言語と呼ばれたりする。
https://note.com/tasting/n/na6152e3aac51

なのでC言語で変数を宣言する際には

int a;

と宣言すると「あ、これはマイナスを含む整数のデータ、それも2バイト以上のデータを扱うことができる変数なんだな!」という扱いになり

unsigned int b;

と宣言すれば、「これは正の数のみの整数のデータ、それも2バイト以上のデータを扱うことができる変数なんだな!」という扱いになる。

マイナスの数を扱えるか扱えないかまで気にして指定しなきゃいけないのかよ!?と思うかもしれない。ここら辺が「お堅い言語」みたいな感じを受けやすいのかと思う。

2バイト以上って表現なんやねん。
C言語の、特にint型のサイズは環境によって2バイトだったり4バイトだったりする。2バイトより小さいことはない。
それの一体何が大事なの?
端的に言うと扱える数値の範囲が変わる。
が、普通にC言語をやる分にはそこまで気にしなくてよい。
だいぶ前にC言語初心者向け記事にてややこしくなると思い、思い切って4バイトと決め打ちで書いたところqiitaの民に「僕の環境は違いますけど???」と凄まじく嚙みつかれたので今回は上記のような表現とした。
ちなみにやさしいCも明解Cも4バイトと書いていた気がする。うろ覚え。
さらにおまけ(知ってる人向け)。
ちなみに明解Cの著者は自らのサイトでcharすら8ビットとは限らないとかなり細かいことまで主張されている。
実際、0から255、-127から127までの範囲を保証できれば良いので9bitのcharがあってもANSI違反ではなく、しかも実在するらしい。8進数で考えると3bitずつの方がキリがいいので9bitになったとかなんとか。
あと-127じゃなくて-128では?と思ったそこの方、規格的には-127までとなっている。負数の表現に2の補数を使えば8bitで-128まで表現できる(1の補数だと-0、+0が発生するのでー127から+127まで)のでほとんどのコンパイラは2の補数を採用かつ-128まで表現できるものが多いとは思うが…。

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;

というのは実は文法的にはダメなのである(コンパイラによってはエラーにならない場合もあるが)。

さっきからちょいちょいコンパイラって単語が出てくるけどコンパイラって一体なんなん。
プログラミング言語というのは英語だらけだしワケのわからない記号やらエラーやら出てくるがこれでも一応人間向けの言語である。
なのでPC改め機械にC言語で書いたコードをはいどうぞと渡してもそのままでは機械的には「読めないよ!」という代物なのだ。
なのでC言語の場合は機械語への翻訳ソフト(コンパイラ)を通して翻訳(コンパイル)することで機械が理解できる命令になる。で、この命令を実行することで機械が動作するという仕組みだ。
いやでもJSだと普段コンパイル?とかしなくても上手くいってるじゃん。
JSの場合は翻訳と翻訳した命令を実行するインタプリンタ方式の言語になっており、いちいち翻訳して実行して…という手間がいらない言語になっている。またブラウザやnode.jsで実行する際には既にインタプリンタに相当するものがブラウザやnode.jsに含まれているためわざわざ自分で用意する必要がない(逆にCを動かしたい!となった際にはコンパイラから用意する必要がある)。
https://www3.cuc.ac.jp/~miyata/classes/prg1/02/2way.html

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から卒業できるんだろうなと思う。


この記事が気に入ったらサポートをしてみませんか?