[英]Is returning a forward-declared structure 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
或者只是崩溃。
如果我用#include "foo.hpp"
替换foo_generator.hpp
中FOO的前向声明,那么它可以工作。
所以这是我的问题:返回一个前向声明的结构会导致未定义的行为吗? 或者什么可能出错?
使用的编译器: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()时,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整数,编译器将始终传递指针。
在海湾合作委员会下,它对我来说很好。 我不知道为什么它不会,因为foo.hpp
包含在foo_generator.hpp
之前。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.