26

関数の戻り値は通常、スタックまたはレジスタに格納されます。しかし、大きな構造体の場合、スタック上になければなりません。このコードを実際のコンパイラでコピーする必要があるのはどれくらいですか? それとも最適化されていますか?

例えば:

struct Data {
    unsigned values[256];
};

Data createData() 
{
    Data data;
    // initialize data values...
    return data;
}

(関数をインライン化できないと仮定します..)

4

5 に答える 5

25

なし; コピーは行われません。

呼び出し元の Data 戻り値のアドレスは、実際には隠し引数として関数に渡され、createData 関数は単に呼び出し元のスタック フレームに書き込みます。

これは、名前付き戻り値の最適化として知られています。このトピックに関する c++ FAQも参照してください。

商用グレードの C++ コンパイラは、少なくとも単純なケースでは、オーバーヘッドを排除できるように値渡しを実装しています。

...

yourCode() が rbv() を呼び出すと、コンパイラは密かにポインタを rbv() が「返された」オブジェクトを構築する場所に渡します。

printf を持つデストラクタを構造体に追加することで、これが行われたことを示すことができます。デストラクタは、この値による戻りの最適化が動作している場合は 1 回だけ呼び出す必要があり、それ以外の場合は 2 回呼び出す必要があります。

また、アセンブリをチェックして、これが発生することを確認できます。

Data createData() 
{
    Data data;
    // initialize data values...
    data.values[5] = 6;
    return data;
}

アセンブリは次のとおりです。

__Z10createDatav:
LFB2:
        pushl   %ebp
LCFI0:
        movl    %esp, %ebp
LCFI1:
        subl    $1032, %esp
LCFI2:
        movl    8(%ebp), %eax
        movl    $6, 20(%eax)
        leave
        ret     $4
LFE2:

不思議なことに、データ項目用にスタックに十分なスペースを割り当てましたが、スタックsubl $1032, %espの最初の引数を8(%ebp)オブジェクトのベース アドレスとして取り、その項目の要素 6 を初期化することに注意してください。createData に引数を指定しなかったので、これが親のバージョンの Data への秘密の隠しポインターであることに気付くまで、これは興味深いものです。

于 2010-01-28T15:43:55.550 に答える
7

ただし、大きな構造の場合は、ヒープスタック上にある必要があります。

確かにそうです!ローカル変数として宣言された大きな構造体がスタックに割り当てられます。それを片付けてくれてうれしい。

他の人が指摘しているように、コピーを避けることに関して:

  • ほとんどの呼び出し規約は、構造体を配置する必要がある呼び出し元のスタックフレーム内の場所を指す追加のパラメーターを渡すことにより、「関数が構造体を返す」ことを扱います。これは間違いなく呼び出し規約の問題であり、言語の問題ではありません。

  • この呼び出し規約を使用すると、比較的単純なコンパイラでも、コードパスが構造体を確実に返す時期を認識し、その構造体のメンバーへの割り当てを修正して、呼び出し元のフレームに直接入り、生成しないようにすることが可能になります。コピーする必要があります。重要なのは、関数を介したすべての終了コードパスが同じ構造体変数を返すことをコンパイラーが認識することです。その場合、コンパイラーは呼び出し元のフレーム内のスペースを安全に使用できるため、戻り時にコピーする必要がありません。

于 2010-01-28T23:47:15.680 に答える
6

例はたくさんありますが、基本的には

この質問には明確な答えはありません。コンパイラに依存します。

C は、関数から返される構造体の大きさを指定しません。

x86 RHEL 5.4 上の特定のコンパイラー gcc 4.1.2 のテストを次に示します。

gcc の些細なケース、コピーなし

[00:05:21 1 ~] $ gcc -O2 -S -c t.c
[00:05:23 1 ~] $ cat t.s
        .file   "t.c"
        .text
        .p2align 4,,15
.globl createData
        .type   createData, @function
createData:
        pushl   %ebp
        movl    %esp, %ebp
        movl    8(%ebp), %eax
        movl    $1, 24(%eax)
        popl    %ebp
        ret     $4
        .size   createData, .-createData
        .ident  "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-46)"
        .section        .note.GNU-stack,"",@progbits

gcc のより現実的なケース、スタックに割り当て、呼び出し元に memcpy

#include <stdlib.h>
struct Data {
    unsigned values[256];
};
struct Data createData()
{
    struct Data data;
    int i;
    for(i = 0; i < 256 ; i++)
        data.values[i] = rand();
    return data;
}

[00:06:08 1 ~] $ gcc -O2 -S -c t.c
[00:06:10 1 ~] $ cat t.s
        .file   "t.c"
        .text
        .p2align 4,,15
.globl createData
        .type   createData, @function
createData:
        pushl   %ebp
        movl    %esp, %ebp
        pushl   %edi
        pushl   %esi
        pushl   %ebx
        movl    $1, %ebx
        subl    $1036, %esp
        movl    8(%ebp), %edi
        leal    -1036(%ebp), %esi
        .p2align 4,,7
.L2:
        call    rand
        movl    %eax, -4(%esi,%ebx,4)
        addl    $1, %ebx
        cmpl    $257, %ebx
        jne     .L2
        movl    %esi, 4(%esp)
        movl    %edi, (%esp)
        movl    $1024, 8(%esp)
        call    memcpy
        addl    $1036, %esp
        movl    %edi, %eax
        popl    %ebx
        popl    %esi
        popl    %edi
        popl    %ebp
        ret     $4
        .size   createData, .-createData
        .ident  "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-46)"
        .section        .note.GNU-stack,"",@progbits

gcc 4.4.2### は大きく成長しており、上記の重要なケースではコピーできません。

        .file   "t.c"
        .text
        .p2align 4,,15
.globl createData
        .type   createData, @function
createData:
        pushl   %ebp
        movl    %esp, %ebp
        pushl   %edi
        pushl   %esi
        pushl   %ebx
        movl    $1, %ebx
        subl    $1036, %esp
        movl    8(%ebp), %edi
        leal    -1036(%ebp), %esi
        .p2align 4,,7
.L2:
        call    rand
        movl    %eax, -4(%esi,%ebx,4)
        addl    $1, %ebx
        cmpl    $257, %ebx
        jne     .L2
        movl    %esi, 4(%esp)
        movl    %edi, (%esp)
        movl    $1024, 8(%esp)
        call    memcpy
        addl    $1036, %esp
        movl    %edi, %eax
        popl    %ebx
        popl    %esi
        popl    %edi
        popl    %ebp
        ret     $4
        .size   createData, .-createData
        .ident  "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-46)"
        .section        .note.GNU-stack,"",@progbits

さらに、VS2008 (上記を C としてコンパイル) は createData() のスタックに構造体データを予約し、rep movsdループを実行してそれをデバッグ モードで呼び出し元にコピーし、リリース モードでは rand() の戻り値を移動します。 (%eax) 呼び出し元に直接戻る

于 2010-01-28T23:33:22.280 に答える
4
typedef struct {
    unsigned value[256];
} Data;

Data createData(void) {
    Data r;
    calcualte(&r);
    return r;
}

Data d = createData();

msvc(6,8,9) およびgccmingw(3.4.5,4.4.0) は、次の擬似コードのようなコードを生成します

void createData(Data* r) {
      calculate(&r)
}
Data d;
createData(&d);
于 2010-01-28T15:47:30.467 に答える
1

Linux の gcc は memcpy() を発行して構造体を呼び出し元のスタックにコピーします。関数に内部リンケージがある場合は、より多くの最適化が利用可能になります。

于 2010-01-28T15:54:15.727 に答える