899 Views
April 29, 23
スライド概要
部活用に作成した資料です。
※ 難しい内容なので飛ばしても大丈夫です
コンピュータはなぜ計算できるのか、C/C++のポインタとは何か、といったことを解説しています
知っておくと後でめちゃくちゃ役立つはずです
プログラミング講座 #7 コンピュータを「理解」する @ZOI_dayo
(注意) 今回は、「パソコンはどうして計算できるのか」「どんな構造なのか」から解説します なので、今までのコードを書くソフトウェア側ではなく、ハードウェア側の話が多いです ここを理解すると、コードがより鮮明に見え、他の言語や技術も爆速で習得できるようになるはずです、 全分野に役に立ちます ただ、これを理解していなくてもコードは書ける、というのは事実です なので、一旦読んでみて、理解できなければ数週間寝かせて再読、でいいと思います
コンピュータとは? コンピュータ、というのはあくまで「電子計算機」、つまり電卓の巨大版に過ぎない (ゲーム機も、スマホもみんなそう) で、電気で動いてる 中に人がいないのになぜ計算できるのか? ← 実はめっちゃ不思議
デジタル信号 PCの中には色々パーツがあって、それぞれが電気でやりとりしてる (当たり前) 通信するときに、音みたいに「強さ」を使って信号を送った場合、ノイズが出ると困る (例えば0なら「a」、0.1なら「b」、...みたいにして、通信途中で0.1が0.05になったら?) これを防ぐには、0と0.1みたいな隙間をできるだけ広くして、あとで四捨五入すればいい なので、0とMAX(5Vなど)だけで表すことにした
デジタル信号 0と5Vだけで表すことにしたので、線に電気が流れてるか、流れてないか、だけで考える 流れている=1、流れていない=0とすると、0と1を並べて2進数で数字を表せる?! (例えば導線ABCがあって、A=5V、B=0、C=0なら100(2)=4と数字を表せる) じゃあ足し算はどうする?
論理回路 このままではどうしようもないので、(どうやるかはあとで考えるとして、) いくつか論理回路というものを使っていいことにする 「1を0に、0を1に変える」NOT回路、 「11を1に、01,10,00を0に変える」AND回路、 「11,10,01を1に、00を0に変える」OR回路 このくらいあればなんとかなるかな?
足し算をしてみる 2進数で3桁+3桁の計算を考える 例えば001+100=101、011+010=101、011+011=110、... これは難しいので、まずは1桁+1桁から 0+0=0、0+1=1、1+0=1、1+1=10 これも難しいので、1桁+1桁の「1桁目」だけを考えることにする 0+0=0、0+1=1、1+0=1、1+1=0
足し算をしてみる 0+0=0、0+1=1、1+0=1、1+1=0 これって、「基本OR、ただし1と1の時は0」となってる つまり、これで1+1以外は正しい答えを返すわけですね
足し算をしてみる 1+1、つまりANDが1の時に結果を変えたい のでちょっと変える (自分で書くのはほぼ無理なので、確かに結果合うな、と納得だけしてください )
足し算をしてみる さっきの回路を「半加算器」と呼ぶ(繰り上がりを考えてないからね) ここから繰り上がりを考えたいけど、こんな感じでいい(これを全加算器と呼ぶ) (これも納得だけでOK)
足し算をしてみる 難しいから説明を省略するけど、実はさっき使ったAND、NOT、OR、...は 「トランジスタ」という半導体で作れる! つまり...電子回路だけで計算ができる、ということ!!!!! PCのCPUという部品は大量のトランジスタの塊で、 最近のものは数十億のトランジスタでできてるらしい (すごいね〜〜)
他の部品 これで、回路を組んだら、N桁+N桁の足し算はできる、ということはわかりました 4桁ならこんな感じですね (ちなみに、掛け算や割り算も全部作れます) ただし、我々がパソコンを使う時、 「この数式だからこんなふうにパーツを繋いで...」 と、いちいち組み替えているわけがないですね
他の部品 なので、一つのパーツで、組み替えなしで、 「入力を変えるだけで足し算したり掛け算したりできる素晴らしいパーツ」 が登場しました 今日、PCに「CPU」として 搭載されているものです ありえないほどの電極が ありますね〜〜〜
他の部品 しかし、パソコンを使うときに、この電極に電線をぽちぽち当てて使うはずはないです なので、キーボードとこれをうまいこと繋ぐパーツが必要です また、ゲームなどをするために、「計算結果を保存しておく場所」「画面を表示する場所」 などいろいろ必要です
他の部品 それがこんな感じです 真ん中のファンがCPUクーラー、 その奥にCPUがあります(発熱する) その右側の縦縞がメモリと呼ばれる 計算結果をメモる場所、 ファンの下がGPUと呼ばれる 画面表示を担当する部分、 それらの奥の基板がマザーボードです
他の部品 このうち、プログラミングで最も意識しないといけないのは「メモリ」です (マザーボードは部品をつなげてるだけ、CPUやGPUは複雑すぎて理解はほぼ不可能です) いろいろありますが...「メモリはデータが一列に並んでいる」ことが最も重要です
メモリ メモリは計算結果を保存しておく... つまり、プログラミングにおいては「変数を保存しておく」役割があります (実行中のコードを置いたりもしますがとりあえず放置です) ただし電気を切るとデータが失われるので注意です 最近の言語ではメモリに直接触れないものもありますが、 ここではC++という言語で説明します
メモリ メモリは、端から0番地、1番地、...と一列にマスが続いていて、 それぞれのマスに0か1を入れて覚えておくことができます (いつも0から始まるわけではなく、適当なところから始まります) 例えば、int a = 0;としたとき、こんなふうに 「0番地~7番地を変数aの値とする」とするわけです(実際は8個ではないですが)
メモリ a = 5;としたときは、5(10) = 101(2)なので、こんなふうになります (まあ、前後どちらが一の位になるかはPCによって変わりますが...) 代入が普通に上書きすることでできる、ことが分かりましたか?
メモリ ただ、一体メモリの何マスを一つの数として扱うか、というのが問題になってきます 大きな数字を使いたい時も小さくていい時もあるので、いくつかの種類が用意されています 例えば普通使うintは32マス、long longという(2単語で1つの名前です)ものは64マス、 charは8マスです(数字として扱えるの思い出してください) これを考えれば、どのくらいの数字なら入るのかわかると思います (2^32、とかですね!)
メモリ 実際には、±どちらも表現したいので、絶対値の範囲は半分になります intは32マスなので、±(2^31)、つまり±2147483648(約21億、2x10^9)です 1くらいズレるかもしれないのでギリギリはやめましょう つまり、long longは10^18くらい表現できるわけですね、 まあintで不足することはほぼないので基本int、困ったらlong longにしましょう ちなみにこのようなマスはbitという単位で数えるので、intは32bit整数型と呼ばれます
メモリ 他の型、つまりstringやvectorなども、全て一列の0と1で表されていて、メモリに配置されています 特にvectorは、メモリ上に一列に並んでいるのが特徴です 例えば4要素のvector<int>なら、4*32=128bitの長さで一列になっているわけですね (実際はもう少し複雑だけど)
文字コード stringの場合、まずは各文字を数字に直さなければ2進数に直せません 例えばA=1、B=2、というふうに決めるわけです 英語と記号が少しだけなら128文字で足りるので、うまく割り当てた「ASCII」と呼ばれる 変換表があります(これがcharを数字として見たときの値です) ただ、「あ」といった文字を考えると大変なことになります このために変換表がいくつか作られたのですが、よく使われているのは Unicodeと呼ばれるものです (100万文字くらい入れられます)
文字コード しかし、Unicodeの適当な番号のまま扱うとデータ量が増えてしまうらしく、 文字→Unicodeの番号→実際の値と、2回目の変換があるらしいです このときの変換方法の一つがUTF-8です 文字化け対策だ、とか、ファイルはUTF-8で書け、と言われたことはありませんか? 他の変換として有名なのはShift-JIS(Windows標準)なのですが、解釈の仕方が違うため、 UTF-8で「あいう」を表す2進数はShiftJISで「縺ゅ>縺」を表しているように見えるのです
ポインタ C++は古の言語なので、変数がどこのメモリアドレスにあるのか、を知ることもできます 変数aに対して、&aと前に&をつけると、メモリアドレスを入手できます (C++では、メモリアドレスの情報のことを「ポインタ」と呼びます) また、ポインタからそこに入っている情報をもらうには、*pとすれば良いです この「ポインタ」は、CやC++において最も難しいと言われている概念です ただ、メモリが一列に並んでいて、変数の保存場所である、ということがわかっていれば 問題なく理解できると思います!
ポインタ int*型の変数aに対して、 *a = 3; みたいな代入をすることも可能です これはイメージで言えば、*aが3になるようにする、ということなので、 aは「3という値が入っている変数、へのポインタ」になるわけです なおint * a = 3やint* a = 3など、アスタリスクとスペースの位置はどこでも良いです
(余談) Cの配列について 実は、C言語では他の言語のようにint a[10]というような配列を作ることができます ただし、扱いが面倒くさいのでこれまではvectorを使ってました 何がめんどくさいかというと、int a[10];と書いた時、aは「int型のポインタ型」、 つまりint *a;と書いた時と同じ型になるのです! どうして...? と思うかもしれませんが、ここまでの知識で理解できます!
(余談) Cの配列について まず、配列a (型はint*) について、これは「先頭要素を指し示すポインタ」になっています 配列a (型はint*) が与えられて、[5]を取得しろ、と言われた時、あなたならどうしますか? ここでわかっているのは、int型が並んでいるということと、先頭要素の場所、 そしてindex=5、つまり6番目をほしい、という情報だけです ならば、先頭要素の場所にint型の長さ(=32bit)*5を足して、 その値を貰えばいいのではないでしょうか? 先頭アドレスをpとすれば、*pが[0]、*(p+32bit)が[1]、*(p*64bit)が[2]、...なのです
(余談) Cの配列について これで、なぜ配列がポインタなのかがわかると思います 「先頭位置と内容の型だけあれば十分だから」ですね そして、なぜ配列が[0]から始まるのか、もわかったでしょう +0が先頭の要素、+1が2番目の要素、...だからですね C言語の歴史は古く、現代のほとんどの言語に影響を与えています そのため、ポインタなどないモダンな言語でも、配列は0始まりなのです! (影響力すごいね)
(余談) Cの配列について ただし問題があり、先頭のポインタのみを保存しているので、要素数がわかりません int a[10];について、sizeof a で40、sizeof a[0] で4が取得できる(サイズはbyte=8bit)ので、 sizeof a / sizeof a[0]でサイズが取得できるにはできるのですが、 なんと、aを変数の引数などでコピーしたとき、先頭アドレスしかコピーされないのです ! これでは、sizeof aは配列の長さを返さなくなります困りました... なので、配列の長さも引数で渡すなどしなければいけないのですが、面倒臭いので vectorやarrayを使うことがほとんどです
ストレージ ストレージ、つまりパソコンでハードディスク(HDD)とかSSDとか言われてるやつです これもデータを保存するのですが、なんと電源を切ってもデータが残ります ! なのでゲームを入れてPCを再起動してもインストールされたままなんですね! ただし、読み書きがメモリよりかなり遅いので注意です
ストレージ 大体、メモリは4~16GBくらい、ストレージは256~1024GBくらいの容量のものが多いです 大量の情報を保存しておきたい場合はストレージに入れればいいですね つまり、あなたが持っているゲームもアプリも全ては一列の01でしかないのです
(発展) 機械語とアセンブラ 先ほど、「CPUは理解は不可能」と言いましたが、プログラムと電子回路の境目あたりならまだ解説で きるかもなので書いときます まず、CPUの電極にどう電気を流せば良いか、は不明なので、intelに入社して考えましょう CPUの中では数GHz、つまり大体10^9回くらい命令が実行されているようです では、プログラムはどのように実行されているのでしょうか?
(発展) 機械語とアセンブラ CPUに対して「int a = 1; …」とか言っても全く理解できません なので、CPUに対してはCPU語で話さないといけません CPU語は普通のプログラミングと全く違った世界なのですが、変数が 8つくらいしかなくて四則演算くら いはできる、と思ってください CPU語は機械語と呼ばれ、CPUによってまちまちです (と言っても最近のPCはほぼx86と呼ばれるシリーズです) (AppleのMシリーズやラズパイ、スマホなどはarm64と呼ばれるものだったりします)
(発展) 機械語とアセンブラ で、機械語を読むのはCPUなのですが、CPU君は余裕がないのでいちいち「えーっと、『変数aに1を足 せ』...なるほどじゃあこのピンにこうして...」みたいな気遣いはないです 人間が0と1で書いてやる必要があるのです こんなのは普通は無理です、0と1を一瞬間違えたら全く違った命令になります DNAを手動で組み立てろと言ってるようなものです絶対無理です
(発展) 機械語とアセンブラ そのために生み出されたのが、機械語とプログラミング言語(高級言語と呼ばれます)の 間に位置する「アセンブリ言語」、そしてそれを機械語にする「アセンブラ」です これなら、「MOV」(値の代入)や「CALL」(関数呼び出し)、「JMP」(コード内移動)など 01よりは少し書きやすいようになっています ただし人間用ではないので、書くのはお勧めしません
コンパイル さて、読んでいなくても大丈夫なのですが、先ほど軽く解説した「機械語」がプログラムの正体なのです で、あなたが今書いているC++はただのテキストファイルです つまり、この文字列を機械語に直す必要があります これが、「コンパイル」と言われる操作です
コンパイル あなたが今書いているのがmain.cppだとすると、このファイルに書いた通りの動作をする機械語がほ しいですね(これで作られる機械語ファイルを実行可能ファイルと呼びます) C++の場合は簡単で、環境構築さえ終わらせれば、 g++ ./main.cpp で、今のmain.cppを機械語に変換したa.outというファイルができます これの実行は、./a.out です、簡単ですね (main.cppを編集した後はg++を実行し直すのを忘れずに!)
OSとは 最後に、OSとは何か?ということだけ解説しておきます OS (Operating System)は、ハードウェアを「ならす」という重要な役割を担っています どういうことか? というと、初期のコンピューターは、1台1台が特注品で、 アプリはそのコンピューターでしか動かなかったのです その後、CPUの設計がある程度統一され、複数台で同じように動くようになったのですが、 まだ問題は残っていました
OSとは 例えば、アプリ一つ一つが勝手にメモリを使えたとすると、他のアプリが使っている場所に勝手に情報 を書き込んでしまうかもしれません 「この範囲はこのアプリが使ってるから他のアプリは触るな」、と指示を出してくれる 存在が必要です! それだけではありません、メモリが4GBのPCもあれば、8GBのPCもあり、CPUの速度にも違いがあり ます また、アプリが「座標(0,0)に赤色を表示...」などやっていては大変です これらを全て解決するのが「OS」なのです
OSとは OSは、全ての周辺機器(CPUやモニター、USBメモリなど)と繋がっており、アプリとのやり取りを中継し てくれます つまり、アプリから見れば、「ウィンドウを作成」とOSに命令するだけで良いのです その裏で、OSはメモリを管理し、CPUやGPUを動かし、モニターに信号を送っているのです OSのおかげで、アプリはカーソルを動かすのにマウスでもトラックパッドでも気にせず使うことができる のです SSDかHDDかUSBメモリなのかを気にせず、同じように使うことができるのです
OSとは このように、OSが全てのデバイスを仲介してくれているので、プログラムからはデバイスの違いを気に することなくコードを書くことができるのです 代表的なOSとしてはWindowsやmacOS、Linux、あとiOSやiPadOS、Android、ChromeOS などですね
まとめ 今回はコンピュータの仕組みからメモリ、ポインタなどまでの話でした ここは知らなくてもコードを書くことはできる知識なのですが、やはり知っておくと 「なんでこんな仕様なんだ?」という疑問が解決することが多いです コンピュータがどれだけ進歩しても、コンピュータの原理は変わらないはずです これから50年くらいは使い続けられる知識だと思うので、ぜひ知っておいてください !
終わり