Domanda

Ho il seguente codice (includere guardie omessi per semplicità):

= 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();
}

Questo codice, così com'è, compila perfettamente bene senza alcun preavviso. Se la mia comprensione è corretta, dovrebbe uscita:

deadbeef, 12345678

ma, invece, a caso mostra:

12345678, 32fb23a1

O semplicemente si blocca.

Se si sostituisce la forward-dichiarazione di FOO in foo_generator.hpp con #include "foo.hpp", allora funziona.

Quindi ecco la mia domanda: fa tornare un vantaggio di struttura a termine dichiarata per un comportamento indefinito? O che cosa può andare storto?

compilatore utilizzato: MSVC 9.0 e 10.0 (entrambi mostrano la questione)

È stato utile?

Soluzione

Questo dovrebbe essere fine secondo 8.3.5.6: "Il tipo di un parametro o il tipo di cambio di una dichiarazione di funzione che non è una definizione può essere un tipo di classe incompleto"

Altri suggerimenti

Credo che ho avuto lo stesso problema. Succede con i tipi di piccolo valore di ritorno e l'ordine delle intestazioni inclusione le cose. Per evitarlo non utilizzare il ritorno tipo di valore dichiarazione anticipata o includono intestazioni nello stesso ordine .

Per una possibile spiegazione a questo aspetto:

func.h

struct Foo;
Foo func();

func.cpp

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

foo.h

struct Foo
{
    int a;
};

Si noti che tutto si adatta Foo in un unico registro 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]

Quando viene dichiarata func () le dimensioni di Foo è sconosciuta. E non sa come Foo potrebbe essere restituito. Così func () si aspetta puntatore allo stoccaggio valore di ritorno come parametro. Qui di _ $ ReturnUdt $. Valore di Foo () viene copiato lì.

Se cambiamo ordine intestazioni in func.cpp otteniamo:

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]

Ora compilatore sa che Foo è abbastanza piccolo per cui viene restituito tramite il registro e nessun parametro aggiuntivo necessario.

main.cpp

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

Si noti che qui le dimensioni di Foo è noto quando viene dichiarata func ().

main.asm

; 5    :     func();

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

; 6    :     return 0;

Quindi compilatore assume func () restituirà il valore tramite il registro. E non passa un puntatore alla posizione temporanea per memorizzare il valore di ritorno. Ma se func () si aspetta che il puntatore si scrive nella memoria corrompere lo stack.

intestazioni cambiamento andiamo per ordine così func.h va in primo luogo.

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 passa il puntatore che func () si aspetta quindi non ha prodotto risultati al danneggiamento dello stack.

Se la dimensione di Foo erano più grandi di 2 numeri interi compilatore sarebbe sempre passare il puntatore.

Funziona bene per me sotto GCC. Non so il motivo per cui non sarebbe, in quanto foo.hpp è incluso prima foo_generator.hpp.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top