8

私は次のコードを持っています(簡単にするためにインクルードガードは省略されています):

= foo.hpp=

struct FOO
{
  int not_used_in_this_sample;
  int not_used_in_this_sample2;
};

= main.cpp=

#include "foo_generator.hpp"
#include "foo.hpp"

int main()
{
  FOO foo = FooGenerator::createFoo(0xDEADBEEF, 0x12345678);

  return 0;
}

= foo_generator.hpp=

struct FOO; // FOO is only forward-declared

class FooGenerator
{
  public:

    // Note: we return a FOO, not a FOO&
    static FOO createFoo(size_t a, size_t b);
};

= foo_generator.cpp=

#include "foo_generator.hpp"
#include "foo.hpp"

FOO FooGenerator::createFoo(size_t a, size_t b)
{
  std::cout << std::hex << a << ", " << b << std::endl;

  return FOO();
}

このコードは、現状では、警告なしで完全に正常にコンパイルされます。私の理解が正しければ、次のように出力されます。

deadbeef, 12345678

ただし、代わりにランダムに表示されます。

12345678, 32fb23a1

または単にクラッシュします。

FOO の前方宣言を に置き換えるfoo_generator.hpp#include "foo.hpp"、機能します。

だからここに私の質問があります: 前方宣言された構造を返すと、未定義の動作が発生しますか? または、何がうまくいかない可能性がありますか?

使用するコンパイラ: MSVC 9.0 および 10.0 (どちらも問題を示しています)

4

3 に答える 3

7

8.3.5.6によると、これで問題ないはずです。「定義ではない関数宣言のパラメーターの型または戻り型は、不完全なクラス型である可能性があります。」

于 2010-11-30T10:56:49.407 に答える
3

私も同じ問題を抱えていると思います。これは小さな戻り値型で発生し、ヘッダーを含める順序が重要です。これを回避するには、戻り値型の前方宣言を使用したり、同じ順序でヘッダーを含めたりしないでください。

考えられる説明については、これを見てください。

func.h

struct Foo;
Foo func();

func.cpp

#include "func.h"
#include "foo.h"
Foo func()
{
    return Foo();
}

foo.h

struct Foo
{
    int a;
};

Foo全体が単一のCPUレジスタに収まることに注意してください。

func.asm(MSVS 2005)

$T2549 = -4                     ; size = 4
___$ReturnUdt$ = 8                  ; size = 4
?func@@YA?AUFoo@@XZ PROC                ; func

; 5    :     return Foo();

    xor eax, eax
    mov DWORD PTR $T2549[ebp], eax
    mov ecx, DWORD PTR ___$ReturnUdt$[ebp]
    mov edx, DWORD PTR $T2549[ebp]
    mov DWORD PTR [ecx], edx
    mov eax, DWORD PTR ___$ReturnUdt$[ebp]

func()が宣言されている場合、Fooのサイズは不明です。Fooがどのように返されるかはわかりません。したがって、func()は、ポインタがパラメータとして値ストレージを返すことを期待しています。これが_ $ReturnUdt$です。Foo()の値がそこにコピーされます。

func.cppでヘッダーの順序を変更すると、次のようになります。

func.asm

$T2548 = -4                     ; size = 4
?func@@YA?AUFoo@@XZ PROC                ; func

; 5    :     return Foo();

    xor eax, eax
    mov DWORD PTR $T2548[ebp], eax
    mov eax, DWORD PTR $T2548[ebp]

これで、コンパイラはFooが十分に小さいことを認識しているため、レジスタを介して返され、追加のパラメータは必要ありません。

main.cpp

#include "foo.h"
#include "func.h"
int main()
{
    func();
    return 0;
}

ここで、func()が宣言されたときにFooのサイズがわかっていることに注意してください。

main.asm

; 5    :     func();

    call    ?func@@YA?AUFoo@@XZ         ; func
    mov DWORD PTR $T2548[ebp], eax

; 6    :     return 0;

したがって、コンパイラはfunc()がレジスタを介して値を返すと想定します。戻り値を格納するための一時的な場所へのポインタは渡しません。ただし、func()がポインタを予期してメモリに書き込むと、スタックが破損します。

func.hが最初になるようにヘッダーの順序を変更しましょう。

main.asm

; 5    :     func();

    lea eax, DWORD PTR $T2548[ebp]
    push    eax
    call    ?func@@YA?AUFoo@@XZ         ; func
    add esp, 4

; 6    :     return 0;

コンパイラはfunc()が期待するポインタを渡すため、スタックが破損することはありません。

Fooのサイズが2整数より大きい場合、コンパイラは常にポインタを渡します。

于 2011-03-14T13:25:38.153 に答える
1

GCCの下で私にとってはうまくいきます。foo.hppが以前に含まれているため、なぜそうしないのかわかりませんfoo_generator.hpp

于 2010-11-30T10:37:41.793 に答える