このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

Cで、関数の引数を後始末するのは呼び側だったよね

「もっとえぐいトリックはないものか?」で次のような関数を出しました。


unsigned _mark;

void mark(int arg)
{
_mark = *(unsigned *)(&arg - 1);
}

これは、変数_markに、呼ばれた時点での戻り番地を代入する関数です。環境に依存しますが、多くの場合、戻り番地は第1引数のすぐ下に置かれています。よって、&arg - 1 が戻り番地の格納場所のアドレスです。

これで確かに戻り番地を取れるのですが、具合が悪い点があります。迂闊にも見落としていたのですが、markが戻った直後に、引数をスタックから取りはらう処理が実行されます*1。つまり、変数_markの指しているアドレスに飛ぶと、とりあえずスタックをはらうので、スタックトップがずれてしまう可能性があります。

これは「可能性」で、gccの場合ではスタックのズレが起きないときもあります。アセンブリをざっと眺めた感じでは、gccでは引数を積んだり降ろしたりをpush/popを使わずにmovを使うようで、関数呼び出しの直後にpop相当コードが入るとも限らないのです。(次の関数呼び出しで以前の引数を上書きすることがある。)

コンパイラに影響されにくいようにするには、引数を使わずにローカル変数を基準にしたほうがいいでしょう。


unsigned _mark;

void mark()
{
int dummy;
_mark = *(unsigned *)(&dummy + 2); // 2は環境に依存
}

とはいっても、ジョーク以外で、こんなミョウチクリンな関数を使う機会なんてどうせないでしょうが、、、

[追記]ジョークの例 → ラベルとgotoを関数で実現(おバカです)[/追記]

*1:Pascal呼び出し規約を指定すれば、引数の後始末は関数内で行うようになるでしょう。