前回の「アップロードの仕組み」に続き、今回はプログラムの中身のお話です。
前回はこちら
cratech.hatenablog.jp
C言語やC++の教科書を読むと、最初の方に必ずこう書いてあります。
「プログラムは main 関数から始まり、main 関数で終わる」
しかし、Arduinoのスケッチ(プログラム)を見てみましょう。あるのは setup() と loop() だけ。
C言語のルールに従えば、これはコンパイルエラーになるはずです。
なぜArduinoはエラーにならずに動くのでしょうか?
実は、私たちの目に見えない裏側に、本当のmain関数が隠されているからです。
今回は、Arduino Core(システムの核)のソースコードを覗き見て、その正体を暴いていきましょう。
全体像:setup と loop は「呼ばれている」だけ
結論から言います。
私たちが一生懸命書いている setup() や loop() は、プログラムの支配者ではありません。
彼らは、裏にいる main() 関数から「呼び出されている」だけの部品に過ぎないのです。
イメージとしては、このような包含関係になっています。

main という大きな枠組みの中に、私たちのコードがすっぽりと収まっているのです。
論より証拠。Arduinoのインストールフォルダの奥底に眠る、実際のソースコード(main.cpp)をお見せします。
これが、Arduinoが動いている時の「真の姿」です。
実際の main.cpp の中身(要約)
#include <Arduino.h>
int main(void)
{
init();
setup();
for (;;) {
loop();
if (serialEventRun) serialEventRun();
}
return 0;
}
※わかりやすく一部省略していますが、構造は忠実に書いています。
コードの解説:裏で何が起きている?
このコードを読み解くと、Arduinoの挙動が見えてきます。
① init()
まず最初に init() という関数が呼ばれます。
ここでは、「時間の計測(millis)」や「PWM(アナログ出力)」などが使えるように、マイコン内部のタイマー設定を自動で行っています。
私たちが面倒なレジスタ設定なしで delay(1000) と書けるのは、ここで準備してくれているおかげです。
② setup()
次に、私たちが書いた setup() が1回だけ呼ばれます。
③ for (;;) { ... }
for (;;) は C言語で無限ループです。
Arduinoは、この無限ループの中で、
1. loop() を呼ぶ
2. loop() が終わったら、また戻ってきて loop() を呼ぶ
をひたすら繰り返しているだけなのです。
つまり、void loop() という関数自体がループしているわけではなく、「外側の main 関数によって、何度も何度も呼び出されている」というのが真実です。
なので、loop関数内でreturnしても、loopの最初に返ってきます。これはfor(;;)で囲まれているからなのです。
なぜこんな構造なのか?
なぜ普通に main() を書かせず、わざわざ setup と loop に分けたのでしょうか?
それは、「組み込みプログラミング特有の作法(無限ループ)」を初心者に意識させないためです。
本来、組み込みプログラムは以下のように書きます。
int main() {
初期化();
while(1) {
処理();
}
}
もし初心者が while(1) を書き忘れると、プログラムは一瞬で終了して止まってしまいます(Lチカなら、一瞬光って消えて終わりです)。
Arduino開発チームは、この「枠組み」をあらかじめ用意してあげることで、
「とにかく loop の中に書けば動き続けるよ!」
という手軽さを実現したのです。素晴らしいですね。
エンジニアの視点:ここから分かること
この構造を知っていると、開発で役立つ知識が2つあります。
1. loop() の終わりは一瞬の隙間
loop() 関数の最後の行まで行くと、一度 main に戻り、またすぐに loop が呼ばれます。
この「戻って入り直す」処理には、ほんのわずかですが時間がかかります(オーバーヘッド)。
もし、マイクロ秒単位で極限まで速い処理(高速な波形生成など)をしたい場合は、loop() の中身をあえて while(1) { ... } で閉じ込めてしまい、main に戻さないというテクニックもあります。
2. serialEvent の正体
main.cpp の中に if (serialEventRun)... という記述がありましたね。
これは「シリアル通信でデータが来ているか?」をチェックする機能です。
loop() が一周するたびに毎回これがチェックされるため、loop() の中で重い処理(delay(3000)など)をしてしまうと、このチェックが遅れ、通信の反応が悪くなる原因になります。
まとめ
- Arduinoにもちゃんと
main() 関数はある。
setup と loop は、裏方から呼ばれているだけのパーツ。
- この親切設計のおかげで、初心者は「無限ループ」を気にせずコードが書ける。
さて、裏側の構造が見えてきました。
次回は、いよいよハードウェアに近い部分に踏み込みます。
便利でよく使う digitalWrite 関数。実はこれ、プロの現場では「遅すぎるから使うな」と言われることがあります。
なぜ遅いのか? どうすれば速くなるのか?
次回は「レジスタ操作」について書く予定です。