#include <stdio.h> int main() { printf("Hello World!\n"); return 0; }
このC言語のプログラムを実行すると
Hello World!
と出ます。これで感激できるのはC言語を作った人と、環境構築に苦労した人くらい。大抵の人は「で?」と思うだけでしょう。 なぜ # で始まる? <stdio.h> はHTMLのタグとは違う? studio のつづり違い?など1行目から分からないことだらけです。
こちら、その謎に本質から迫るため開発した、x64版の超シンプルマシン語標準入出力ライブラリ stdio.asm を Windows 10で動かした様子。Mac/Linux/FreeBSDでも同様に動きます。
今日の記事はC言語を少しやったけど、なんだかスッキリしない人、現代PCの主力CPU、Intel/AMDのx86系x64マシン語を使ったOSの深い話が気になる人向けです。 まずは楽しくプログラミングでゲームを作ってみたい人や、マシン語の基本は、シンプル&コンパクトなパソコン、IchigoJamからスタートするのがオススメです。(IchigoJam入門動画、はじめてのマシン語)
C言語プログラミング、本当のはじめのいっぽはこちらです。
int main() { return 1 + 1; }
これを test1.c と保存し、コンパイルし、実行し、実行結果を表示すると(for macOS/Linux)
gcc test1.c ./a.out echo $? 2
1 + 1 という人間らしい言葉で、コンピューターに計算させることに成功!
細かく解説すると・・・。 こちら、整数(int)を返すmainと名付けた関数(数学の関数と似たもの)を作り、中身に「1 + 1」を返して(return)と記述したC言語のプログラムです。 mainという名前は特別で、プログラム起動時に最初に呼び出されます。 「1 + 1 」という人にわかりやすい言葉を、コンピューターが分かる言葉、マシン語へ変換してくれるのがC言語のコンパイラというツールです。 gccというコンパイラは、特に何も指定しないと a.out という実行ファイルができます。それを、実行。 プログラムが実行した結果をみるコマンド(echo $?)を使って、コンピューターが計算した結果「2」を得ることができた。というわけです。
ゲームのキャラクターを表示したい、コントローラー入力を使いたい、音を慣らしたい、いろいろ欲が出てきます。 そこで登場するのがライブラリ。コンピューターの仕様を隅から隅まで調べなくても、誰かが作ってくれた便利な部品を使うことで楽できます!
そんなライブラリの代表格が stdio.h(スタンダードI/Oの略) で定義されている標準ライブラリというものです。 基本的な文字の表示(output)と入力(input)のための関数が揃っています。 冒頭に登場した、printfという関数もそのひとつ。
int main() { printf("hello!\n"); return 0; }
上記のようにプログラムを変更してコンパイルすると、printfって何?とエラーになります。
printfを事前に定義する必要があります。stdio.h というファイルに定義があります。#include は指定したファイルを埋め込んでくれます。 ファイル stdio.h はどこにあるのでしょう?コンパイラが含めるために使うディレクトリが決まっていてその中のを使ってというのが、「<」と「>」で囲う意味でした。 その場にある自分で作ったファイルを含めるときは、ダブルクォートで囲みます。 HTML,CSS,BASIC,C,JavaScript,Pythonなど、コンピューター言語毎に、記号の意味は変わります。
文字の表示やサウンド出力などの手順は、Windows/macOS/LinuxなどのOS毎に違います。 C言語のコンパイラはCPUの違いを吸収してマシン語を生成してくれますが、手順の違いを吸収するのはライブラリの役目。
例えば、Macで文字を出力するには、レジスタrsiに出力したい文字列の先頭アドレスを、レジスタrdxに長さを、rdiに1を、raxに0x2000004をそれぞれ設定して、システムコール(syscall)を呼ぶことで、OSが文字を表示してくれます。(stdio_mac64.asm src on GitHub、cmd: nasm -f macho64 hello.asm -DMAC64; ld -lSystem hello.o -o hello )
mov rdi, 1 ; fd = stdout mov rax, 0x2000004 ; syscall write syscall
CentOS, Ubuntu, Debian, OpenSuSE, Arch Lniux, Fedoraなど、多くのLinuxではレジスタraxに設定するシステムコール番号を変えるだけでOKです。(stdio_linux64.asm src on GitHub、cmd: nasm -f elf64 hello.asm -DLINUX64; ld -e _main hello.o -o hello )
mov rdi, 1 ; fd = stdout mov rax, 1 ; syscall write syscall
FreeBSDでは他のLinuxと異なります。macOSはBSD系の子孫、この値に 0x2000000 数を足したものです。(stdio_bsd64.asm src on GitHub、cmd: nasm -f elf64 hello.asm -DBSD64; ld -m elf_x86_64_fbsd -e _main hello.o -o hello )
mov rdi, 1 ; fd = stdout mov rax, 4 ; syscall write syscall
Windowsでは直接システムコールを呼んではいけないことになっていて、dll(ダイナミックリンクライブラリ)を経由しての呼び出しとなります。stdio_stdout の初期化が必要で、レジスタの使い方や、スタックの使い方も違います。(stdio_win64.asm src on GitHub、cmd: nasm.exe -fwin64 hello.asm -DWIN64& link.exe /entry:_main /subsystem:console hello.obj kernel32.lib )
mov r8, rdx ; len mov rdx, rsi ; buffer mov rcx, [rel stdio_stdout] mov r9, stdio_bytesWritten push qword 0 call WriteFile
このようにOSによってそれぞれ使い方は異なりますが、名前と使い方に揃えれば、以後気にしなくてよくなります。stdio.asm で、環境によって使うライブラリを切り替えるようにしておきます。
%ifdef WIN64 %include "stdio_win64.asm" %elifdef MAC64 %include "stdio_mac64.asm" %elifdef LINUX64 %include "stdio_linux64.asm" %elifdef BSD64 %include "stdio_bsd64.asm" %endif
こうして準備をしておき、stdio.asm を include すれば、どのOSでも動くマシン語でプログラム「Hello World!」が、このようにシンプルに書けます。NASMでのファイルへの埋め込みのincludeは#ではなく%。 いろんな環境での動作確認はConoHaのVPSを使うと1環境1時間1円〜と、とても手軽です。
%include "stdio.asm" global _main section .rodata MESSAGE db "Hello World!", 0x0d, 0x0a LEN_MESSAGE equ $-MESSAGE section .text _main: call stdio_init mov rsi, MESSAGE mov rdx, LEN_MESSAGE call stdio_write mov rdi, 0 call stdio_exit ret
開発効率、速度、汎用性の向上や、特殊な用途への特化するためなど、さまざまなプログラミング��語が誕生し、使われたり、廃れたりしていますが、結局動いているのはこれらマシン語です。 基本を抑えておけば、余計な手間をかけることなく、楽しく楽に開発できてますますプログラミングが楽しくなります。
理解を深めるには、実際にやってみるのが一番です!Windows/Mac/Linux/FreeBSD、それぞれ環境に合わせた c-*.sh/bat、c-*-test.sh/bat を使って、実際に動かしてみましょう。(src on GitHub)
開発に必要なツールを設定しましょう。Windowsでは「Visual Studio C++ 2019 コミュニティ」、Macでは「Xcode」がそれぞれ無料で提供されています。
アセンブリ言語をマシン語化してくれるツール、アセンブラ「NASM」もオープンソース、無料です。
Visual Studio C++ 2019 コミュニティ、右側赤枠で囲んだ2つ、ビルドツールとSDKがあれば、ひとまず今回の開発は可能です。セットアップ後、コマンドプロンプトで hello-x64asm のディレクトへ移動し init-win.bat を動かし、開発ツールとNASMにPATHを通して、レッツトライ!
stdio.asm は、stdio.h が持つ機能の極一部。自分なりのライブラリや、プログラミング言語づくりにチャレンジするのもおもしろいですよ!
links
- taisukef/hello-x64asm: the first step of x64 64bit assembly programming on Windows/macOS/Linux
- C言語開発者「C言語は初心者にはお勧めできない」 エントリーにオススメBASICは、世界初のクラウド対応言語だった! IoTで起業家甲子園目指す、長岡高専チームメンタリング
- プログラミング言語は何から学ぶべきか? ロボットプログラミングゲームをIchigoJamでプログラミング! C言語の教科書「Springs of C」より
- 高専でなぜC言語を学ぶのか? IchigoJamマシン語生成プログラム c4ij で作る、C言語版かわくだり
- IchigoJamからのステップアップ - IchigoJam BASIC / Python3 / JavaScript / Java / C言語 対照表
- ハンドアセンブルで高速計算! RISC-V、RV32ICエミュレーターのC言語実装
- マシン語対応 IchigoJam web、気軽にハンドアセンブルして遊べます!
- OpenCL/C言語 GPUプログラミングはじめのいっぽ on Mac
- わずか16KB! WebAssemblyで動くミニC言語オフラインコンパイラ&インタプリタ webci0
- 深いプログラミング言語学習に最適! 512行のC言語コンパイラ ci0 を使ってみました
- 地味なC言語がなぜ楽しいのか?