↓PythonをSegmentation Faultで落とすのが最近流行っているようなので。
pythonを三行でセグフォらせる
pythonを2行でセグフォらせる
pythonを1行でセグフォらせる
Pythonを33文字でセグフォらせる
Pythonをctypesを使わずに1行でセグフォらせる
Rustを5行でセグフォらせる
主語(Python)をC言語に変えるだけで途端につまらなくなりますが、やってることは同じです。
コード
gcc 10.1.0で動作確認(警告は出ます)
*a;main(){*a=0;}
> gcc segf.c
segf.c:3:4: 警告: データ定義が型または記憶域クラスを持っていません
3 | *a;main(){*a=0;}
| ^
segf.c:3:5: 警告: 型がデフォルトの ‘int’ に ‘a’ の宣言内でなります [-Wimplicit-int]
3 | *a;main(){*a=0;}
| ^
segf.c:3:7: 警告: 戻り値の型をデフォルトの ‘int’ にします [-Wimplicit-int]
3 | *a;main(){*a=0;}
| ^~~~
> ./a.out
zsh: segmentation fault (core dumped) ./a.out
注意 現在のところ、C言語をセグフォらせる最小の文字数は5文字です。(下のコメント参照)
簡単な解説
これだけだとQiitaの記事要件を満たさないため、簡単に解説します。
まず最初に観るコードは↓です。
int main(void) {
int *a = 0;
*a = 0; // ←ここで落ちる
return 0;
}
C言語にはポインタという概念があり、これを用いるとメモリの好きな番地にアクセスすることができます。
int *a = 0
のところでa
という名前のポインタを宣言し、メモリ番地0
を代入します。2行目で*a
に値を代入すると、実際には存在しないアドレス1 にアクセスすることになり、CPU例外が発生します。
ショートコーディング
それでは、上のコードを短くしていきましょう。まず、C言語の規約上return 0
は省略できます。2また、返り値の型int
も互換性の観点から省略できます。(省略すると暗黙的にint
になります。)さらに、引数のvoid
も書かなくても動きます。
これらを踏まえると以下のようになります。
main() {
int *a = 0;
*a = 0; // ←ここで落ちる
}
ここで、ローカル変数a
はグローバル変数として定義してもよいです。さらに、グローバル変数として定義すると、(よほど特殊な環境でない限り)0
で初期化してくれるため初期化のための代入が不要です3。
*a;
main() {
*a = 0; // ←ここで落ちる
}
さらに、グローバル変数の型指定子int
も省略できます。これで冒頭のコードになりました。
おまけ。番地*(0p00000000)
は本当に存在しないか?
古典的なコンピュータにおいて、メモリはたくさんの情報を保管しておくことができる装置です。しかしながら、たくさんの情報をメモリに送ったり、メモリから受け取るためには、直感的には送受信したい情報の個数分だけ電線が必要になります。しかしながら、CPUの動作速度に耐えられるような細い電線をたくさん用意するのはコスト面で不可能なので、メモリアドレスという概念を導入します。これにより、一度に読み書きできる情報の個数(1Byteという)4は少ないながら、メモリアドレスを変更することによってたくさんの情報をメモリに保管することができます。
このように、メモリ大容量時代に欠かすことのできないメモリアドレスという概念ですが、これは0
から始まる正の整数で表されるため、実際には0
番地というアドレスは存在するのです。では、なぜセグメンテーションフォルトが起きたか、というと、OSが利用しているページングというCPUの機能が原因です。
コンピュータにおいてメモリアドレスはユニークです。よって、特に工夫しない場合、マルチタスキングOSにおいて複数のプロセスが同じメモリアドレス空間を所有することになります。この場合、メモリにアクセスする場合は、他のプロセスが利用しているメモリに間違ってアクセスしないように気をつけなければなりません。このような不便を解消するのがページングで、ページングを用いると各プロセスがそれぞれ別個の仮想アドレス空間を所有できます。このとき、仮想アドレス空間5と物理(メモリ)アドレス空間の間を変換する表をOSが管理しています。
よって、本来プロセスは存在するすべてのアドレスにアクセスできるはずです。しかしながら、実際はそうはいかず、仮想アドレス空間においてもOSが幅を利かしています。プログラムから利用できるメモリの範囲を制限しているのです。このような仮想アドレス空間の区画分けをメモリマップといいます。この記事にあるように、0番地の周辺は使用しないこととなっており(対応する物理メモリアドレスが割り当てられていない)、そのためセグメンテーションフォルトが発生します。6
おわりに
さらっと書くつもりが20分くらい掛かった。書かなきゃよかった。