2011-07-18 33 views
4
(gdb) disas func 
Dump of assembler code for function func: 
0x00000000004004b8 <func+0>: push %rbp 
0x00000000004004b9 <func+1>: mov %rsp,%rbp 
0x00000000004004bc <func+4>: movl $0x64,0xfffffffffffffff0(%rbp) 
0x00000000004004c3 <func+11>: movb $0x61,0xfffffffffffffff4(%rbp) 
0x00000000004004c7 <func+15>: mov 0xfffffffffffffff0(%rbp),%rax 
0x00000000004004cb <func+19>: leaveq 
0x00000000004004cc <func+20>: retq 
End of assembler dump. 


t_test func() 
{ 
    t_test t; 
    t.i = 100; 
    t.c = 'a'; 
    return t; 
} 

ローカル変数tが返されているようですが、このような仕事が保証されていますか、返されたときにローカル変数を参照しないと思われますか?Cはどのように構造体を返しますか?

+0

あなたが返すものの型が構造体であるという意味で構造体を返しています。しかし、あなたが実際に返すのは、構造の価値です。したがって 'int f(){...'は 'f'が返すものの型が' int'であることを意味します。そして、 'return 5;'は、* value * 5を返すことを意味します。同様に 'int q = 3; return q; 'は' q 'の* value *を返すことを意味し、これは3です。実際には 'q'を返すわけではありません。 –

答えて

4

恐らくraxは構造全体を保持するのに十分です。 0x00000000004004c7では、アドレスではなく構造体全体を取得します(代わりにleaを使用します)

+0

raxが十分に大きくない場合はどうなりますか? –

+1

@new_perlより大きな構造体を作成し、自分自身で参照してください。 – Justin

+0

それは私が推測するスタック上の構造体をコピーします。試す;) – BlackBear

0

スタックポインタは関数の開始時に変更されないため、t_testの割り当ては '関数内で作られているので、関数によって解放されません。これがどのように処理されるかは、使用される呼び出し規約によって異なります。関数がどのように呼び出されているかを見ると、それがどのように行われているかを見るのが簡単になります。

+0

t_testが関数内に割り当てられていないと言うことができるかどうかはわかりません。明示的には割り当てられていませんが、 Win64呼び出し規約では、呼び出し元(呼び出し元のメモリ)にレジスタを保存するために呼び出し元に特別なメモリブロックを予約させる必要があり、そのようなものがここで使用されているようです(RBP-16が使用されるアドレスです)。たとえスタックから明示的に割り当てられていなくても、ローカルになる可能性があります。 –

1

元のコードは、構造体へのポインタではなく構造体型を返すため、関数で作成された構造体のコピーを返しています。それは、構造全体がraxで値渡しされているように見えます。一般的に言えば、コンパイラは、これと、呼び出し元と呼び出し先の振る舞いと呼び出し規約に応じて、さまざまなアセンブリコードを生成することができます。

構造を処理するための適切な方法が出てパラメータとしてそれらを使用することです。私の経験で

void func(t_test* t) 
{ 
    t->i = 100; 
    t->c = 'a'; 
} 
+0

これは人為的な例です... –

+0

実際には適切な方法は値で返すことです。この方法では、コードを読み取っている人は、何が起こっているのかを理解するために非局所的な影響を考慮する必要があります。 ReentrancyとIdempotenceは、あなたのプログラムが正しいことを証明することを容易にします。 Googleの価値セマンティクス。 – spraff

+1

@spraff - 私は埋め込まれた背景から来て、価値ある構造を返す私にとっては悪い習慣です。パラメータ(配列、クラスなど)の暗黙のコピーに対しても同じことが言えます。これは、主にパフォーマンス上の理由、およびコンパイラエラーや汎用HWブードゥー(不足している言い訳ですが、気にする以上に起こる)によって発生するさまざまなバグが原因です。 –

5

、Cは構造体を返す方法を標準的な方法はありません。構造体を渡すことができるように、コンパイラは通常、(ユーザには目に見えない)構造体へのポインタを渡します。このポインタには、関数が内容をコピーすることができます。このポインタがどのように渡されるか(スタックの最初または最後)は、実装に依存します。 32ビットMSVC++のようなコンパイラの中には、EAXやEDXのようなレジスタ内の小さな構造体を返すものがあります。どうやら、GCCはそのような構造体をRAXで64ビットモードで返します。

しかし、もう一度これがどのように行われるのか標準的な方法はありません。これは、関数を使用するコードの残りの部分も同じコンパイラでコンパイルされても問題ありませんが、関数がDLLまたはlibのエクスポートされた関数であれば問題になります。私はこれを数回、別の言語(Delphi)や異なるコンパイラを使ったC言語などの関数を使用すると噛まれました。 this linkも参照してください。

+0

これは実装依存の動作であると言うリンクはありますか? –

+1

私は実装に依存していることを知っています。各実装には独自の方法があります。私はCやDelphiプログラムから構造体を返す関数(DLL内)にアクセスする方法を見つけるのは必ずしも容易ではないので、これを複数回試しています。特にfuncがレジスタに戻る場合、これを直接処理する方法はほとんどありません(アセンブラを除く)。 –

3

どのように返されるのかはまったく標準的ではありませんが、通常はRAXにあります。あなたの例では、t_test :: iとt_test :: cがt_testの唯一のメンバーであり、それぞれが最大で32ビットであると仮定すると、構造体全体は64ビットのレジスタに収まるので、RAXから直接値を返します通常は2つのレジスタに収まるものがRAX:RDX(またはRDX:RAX、私は共通の順序を忘れてしまいます)で返されます。

3つ以上のレジスタの場合、一般に、呼び出し関数内のオブジェクト(通常は戻り値が直接割り当てられるもの)を指す、隠されたポインタパラメータが最初のパラメータとして渡されます。このオブジェクトは、呼び出された関数から返される前に(通常は呼び出された関数で使用されるローカル構造からコピーされる)書き込まれ、通常渡された同じポインタがRAXに返されます。

EAX/EDXは、32ビットx86システムのRAX/RDXに​​置き換えることができます。

"this"ポインタをスタックに渡す規約(標準的なx86 GCCの規則のようなもの)では、戻り値ポインタは通常、最初のものではなく隠れた第2引数として渡されます。

+0

隠しポインタは、呼び出された関数のスタックフレームを指します。これは論理的には期限切れですか? –

+0

_calling_関数は、スペースを割り当てます(通常、関数呼び出しのために特別に割り当てられたものではなく、その関数内のローカル構造体です)。次に_called_関数(大きな構造体を返す関数)によって書き込まれます。戻る前にあなたが言及したことは、JUSTが返す関数のスタックフレームがまだ上書きされていないので、 "ok"となる可能性があります。 –

+0

Windows上の一部のCおよびC++コンパイラでは、あなたが記述した内容が正しいかもしれませんが、それはすべてに当てはまりません。これは普遍的な方法ではありません(特に、すべてのプロセッサにこれらのレジスタがあるわけではないため)。 –

関連する問題