Help us understand the problem. What is going on with this article?

C言語で16文字でセグフォらせる

↓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

int *a;
main() {
    *a = 0;  // ←ここで落ちる
}

さらに、グローバル変数の型指定子intも省略できます。これで冒頭のコードになりました。

おまけ。番地*(0p00000000)は本当に存在しないか?

古典的なコンピュータにおいて、メモリはたくさんの情報を保管しておくことができる装置です。しかしながら、たくさんの情報をメモリに送ったり、メモリから受け取るためには、直感的には送受信したい情報の個数分だけ電線が必要になります。しかしながら、CPUの動作速度に耐えられるような細い電線をたくさん用意するのはコスト面で不可能なので、メモリアドレスという概念を導入します。これにより、一度に読み書きできる情報の個数(1Byteという)4は少ないながら、メモリアドレスを変更することによってたくさんの情報をメモリに保管することができます。

このように、メモリ大容量時代に欠かすことのできないメモリアドレスという概念ですが、これは0から始まる正の整数で表されるため、実際には0番地というアドレスは存在するのです。では、なぜセグメンテーションフォルトが起きたか、というと、OSが利用しているページングというCPUの機能が原因です。

コンピュータにおいてメモリアドレスはユニークです。よって、特に工夫しない場合、マルチタスキングOSにおいて複数のプロセスが同じメモリアドレス空間を所有することになります。この場合、メモリにアクセスする場合は、他のプロセスが利用しているメモリに間違ってアクセスしないように気をつけなければなりません。このような不便を解消するのがページングで、ページングを用いると各プロセスがそれぞれ別個の仮想アドレス空間を所有できます。このとき、仮想アドレス空間5と物理(メモリ)アドレス空間の間を変換する表をOSが管理しています。

よって、本来プロセスは存在するすべてのアドレスにアクセスできるはずです。しかしながら、実際はそうはいかず、仮想アドレス空間においてもOSが幅を利かしています。プログラムから利用できるメモリの範囲を制限しているのです。このような仮想アドレス空間の区画分けをメモリマップといいます。この記事にあるように、0番地の周辺は使用しないこととなっており(対応する物理メモリアドレスが割り当てられていない)、そのためセグメンテーションフォルトが発生します。6

おわりに

さらっと書くつもりが20分くらい掛かった。書かなきゃよかった。


  1. 後述。 

  2. return 0;を省略するのは間違い、と勘違いしている人が大勢います。 

  3. セグフォを起こすだけなので、初期化しなくても大丈夫だろうけど。 

  4. 実際にはメモリモジュール一本あたり64bitが同時に書き込めます。他にもDMA等の技術により、高速にメモリにアクセスできます。 

  5. とリニアアドレス空間 

  6. OSの実装方法によります。また、OSによっては別のCPU例外が発生します。 

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした