返回一个前向声明的结构未定义的行为?

时间:2010-11-30 10:30:39

标签: c++ visual-c++ undefined-behavior

我有以下代码(为简单起见省略了包含保护):

= 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_generator.hpp中的FOO的前向声明替换为#include "foo.hpp",那么它可以正常工作。

所以这是我的问题:返回前向声明的结构会导致未定义的行为吗?或者什么可能出错?

使用的编译器:MSVC 9.0和10.0(都显示问题)

3 个答案:

答案 0 :(得分:7)

根据8.3.5.6,这应该没问题:“参数的类型或非定义的函数声明的返回类型可能是不完整的类类型。”

答案 1 :(得分: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整数,编译器总是会传递指针。

答案 2 :(得分:1)

GCC下我的工作正常。我不知道为什么不会,因为foo.hpp之前包含foo_generator.hpp