返回向前指定的结构不确定的行为吗?
-
29-09-2019 - |
题
我有以下代码(为了简单起见,省略了淘汰者):
= 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(都显示问题)
解决方案
根据8.3.5.6:“不是定义的函数声明的返回类型的类型或返回类型可能是不完整的类类型。”
其他提示
我想我有同样的问题。它发生 小回报价值类型 和 标头包含的顺序 很重要。避免它 不要使用返回值类型的正向声明 或者 以相同的顺序包括标题.
有关可能的解释,请看一下:
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()声明func()时,都知道这里的大小。
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个整数编译器,总是会通过指针。
在海湾合作委员会下对我来说很好。我不知道为什么不会,因为 foo.hpp
在之前包括 foo_generator.hpp
.