「この位置にｐｒｉｎｔｆが無いとなぜか動かないんだ。」が面白かったので細かい動作を考えてみる。 

本日Qiitaのお知らせメールで紹介された（？）こちらの記事。
https://qiita.com/fujitanozomu/items/8b7d9e4e51baf0764dfc

面白かったので、何故そうなるのか細かく考えてみました。

以下のコードは、上記の記事より拝借させて頂きました。

```
#include <stdio.h>

void fizzbuzz(int n)
{
    int next;
    int i = 1;
    do {
        printf(i % 15 ? i % 5 ? i % 3 ? "%d\n" : "Fizz\n" : "Buzz\n" : "FizzBuzz\n", i);
        if (i++ >= n) next = 0;
    } while (next);
}

int main(void)
{
    printf((char[]){""}); // この位置にprintfが無いとなぜか動かない
    fizzbuzz(100);
}
```

main関数内のprintfがないと、fizzbuzz関数が100まで出力しなくなる、というもの。
この動作を理解するためには、「呼び出し規約」「スタック」等の知識が必要となります。

# 詳細は省いてとりあえず答え。
main関数実行直後のsp（スタックポインタ、スタック領域の現在のメモリ上のアドレスを指し示す）が1000であるとすれば、printf実行時のスタックは、以下のようになります。
![q3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478224/743a149a-b79e-4c94-6d58-9bb4ff77cf69.png)

そして、fizzbuzz(100)実行時のスタック（厳密には、fizzbuzz関数のdoの位置）は以下のようになります。
![q2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478224/05ade834-a8a5-e135-7055-3892021f6c68.png)
詳細は後述しますが、printfを実行した時点で1008番地にはprintfで使用された値が入り、それはprintf実行後に特にクリアされたりはしません。つまり、fizzbuzz関数実行時、next変数は同じ1008番地を見ているため、printf実行時のゴミを参照することになります。
これ故に、printfが実行されるとnextが0以外の値となり、fizzbuzz関数がうまく動作する、というスンポーです。
※もしprintf実行時に、1008番地が0になったとしたら、当然ですがfizzbuzz関数はうまく動作しません。

# なんでそうなるの？
これを理解するためには、CPUの仕組みやC言語の仕組みを少し知る必要があります。
※なお、専門家ではないので多少正確でない表現があるかも知れません。突っ込む際は優し目にお願いします。

## スタック領域
まずスタック領域について軽く説明させてください。
スタックという名前だけあって、pushで積んでpopで取得（除去）するアレです。
CPUにはメモリの一区画をスタック的に使うための仕組みがあります。SP（スタックポインタ）やPUSH命令、POP命令等です。
※これは設計次第なので、もしかしたらCPUによってはないかもです。ですが、普段我々が使用するパソコン等のCPUにはあるはずです。

スタック領域はあくまで「スタック的に使う」だけなので、「スタックのてっぺん - 4のアドレス」といったアクセスが可能です。

ちなみに、こちらもコンパイラの設計次第ではあるのですが、C言語では通常ローカル変数はスタックに積まれます。
なので、関数内で

```
int a;
int b;
```

とかやると、スタックのてっぺんにa用の領域、b用の領域が積まれます。
この時点でSPはb用の領域を指しているので、SP - 4とすることでa用の領域にアクセスできるわけです。
※32bitの場合、intは4バイトなので、「てっぺんの一個前」という意味で-4です。

なお、スタックに積まれたローカル変数は、関数終了時にスタックから除去されます。
※もっと厳密な言い方すると、「スコープを外れたら」ですかね。
　しかも除去はPOPではなく、SPの値を関数呼び出し前の値で書き換える、みたいなことをすることが多いようです。

## 機械語に関数呼び出し等ない
みなさん馴染みの言語は、多分殆ど「関数」があると思います。
しかし、CPUが理解できる唯一の言語「機械語」には関数はありません。
代わりに（？）CALL命令という物があり、サブルーチンを実行することができます。

プログラムは通常、メモリにロードされ、IP（インストラクションポインタ）が指し示すアドレスにある命令を順に実行していきます。
このIPの値を書き換えれば、別の場所のプログラムを実行することができます。この「別の場所のプログラム」がサブルーチンです。

ただ別のアドレスのプログラムに制御をうつしたいだけならJMP命令というものを使えばよいのですが、飛び先のプログラムが完了したら元の場所に戻ってきたい！なんて場合はCALL命令を使います。JMP命令はIPの値を書き換えるだけですが、CALL命令はIPの値をスタックに積んだ後JMPします。

この仕組みにより、関数を実行できる！ようにも思えるのですが、実は引数の受け渡しと、戻り値の取得は、自前でなんとかしないといけません。

## 呼び出規約
さて、「引数をどうやって渡す」「戻り値をどうやって受け取る」は、実は色々な方法があります。
この辺りのやり方をルールとして定めたのが「呼び出し規約」です。
実際は、引数・戻り値の他にも、スタックを使用した場合、それを元の状態に戻すのは、関数の呼び出し元？先？というのもあります。
c言語の標準はcdeclというやつですが、ウィンドウズではstdcallという規約を使っていたりします。
cdeclでは、引数はスタックに積む、というルールになっています。

スタックに積んだ後CALL命令を発行するので、先ほどのprintfの例では1000番地に引数、1004番地にreturn addressが格納された訳です。
※なお、1000番地の次が1004番地なのは、32bit命令を想定しているためです。64bit命令ならば、1008になるかと思います。多分。

ちなみに、何ですが、異なる言語のライブラリを使うケースでは、呼び出し規約をあわせないとうまく動作しません。
JavaからCで書かれたプログラムを呼ぶときや、VB.NETからCで書かれたプログラムを呼ぶとき、等です。

## 結局printfは？
先ほど、printf実行時の1008番地が？のままでした。
printfの引数が二つあれば何事もなく説明完了とできたんですが、実際にメモリの内容を（手で）書き出してみると、それだけでは説明できず、軽く愕然としてこの記事書くの辞めようかと思いました。

しかし、ネットでprintfのコードを見つける事ができたので掲載します。
※printfのコードは、処理系によって異なります。なので、以下はあくまで一例です。

```
int
__printf (const char *format, ...)
{
 va_list arg;
 int done;

 va_start (arg, format);
 done = vfprintf (stdout, format, arg);
 va_end (arg);

 return done;
}
```

関数名が__で始まっているのは目をつぶってください。話が長くなる（というか私には説明できない）ので。
このコードではva_list arg;というコードがあるので、これが1008番地に来るはずです。
なので、このargが0以外の値になれば、「このprintfがあると何故か動くんだ」という状態になります。

# おしまい
ということで、何とか説明できたんじゃないかな？と思うんですが、如何でしょうか？
この辺り詳しく知りたい方は、「はじめて読む8086」「30日でできる! OS自作入門」等の本で勉強されるとよろしいかと思います。

※ちなみに、SE歴15年程のアプリ屋さんですが、上記知識が役に立ったのは1回だけです。
　しかも、VB.NETからC言語のプログラムを呼ぶ、というケースで担当者が「なんかちゃんと実行されてるんですけどエラーになるんですよね」と言ってきて、「呼び出し規約あってないんじゃないすか」とアドバイスした、という、その程度です…。（実際呼び出し規約の問題でした）