返回前向声明的结构是否是未定义的行为?
我有以下代码(为了简单起见,省略了 include-guards):
= 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(均显示该问题)
I have the following code (include-guards omitted for simplicity's sake):
= 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();
}
This code, as it stands, compiles perfectly fine without any warning. If my understanding is correct, it should output:
deadbeef, 12345678
But instead, it randomly displays:
12345678, 32fb23a1
Or just crashes.
If I replace the forward-declaration of FOO in foo_generator.hpp
with #include "foo.hpp"
, then it works.
So here is my question: Does returning a forward-declared structure lead to undefined behavior ? Or what can possibly go wrong ?
Compiler used: MSVC 9.0 and 10.0 (both show the issue)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
根据 8.3.5.6,这应该没问题:“不是定义的函数声明的参数类型或返回类型可能是不完整的类类型。”
That should be fine according to 8.3.5.6: "The type of a parameter or the return type for a function declaration that is not a definition may be an incomplete class type."
我想我也遇到了同样的问题。
小返回值类型会发生这种情况,并且标头包含的顺序很重要。为了避免这种情况,不要使用返回值类型前向声明或以相同的顺序包含标头。
有关可能的解释,请查看以下内容:
func.h
func.cpp
foo.h
请注意,整个 Foo 适合单个 CPU 寄存器。
func.asm (MSVS 2005)
当声明 func() 时,Foo 的大小未知。它不知道如何返回 Foo。因此 func() 期望指针返回值存储作为其参数。这是_$ReturnUdt$。 Foo() 的值被复制到那里。
如果我们更改 func.cpp 中的标头顺序,我们会得到:
func.asm
现在编译器知道 Foo 足够小,因此它通过寄存器返回,不需要额外的参数。
main.cpp
请注意,当声明 func() 时,Foo 的大小是已知的。
main.asm
因此编译器假设 func() 将通过寄存器返回值。它不会传递指向临时位置的指针来存储返回值。
但是,如果 func() 需要指针,它就会写入内存,从而破坏堆栈。
让我们更改标头顺序,以便 func.h 首先出现。
main.asm
编译器传递 func() 期望的指针,因此不会导致堆栈损坏。
如果 Foo 的大小大于 2 个整数,编译器将始终传递指针。
I guess I got the same problem.
It happens with small return value types and the order of headers inclusion matters. To avoid it don't use return value type forward declaration or include headers in the same order.
For a possible explanation look at this:
func.h
func.cpp
foo.h
Notice that whole Foo fits in a single CPU register.
func.asm (MSVS 2005)
When func() is declared Foo's size is unknown. It doesn't know how Foo could be returned. So func() expects pointer to return value storage as its parameter. Here it's _$ReturnUdt$. Value of Foo() is copied there.
If we change headers order in func.cpp we get:
func.asm
Now compiler knows that Foo is small enough so it is returned via register and no extra parameter needed.
main.cpp
Notice that here Foo's size is known when func() is declared.
main.asm
So compiler assumes func() will return value through register. It doesn't pass a pointer to temp location to store return value.
But if func() expects the pointer it writes to memory corrupting the stack.
Let's change headers order so func.h goes first.
main.asm
Compiler passes the pointer that func() expects so no stack corruption results.
If Foo's size were bigger than 2 integers compiler would always pass the pointer.
它在 GCC 下对我来说工作得很好。我不知道为什么它不会,因为
foo.hpp
包含在foo_generator.hpp
之前。It works fine for me under GCC. I don't know why it wouldn't, since
foo.hpp
is included beforefoo_generator.hpp
.