This is the mail archive of the gcc-bugs@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[Bug c++/61015] Stack corruption with templates and pass-by-reference


https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61015

Melissa <myriachan at gmail dot com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |myriachan at gmail dot com

--- Comment #1 from Melissa <myriachan at gmail dot com> ---
Your code is doing something bad - it's retaining a pointer to a temporary
object across a function call.

Although pointers to a derived class are convertible to pointers to a parent
class, that is not true for double-pointers.  For example, the following code
is not legal:

struct Base {};
struct Derived : Base {};

int main()
{
    // I don't normally use so-called Hungarian notation, but here it's clearer
    Derived *pDerived = new Derived();
    Derived **ppDerived = &pDerived;

    // This is legal.
    Base *pBase = pDerived;

    // This is not legal, and will cause a compiler error.
    Base **ppBase = ppDerived;
    return 0;
}

So back to your code, the problem is visible in this small part:

template<typename T>
class ArrayRef
{
  private:
    const T* data;
    ArrayRef( const T& oneElt )  // <--- important
    {
      this->data = &oneElt;
      ...
    }
};

int main()
{
  SpecialObj specialObj;
  SpecialObj* pSpecialObj = &specialObj;
  ArrayRef<Obj*> arrayRef( pSpecialObj );
...
}

The important thing is the const T & parameter.  T is Obj *, not SpecialObj *,
so the constructor takes Obj *const &.  (The const ends up on the right side of
the asterisk in this notation when expanding out T.)  References are really
pointers in alternate syntax, and just like pointers, it is not legal to have a
double-pointer conversion from derived to base.  Just like how SpecialObj **
can't be assigned to type Obj *const *, it's also the case that SpecialObj *
can't be bound to Obj *const &.

But why does it compile when the double-pointer case didn't?  It's because
const references have another property: they also take temporaries.  If an
object cannot be bound as a const lvalue to the const reference, the compiler
will instead try to create a temporary using an implicit conversion,
constructor call, or similar, then bind the temporary to the const reference.

Here, the temporary it can create is of type Obj *.  And sure enough,
SpecialObj * can be implicitly converted to Obj *.  So the compiler is going to
create a temporary Obj * on the stack containing a copy of the SpecialObj *
cast to Obj *, then pass *that* to to the ArrayRef<T> constructor.  Then you
save a pointer to the passed const Obj * inside the ArrayRef<T> constructor. 
References are really just pointers behind the scenes - and you just saved a
pointer to a temporary object.  Now your ArrayRef class has a pointer to an
arbitrary location in the caller's stack.

I'm going to guess that the compilers that don't crash are actually not
crashing because of the optimizer.  The inlining of the optimizer may actually
cause the temporary to remain in the current stack frame, possibly preventing
the pointer from corrupting anything.  It's when the pointer stored in
ArrayRef<T> points to a temporary whose memory was freed and reused that you
have a problem.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]