简体   繁体   English

返回一个前向声明的结构未定义的行为?

[英]Is returning a forward-declared structure undefined behavior?

I have the following code (include-guards omitted for simplicity's sake): 我有以下代码(为简单起见省略了包含保护):

= foo.hpp = = foo.hpp =

struct FOO
{
  int not_used_in_this_sample;
  int not_used_in_this_sample2;
};

= main.cpp = = main.cpp =

#include "foo_generator.hpp"
#include "foo.hpp"

int main()
{
  FOO foo = FooGenerator::createFoo(0xDEADBEEF, 0x12345678);

  return 0;
}

= foo_generator.hpp = = 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 = = 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. 如果我用#include "foo.hpp"替换foo_generator.hpp中FOO的前向声明,那么它可以工作。

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) 使用的编译器:MSVC 9.0和10.0(都显示问题)

根据8.3.5.6,这应该没问题:“参数的类型或不是定义的函数声明的返回类型可能是不完整的类类型。”

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.h

struct Foo;
Foo func();

func.cpp func.cpp

#include "func.h"
#include "foo.h"
Foo func()
{
    return Foo();
}

foo.h foo.h中

struct Foo
{
    int a;
};

Notice that whole Foo fits in a single CPU register. 请注意,整个Foo适合单个CPU寄存器。

func.asm (MSVS 2005) 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]

When func() is declared Foo's size is unknown. 当声明func()时,Foo的大小是未知的。 It doesn't know how Foo could be returned. 它不知道如何返回Foo。 So func() expects pointer to return value storage as its parameter. 所以func()期望指针将返回值存储作为其参数。 Here it's _ $ReturnUdt$. 这是_ $ ReturnUdt $。 Value of Foo() is copied there. 在那里复制Foo()的值。

If we change headers order in func.cpp we get: 如果我们在func.cpp中更改标题顺序,我们得到:

func.asm 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]

Now compiler knows that Foo is small enough so it is returned via register and no extra parameter needed. 现在编译器知道Foo足够小,所以它通过寄存器返回,不需要额外的参数。

main.cpp main.cpp中

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

Notice that here Foo's size is known when func() is declared. 请注意,声明func()时,Foo的大小是已知的。

main.asm main.asm中

; 5    :     func();

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

; 6    :     return 0;

So compiler assumes func() will return value through register. 因此编译器假定func()将通过寄存器返回值。 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. 但是如果func()期望它写入内存的指针会破坏堆栈。

Let's change headers order so func.h goes first. 让我们更改标题顺序,以便func.h先行。

main.asm main.asm中

; 5    :     func();

    lea eax, DWORD PTR $T2548[ebp]
    push    eax
    call    ?func@@YA?AUFoo@@XZ         ; func
    add esp, 4

; 6    :     return 0;

Compiler passes the pointer that func() expects so no stack corruption results. 编译器传递func()期望的指针,因此不会产生堆栈损坏。

If Foo's size were bigger than 2 integers compiler would always pass the pointer. 如果Foo的大小大于2整数,编译器将始终传递指针。

It works fine for me under GCC. 在海湾合作委员会下,它对我来说很好。 I don't know why it wouldn't, since foo.hpp is included before foo_generator.hpp . 我不知道为什么它不会,因为foo.hpp包含在foo_generator.hpp之前。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM