我有以下代码(为简单起见省略了包含保护):
= 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(都显示问题)
答案 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
。