web-dev-qa-db-ja.com

CまたはC ++で構造体を返すことは安全ですか?

私が理解しているのは、これを行うべきではないということですが、このようなことをする例を見たことがあると思います(コードは必ずしも構文的に正しいわけではありませんが、アイデアはあります)

typedef struct{
    int a,b;
}mystruct;

そして、ここに関数があります

mystruct func(int c, int d){
    mystruct retval;
    retval.a = c;
    retval.b = d;
    return retval;
}

このようなことをしたい場合は、常にmallocされた構造体へのポインタを返す必要があることを理解しましたが、このようなことをする例を見たことは確かです。これは正しいです?個人的には、常にmallocされた構造体へのポインタを返すか、関数への参照によってパスを行い、そこで値を変更します。 (私の理解では、関数のスコープが終了すると、構造の割り当てに使用されたスタックはすべて上書きされる可能性があるということです)。

質問に2番目の部分を追加しましょう。これはコンパイラによって異なりますか?もしそうなら、デスクトップ用コンパイラの最新バージョンの動作は何ですか:gcc、g ++、Visual Studio

問題についての考え?

78
jzepeda

それは完全に安全であり、そうすることは間違っていません。また、コンパイラによって異なりません。

通常、(例のように)構造体が大きすぎない場合、このアプローチはmallocされた構造体を返すよりも優れていると主張します(mallocは高価な操作です)。

72

それは完全に安全です。

値で戻ります。未定義の動作につながるのは、参照によって戻る場合です。

//safe
mystruct func(int c, int d){
    mystruct retval;
    retval.a = c;
    retval.b = d;
    return retval;
}

//undefined behavior
mystruct& func(int c, int d){
    mystruct retval;
    retval.a = c;
    retval.b = d;
    return retval;
}

スニペットの動作は完全に有効で定義されています。コンパイラーによって異なりません。 大丈夫!

個人的には、常にmallocされた構造体へのポインタを返す

してはいけません。可能であれば、動的に割り当てられたメモリを避ける必要があります。

または、関数への参照によるパスを行い、そこで値を変更します。

このオプションは完全に有効です。それは選択の問題です。一般的に、元の構造体を変更しながら、関数から何か他のものを返したい場合にこれを行います。

私の理解では、関数のスコープが終了すると、構造を割り当てるために使用されたスタックは上書きされる可能性があるためです

これは間違っています。つまり、それは一種の正しいことですが、関数内で作成した構造のコピーを返します。 理論的に。実際には、[〜#〜] rvo [〜#〜]が発生する可能性があります。戻り値の最適化について読んでください。つまり、retvalは、関数の終了時にスコープ外に出ているように見えますが、余分なコピーを防止するために、実際には呼び出しコンテキストで構築されている可能性があります。これは、コンパイラが自由に実装できる最適化です。

68
Luchian Grigore

関数を終了すると、関数内のmystructオブジェクトの有効期間は実際に終了します。ただし、returnステートメントでオブジェクトを値渡ししています。これは、オブジェクトが関数から呼び出し元の関数にコピーされることを意味します。元のオブジェクトはなくなりましたが、コピーは残ります。

9

Cでstruct(またはC++でclassを返すのが安全であるだけでなく、struct- sは実際にはデフォルトのpublic:class- esですメンバー)、しかし多くのソフトウェアがそれをしています。

もちろん、C++でclassを返す場合、言語はデストラクタまたは移動コンストラクタが呼び出されることを指定していますが、これはコンパイラによって最適化される場合が多くあります。

さらに、Linux x86-64 ABI は、structtwoスカラー(ポインター、またはlong)値はレジスタ(%rax%rdx)を介して実行されるため、非常に高速で効率的です。したがって、その特定のケースでは、おそらく、そのような2スカラーフィールドstructを返すことは、他のことを行うよりも高速です(たとえば、引数として渡されたポインターに格納する)。

そのような2スカラーフィールドstructを返すことは、malloc- ingしてポインターを返すことよりもはるかに高速です。

完全に合法ですが、大きな構造体では、速度とスタックサイズの2つの要素を考慮する必要があります。

5
ebutusov

構造型は、関数によって返される値の型にすることができます。コンパイラは構造体のコピーを作成し、関数内のローカル構造体ではなくコピーを返すため、安全です。

typedef struct{
    int a,b;
}mystruct;

mystruct func(int c, int d){
    mystruct retval;
    cout << "func:" <<&retval<< endl;
    retval.a = c;
    retval.b = d;
    return retval;
}

int main()
{
    cout << "main:" <<&(func(1,2))<< endl;


    system("pause");
}
4
haberdar

あなたが行ったように構造体を返すことは完全に安全です。

ただし、このステートメントに基づいて:関数のスコープが終了すると、構造を割り当てるために使用されたスタックはすべて上書きできると理解されているためメンバーのいずれかがいるシナリオのみを想像します構造体の動的に割り当てられた(malloc'edまたはnew'ed)、その場合、RVOなしでは、動的に割り当てられたメンバーは破棄され、返されたコピーにはガベージを指すメンバーが含まれます。

4
maress

安全性は、構造体自体の実装方法に依存します。同様の何かを実装しているときに、私はこの質問につまずいただけで、潜在的な問題があります。

コンパイラーは、値を返すときに、いくつかの操作を実行します(他の操作も可能です):

  1. コピーコンストラクターmystruct(const mystruct&)を呼び出します(thisは一時変数です外部コンパイラー自体によって割り当てられた関数func
  2. func内に割り当てられた変数でデストラクタ_~mystruct_を呼び出します
  3. 返された値が_mystruct::operator=_で他の何かに割り当てられている場合は_=_を呼び出します
  4. コンパイラが使用する一時変数でデストラクタ_~mystruct_を呼び出します

ここで、mystructがここで説明したものと同じくらい簡単であれば問題ありませんが、ポインター(_char*_など)またはより複雑なメモリ管理がある場合は、_mystruct::operator=_に依存します。 、mystruct(const mystruct&)、および_~mystruct_が実装されています。したがって、複雑なデータ構造を値として返す場合は注意が必要です。

4
Filippo

私もsftrabbitに同意します、人生は確かに終わり、スタック領域はクリアされますが、コンパイラはすべてのデータがレジスタまたは他の方法で取得されることを保証するのに十分スマートです。

確認のための簡単な例を以下に示します(Mingw compiler Assemblyから取得)。

_func:
    Push    ebp
    mov ebp, esp
    sub esp, 16
    mov eax, DWORD PTR [ebp+8]
    mov DWORD PTR [ebp-8], eax
    mov eax, DWORD PTR [ebp+12]
    mov DWORD PTR [ebp-4], eax
    mov eax, DWORD PTR [ebp-8]
    mov edx, DWORD PTR [ebp-4]
    leave
    ret

Bの値がedxを介して送信されたことがわかります。一方、デフォルトのeaxにはaの値が含まれます。

3
perilbrain

質問に2番目の部分を追加しましょう。これはコンパイラによって異なりますか?

確かに、私の痛みに気づいたように: http://sourceforge.net/p/mingw-w64/mailman/message/33176880/

Win32(MinGW)でgccを使用して、構造体を返すCOMインターフェイスを呼び出していました。 MSはGNUとは異なる方法で行うため、私の(gcc)プログラムはスタックが破壊されてクラッシュしました。

MSがここで高い地位にある可能性がありますが、私が気にするのは、MSとGNU Windowsでのビルドとの間のABI互換性です。

もしそうなら、デスクトップ用コンパイラの最新バージョンの動作は何ですか:gcc、g ++、Visual Studio

Wineのメーリングリストで、MSがそれをどのように行っているかのメッセージを見つけることができます。

2
effbiae

構造体を返すことは安全ではありません。私は自分でそれをするのが大好きですが、誰かが後で返される構造にコピーコンストラクタを追加する場合、コピーコンストラクタが呼び出されます。これは予期しないものであり、コードを破壊する可能性があります。このバグを見つけるのは非常に困難です。

私はより詳細な回答をしたが、モデレーターはそれを好まなかった。だから、あなたの費用で、私のヒントは短いです。

2
Igor Polk