Возвращает ли объявленную заранее структуру неопределенное поведение? - PullRequest
8 голосов
/ 30 ноября 2010

У меня есть следующий код (для простоты опущены защитные щитки):

= 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 (оба показывают проблему)

Ответы [ 3 ]

7 голосов
/ 30 ноября 2010

Это должно быть хорошо в соответствии с 8.3.5.6: «Тип параметра или возвращаемого типа для объявления функции, который не является определением, может быть неполным типом класса.»

3 голосов
/ 14 марта 2011

Я думаю, у меня та же проблема. Это происходит с малыми типами возвращаемых значений и . Порядок включения заголовков имеет значение. Во избежание этого не используйте прямое объявление типа возвращаемого значения или , включая заголовки в том же порядке .

Для возможного объяснения посмотрите на это:

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 помещается в один регистр процессора.

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 неизвестен. Он не знает, как можно вернуть Фу. Поэтому 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;
}

Обратите внимание, что здесь размер Foo известен при объявлении 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 целых, компилятор всегда передавал бы указатель.

1 голос
/ 30 ноября 2010

Работает у меня под GCC.Я не знаю, почему это не так, поскольку foo.hpp включено до foo_generator.hpp.

...