こんにちは。
とあるエンジニアです。
この記事は 呉高専エンジニア勉強会 Advent Calendar 2017の23日目の記事です。
本日はクリスマスによって正帰還され発散しそうな僕のストレスをC言語にぶつける話と、それを現実的に行うべきではないということを再確認する話です。
Cの関数に八つ当たりします。
##八つ当たりの対象
- 環境
gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.5)
64bit
#include <stdio.h>
void f(){
printf("とっても楽しい!");
}
int main(){
printf("fの彼女「クリスマス、楽しいよね!」\n");
printf("f「");
f();
printf("」\n");
putchar('\n');
return 0;
}
実行結果
fの彼女「クリスマス、楽しいよね!」
f「とっても楽しい!」
今からこのf()に少し細工して思ってもないことを言わせます。
##思ってもないことを言わせる。
思ってもいないようなことを言わせる関数を定義してやります。
void y(){
printf("...と言ったな。あれは嘘だ。」\n");
}
そしてf()に代入処理を追加します。(説明は次節で)
void f(){
int a;//追加
*((&a) + 5)=&y;//追加
printf("とっても楽しい!");
}
すると、以下のような結果になります。
fの彼女「クリスマス、楽しいよね!」
f「とっても楽しい!...と言ったな。あれは嘘だ。」
きっとこの後は爆散したことでしょう。めでたしめでたし。
##あの代入処理は何をしているのか
簡単に言うと、関数f()のリターン先アドレスをy()に書き換えてやりました。
だからf()がmain関数に戻ろうとした時に、
間違えてy()に行っちゃったんですね。
なお、main関数に処理は戻っていません。
y()の戻り先は不明の状態なのでその場で強制終了していることでしょう。
##※これを真面目に使う実装は一般的に非現実的である
なぜなら、コンパイラのバージョン、OSの違い等でアドレスはわりと変わるからです。
VC++でやってみたところリターン先アドレスにアクセスするときのインクリメントは2になりました。他の環境でも変わると思います。
これを実際に使おうと思うと、実行環境やコンパイラのバージョンアップに合わせて#ifdefで分岐させたりして対応する必要があります。
ソフトウェアの開発では非効率的すぎです。
##先人たちに感謝するべき
上で非効率的といいましたが、私たちが非効率的と言えるのは、同じ書き方で実行環境の違いを吸収してくれる言語、OS、コンパイラ、インタプリタ、ライブラリ等があるからです。そんなありがたい仕組みがあるのに、今回のような「逆らってみる」ようなことを現実に行うと痛い目を見るのはその人自身です。
仕組みに逆らわずに正しい実装で(環境にバグがあってもクソとは言わずに)、楽しいプログラミングをしていきましょう。