yohhoyの日記

技術的メモをしていきたい日記

ローカル変数アドレス返却とgccの挙動

警告:C/C++言語においてはローカル変数*1はその変数スコープ内(関数やブロック)でのみ有効であり、同変数へのポインタ値をスコープ外に持ち出すと未定義動作(undefined behavior)を引き起こす。本記事の内容は未定義動作に依存しているため、実行環境のOSやCPUアーキテクチャ、将来のgccバージョンアップにより挙動が変わる可能性が高い。そもそも未定義動作を引き起こすソースコードを書いてはいけない

下記コードをgccコンパイルすると、問題を検知し警告"function returns address of local variable"を出力する。この警告を無視してプログラム実行すると、gcc 4.9以前と5.1以降でその振る舞いが異なるはず。

#include <stdio.h>

int* f() {
  int value = 42;
  return &value;  // BUG: 未定義動作
}

int main() {
  printf("%d", *f());
  // gcc 4.9以前: (おそらく)なんらかの値が出力される
  // gcc 5.1以降: (おそらく)SEGVが発生する
}

gcc 4.9.2, 5.1が生成するx86_64アセンブリは下記の通り。gcc 4.9.2はCソースコードを直訳したコードになっているが、gcc 5.1では意図的に値0を返している。

; gcc 4.9.2
pushq   %rbp
movq    %rsp, %rbp
movl    $42, -4(%rbp)
leaq    -4(%rbp), %rax  ; スタック上(ローカル変数)のアドレスを返す
popq    %rbp
ret
; gcc 4.9.2/-O1オプション
leaq    -4(%rsp), %rax
ret
; gcc 5.1
pushq   %rbp
movq    %rsp, %rbp
movl    $42, -4(%rbp)
movl    $0, %eax
popq    %rbp
ret
; gcc 5.1/-O1オプション
movl    $0, %eax  ; 値0を返す
ret

gcc 5.1の動作も、自動記憶域期間(automatic storage duration)を持つオブジェクトの生存期間(lifetime)が終わったことで、同オブジェクトを指すポインタの値が不定値(indeterminate value)に変化したと解釈でき、言語仕様に準拠した振る舞いと言える。C99 6.2.4/p2より引用(下線部は強調)。

The lifetime of an object is the portion of program execution during which storage is guaranteed to be reserved for it. An object exists, has a constant address, and retains its last-stored value throughout its lifetime. If an object is referred to outside of its lifetime, the behavior is undefined. The value of a pointer becomes indeterminate when the object it points to reaches the end of its lifetime.

ノート:このgccの振る舞いは「テンポラリ・オブジェクト中のフィールドをさすダングリング(dangling)ポインタ検知」を目的としたコンパイラ・フロントエンド変更の一環。2016年5月現在、当初の目的までは達成されていない模様。

関連URL

*1:より正確にはファイルスコープで宣言されず、extern指定やstatic指定がされていない変数。厳密な定義はC99 6.2.2/p6, 6.2.4/p4参照。