[英]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.