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

我想我遇到了同样的问题。它发生在小的返回值类型标题包含的顺序上。为避免这种情况,请不要使用返回值类型前向声明以相同的顺序包含标题

有关可能的解释,请查看以下内容:

函数.h

struct Foo;
Foo 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 足够小,所以它通过寄存器返回,不需要额外的参数。

主文件

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

请注意,这里 Foo 的大小在声明 func() 时是已知的。

主程序

; 5    :     func();

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

; 6    :     return 0;

所以编译器假设 func() 将通过寄存器返回值。它不传递指向临时位置的指针来存储返回值。但是如果 func() 期望它写入内存的指针会破坏堆栈。

让我们更改标题顺序,以便 func.h 先行。

主程序

; 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 回答