Topic: elided copy constructor


Author: Ron Natalie <ron@sensor.com>
Date: 1999/03/22
Raw View
>
> alloca() was invented for GNU gcc (IIRC), and it's basically a
> local malloc which gives you space on the stack that stays around
> until the function returns.

Sorry, while the description of alloca is correct, it predates
GNU by some time.  The early X code used to make heavy use of
it long before the GCC project started.  It was one of those
hack routines that people wrote that had intimate knowledge
of the compiler stack calls and it wasn't even possible to
implement on all machines.



[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: "E. Robert Tisdale" <edwin@netwood.net>
Date: 1999/03/19
Raw View
formerly AllanW@my-dejanews.com wrote:

>     class X {
>         int size;
>         int *data;
>         bool death_row;
>     public:
>         // Marks the object as "about to be copied and then deleted"
>         // Makes it okay for copy ctor to "rob" data instead of deep copy
>         X& condemn() { death_row = true; }
>
>         X(X&x) // Copy constructor
>             : size(x.size)
>             , data(0)
>             , death_row(false)
>         {
>             // Okay to steal the data?
>             if (x.death_row) {
>                 // Ours for the takin'
>                 data = x.data;
>                 x.data = 0;
>             }
>             else
>             {
>                 // Data is live; must make deep copy
>                 data = new int[size];
>                 // Error check elided
>
>                 // Assumes data points to POD:
>                 memcpy(data, x.data, size*sizeof(*data));
>             }
>         }
>         // ...
>     };
>
>     // A function that returns an X by value
>     X func() {
>         X my_X;
>         // ...
>
>         // Mark object as near-death, then return by value
>         return my_X.condemn();
>     }

What happens when X func(void) is submitted
to an optimizing C++ compiler which would correctly elide
the X(my_X) copy constructor upon return from X func(void)?
Will the compiler still recognize X& my_X.condemn(void)
as a reference to the return value?
Look for an article in the gnu.g++ newsgroup
with the same subject "elided copy constructor".
I think my solution for the egcs compiler is a bit more elegant.

> The hallmark of a good interface is that both sides have a good deal of
> flexibility,
> with little or no knowledge about the internals of the other side.
> As a consumer of the free store, we can (for all practical purposes)
> allocate objects of any size at any time, limited only by the amount
> of memory available to the program. There are also rules that
> must be followed, such as deleting the memory when finished with it
> [with certain exceptions that are off-subject].
> So long as the rules are followed rigidly,
> we can expect the implementation of the free store to handle our needs.
>
> The implementation also has flexibility. It has nearly complete autonomy
> to decide the physical allocation scheme; it can be pre-allocated, or
> handled through OS calls at runtime, or it can even come from a dedicated
> section of the stack area. There are also rules that must be followed;
> for instance, any system which reclaims memory when deleted must be able
> to determine the size of an allocation given only it's address. So long
> as the rules are followed rigidly, we can expect the implementation to
> work correctly with every compliant program.
>
> The same thing can be said of the process stack, except that the allocation
> routines are always private and typically inline. For most CPUs this
> involves adjusting the hardware stack pointer and noting the new address,
> and then performing compatible cleanup on the way out. Once again, these
> (compiler-private) routines have autonomy for the actual operation.
>
> Where do you keep your car keys, when you're not in your car? This is up
> to you, but I doubt that you consider it a "mystery" or a "dirty little
> secret."
> Indeed, all I have to do is observe you getting out of your car,
> and I can determine this for myself. And yet if I had some need to keep
> track of your keys, it would be foolish for me to assume that they are
> always in your left front pocket. Someday you may wear pants that have
> no left front pocket; someday you may loan them to a friend for some
> purpose that's none of my business; someday you might get a gift of a
> keychain that clips on to your belt; someday you might just get tired of
> keeping your keys in the same place.
>
> The memory allocated for you by the compiler and library is also subject
> to change. Computers are deterministic, and it's unlikely that the same
> compiled program would do this differently from one execution to the
> next. But it's certainly possible (what if different "shared memory"
> objects are in place tomorrow?). It's even more possible that the next
> time you compile the program, the implementation details are different
> than they were yesterday. It's even more possible that the next version
> of this compiler is different than the current one. By the time you get
> to different brands of compiler, we can start calling the event likely;
> by the time we get to a different platform, we can expect it. But even
> then, there are no guarantees!
>
> > Do you agree?
> [that programmers shouldn't talk about the process stack]
>
> This whole message is talking about it.
>
> Of course, the question is void. Anyone that agreed that it shouldn't
> be talked about, wouldn't answer the question, because that would be
> talking about it, which that person would have to agree is bad.
>
> Or, to put it another way:
>
> Is there anyone out there that won't respond to this question? Note
> that by responding you automatically disqualify yourself.

No, I think you missed the point.
The standard avoids discussion of the stack
because it is not required to implement automatic storage
any more than a heap is required to implement the free store
despite the fact that these are the most probable implementations
for any C++ compiler.
You don't know whether keys are even required to operate my car
although that might be a good guess since almost all cars
require car keys.  Are car keys standard?
I suppose that you might say that a "defacto standard" exists
failing the existence of an official standard.
Is the stack implementation of automatic storage standard
despite the fact that it is not part of the official ANSI standard?

Thanks in advance, E. Robert Tisdale <edwin@netwood.net>
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: David R Tribble <dtribble@technologist.com>
Date: 1999/03/20
Raw View
"E. Robert Tisdale" wrote:
>> Of course.  I'd be much happier with a standard technique
>> for identifying "salvageable" objects.
>> But it appears to me that automatic storage is like the free store.
>> It is "mystery memory" and the process stack is a dirty little secret
>> that C++ programmers really shouldn't talk about
>> even though Stroustrup does mention it from time to time.

Bruce Visscher wrote:
> I think alloca may be what you are looking for.  I've never used it
> myself, but I think it's basically a stack based version of malloc
> (someone with more experience can comment).  Perhaps alloca should be
> standardized, I don't know.  If so then I think it should be a C
> standard not a C++ standard.

alloca() was invented for GNU gcc (IIRC), and it's basically a
local malloc which gives you space on the stack that stays around
until the function returns.  Simply put, it adjusts the stack
pointer to give you more auto space during the lifetime of the
function frame; once the function returns, the stack pointer is
restored to the previous calling function's frame, and the extra
auto space goes away along with the rest of the function's auto
variables.

The impending C9X standard adds something similar (but a little
more robust) to C: variable-length arrays (VLAs).  For example:

    void foo(int sz)
    {
        float   arr[sz];    // VLA, array of 'sz' floats

        for (int i = 0;  i < sz;  i++)
            arr[sz] = i * 3.141592653589793238;
        ...
    }

The auto variable 'arr' is a VLA, whose size is determined at
runtime ('sz' must be greater than zero).  VLAs have block scope,
and their space goes away when the block ends.  (The expression
'sizeof(arr)' is also evaluated at runtime.)

I'm not sure if the next version of C++ should adopt VLAs or not;
arguments can be made both for it and against it.  VLAs would be a
little more complicated in C++ because of object contruction and
destruction.

-- David R. Tribble, dtribble@technologist.com --



[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: James Kuyper <kuyper@wizard.net>
Date: 1999/03/20
Raw View
David R Tribble wrote:
....
> alloca() was invented for GNU gcc (IIRC), and it's basically a

It's a lot older than gcc. It might even pre-date C itself. I wish I
could cite authoritative dates, but I think I first read about alloca()
around 1979.


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: "E. Robert Tisdale" <edwin@netwood.net>
Date: 1999/03/15
Raw View
Jerry Coffin wrote:

> OTOH, there's also no secret of the fact that _some_ machines
> (most IBM mainframes for a prominent example)
> do NOT use stacks for much of anything.

I just want to make sure that you're not confusing
IBM hardware or an IBM operating system
with the code emitted by a C++ compiler.
Which C++ compiler for which IBM hardware
does not use a stack to implement automatic storage?

> Now, as far as the standard goes, _most_ of these things
> could just about as easily fall under the notorious as-if rule --
> as long as the implementation _acts_ like it's using a stack,
> it doesn't really matter to a portable program
> that it's imitating a stack in some other fashion.
> By this figuring, it would be perfectly fine for the standard
> to talk about the implementation using a stack for automatic storage.
>
> IMO, this would be a mistake though: in particular,
> it could make the exposition LESS rather than more informative,
> and quite possibly more difficult to sort out some of the details as well.
>
> Thus, without extensive reading about the definitions of the words
> involved, a programmer reading the standard would typically be badly
> mislead if it treated a stack as a synonym for automatic storage.

I don't have a copy of the standard.
Does it actually mention the stack anywhere?

Thanks in advance, E. Robert Tisdale
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: "E. Robert Tisdale" <edwin@netwood.net>
Date: 1999/03/15
Raw View
Daniel Kaeps wrote:

> Are you sure, that the magic really works,
> e.g. if X is itself part of a vector?
> The copy c-tor may be called
> by the vector implementation assuming a deep copy...
> What if X is used for exception-classes,
> inside an exception handler etc.
>(I don't see the correctness on the first look).

It is really difficult to understand exactly what
you are trying to tell us here.
Could you code some examples for us using class X?

> If you don't need to return your objects as *function return value*,
> you could also use ref.-parameters to return the objects.
> Then it's really easy to call a function, lets say, "relocate",
> to return your value (instead of using "operator=").
> By explicit distinction between copy and relocation,
> you can avoid the magic.
>
> Then the copy can be avoided also for f1() in your example.
>
> (I use for function return values usually only *booleans*
> (or pointer or references)
> because of the odd C++ function return value concept,
> since I've realized some of the problems
> (by reading Scott Meyers book "Effective C++ programming")).

No, I really need to return X by value.
I am designing class libraries

    http://www.netwood.net/~edwin/svmt/

for application programmers to use.
They should be able to write functions which return X by value
without concerning themselves with the extra cost imposed
by an optimizing  C++ compiler which fails to elide
an unnecessary copy constructor.  Don't you agree?

E. Robert Tisdale <edwin@netwood.net>



[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: James Kuyper <kuyper@wizard.net>
Date: 1999/03/15
Raw View
"E. Robert Tisdale" wrote:
...
> They should be able to write functions which return X by value
> without concerning themselves with the extra cost imposed
> by an optimizing  C++ compiler which fails to elide
> an unnecessary copy constructor.  Don't you agree?

No - they should put pressure on the compiler vendor to produce an
implementation which does elide unnecessary copy constructors. That's a
far cleaner solution. There are much better things to spend your time on
than working around a compiler's possible failure to optimize. There's
no upper limit on the possible non-optimizations a compiler could
perform.
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: AllanW@dejanews.com (formerly AllanW@my-dejanews.com <allan_w@my-dejanews.com>)
Date: 1999/03/15
Raw View
In article <36E9DC90.59FA9DA8@netwood.net>,
  "E. Robert Tisdale" <edwin@netwood.net> wrote:
> Francis Glassborow wrote:
>
> > In article <36E6DF50.D0D6E975@netwood.net>, E. Robert Tisdale
> > <edwin@netwood.net> writes
> > >I assume that you meant to add,
> > >"if the program must be portable to all platforms."
> > >But I'd be happy if I just knew which platforms
> > >would permit me to use this or any other technique
> > >to determine when the argument of a copy constructor is doomed
> > >and the data which is referenced by it can be salvaged.
> >
> > But the problem of portability even applies to different releases
> > of the same compiler on the same platform.
>
> Of course.  I'd be much happier with a standard technique
> for identifying "salvageable" objects.

    class X {
        int size;
        int *data;
        bool death_row;
    public:
        // Marks the object as "about to be copied and then deleted"
        // Makes it okay for copy ctor to "rob" data instead of deep copy
        X& condemn() { death_row = true; }

        X(X&x) // Copy constructor
            : size(x.size)
            , data(0)
            , death_row(false)
        {
            // Okay to steal the data?
            if (x.death_row) {
                // Ours for the takin'
                data = x.data;
                x.data = 0;
            }
            else
            {
                // Data is live; must make deep copy
                data = new int[size];
                // Error check elided

                // Assumes data points to POD:
                memcpy(data, x.data, size*sizeof(*data));
            }
        }
        // ...
    };

    // A function that returns an X by value
    X func() {
        X my_X;
        // ...

        // Mark object as near-death, then return by value
        return my_X.condemn();
    }

> But it appears to me that automatic storage is like the free store.
> It is "mystery memory" and the process stack is a dirty little secret
> that C++ programmers really shouldn't talk about
> even though Stroustrup does mention it from time to time.

The hallmark of a good interface is that both sides have a good deal of
flexibility, with little or no knowledge about the internals of the other
side. As a consumer of the free store, we can (for all practical
purposes) allocate objects of any size at any time, limited only by the
amount of memory available to the program. There are also rules that
must be followed, such as deleting the memory when finished with it
[with certain exceptions that are off-subject]. So long as the rules are
followed rigidly, we can expect the implementation of the free store to
handle our needs.

The implementation also has flexibility. It has nearly complete autonomy
to decide the physical allocation scheme; it can be pre-allocated, or
handled through OS calls at runtime, or it can even come from a dedicated
section of the stack area. There are also rules that must be followed;
for instance, any system which reclaims memory when deleted must be able
to determine the size of an allocation given only it's address. So long
as the rules are followed rigidly, we can expect the implementation to
work correctly with every compliant program.

The same thing can be said of the process stack, except that the allocation
routines are always private and typically inline. For most CPUs this
involves adjusting the hardware stack pointer and noting the new address,
and then performing compatible cleanup on the way out. Once again, these
(compiler-private) routines have autonomy for the actual operation.

Where do you keep your car keys, when you're not in your car? This is up
to you, but I doubt that you consider it a "mystery" or a "dirty little
secret." Indeed, all I have to do is observe you getting out of your car,
and I can determine this for myself. And yet if I had some need to keep
track of your keys, it would be foolish for me to assume that they are
always in your left front pocket. Someday you may wear pants that have
no left front pocket; someday you may loan them to a friend for some
purpose that's none of my business; someday you might get a gift of a
keychain that clips on to your belt; someday you might just get tired of
keeping your keys in the same place.

The memory allocated for you by the compiler and library is also subject
to change. Computers are deterministic, and it's unlikely that the same
compiled program would do this differently from one execution to the
next. But it's certainly possible (what if different "shared memory"
objects are in place tomorrow?). It's even more possible that the next
time you compile the program, the implementation details are different
than they were yesterday. It's even more possible that the next version
of this compiler is different than the current one. By the time you get
to different brands of compiler, we can start calling the event likely;
by the time we get to a different platform, we can expect it. But even
then, there are no guarantees!

> Do you agree?
[that programmers shouldn't talk about the process stack]

This whole message is talking about it.

Of course, the question is void. Anyone that agreed that it shouldn't
be talked about, wouldn't answer the question, because that would be
talking about it, which that person would have to agree is bad.

Or, to put it another way:

Is there anyone out there that won't respond to this question? Note
that by responding you automatically disqualify yourself.

----
Allan_W@my-dejanews.com is a "Spam Magnet" -- never read.
Please reply in USENET only, sorry.

-----------== Posted via Deja News, The Discussion Network ==----------
http://www.dejanews.com/       Search, Read, Discuss, or Start Your Own


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: AllanW@dejanews.com (formerly AllanW@my-dejanews.com <allan_w@my-dejanews.com>)
Date: 1999/03/15
Raw View
In article <36ECA2B0.AAFF16BD@netwood.net>,
  "E. Robert Tisdale" <edwin@netwood.net> wrote:
> Jerry Coffin wrote:
>
> > OTOH, there's also no secret of the fact that _some_ machines
> > (most IBM mainframes for a prominent example)
> > do NOT use stacks for much of anything.
>
> I just want to make sure that you're not confusing
> IBM hardware or an IBM operating system
> with the code emitted by a C++ compiler.
> Which C++ compiler for which IBM hardware
> does not use a stack to implement automatic storage?

Before I knew C very well, I used an IBM 3090 in COBOL and a little bit
of assembly language. The 3090 has no hardware stack. Naturally, it would
be easy to emulate one with software, but using this as the call stack
would not work well in mixed-language projects.

[...]
> > Thus, without extensive reading about the definitions of the words
> > involved, a programmer reading the standard would typically be badly
> > mislead if it treated a stack as a synonym for automatic storage.
>
> I don't have a copy of the standard.

You really should get one, big spender.

> Does it actually mention the stack anywhere?

The standard says precisely two things about stacks. (It says them in a
multitude of places.)

    -- Stack Unwinding is the process of calling destructors for automatic
       objects constructed on the path from a try block to a
       throw-expression.
    -- stack<T> is a template container class.



Author: jcoffin@taeus.com (Jerry Coffin)
Date: 1999/03/15
Raw View
In article <36ECA2B0.AAFF16BD@netwood.net>, edwin@netwood.net says...
> Jerry Coffin wrote:
>
> > OTOH, there's also no secret of the fact that _some_ machines
> > (most IBM mainframes for a prominent example)
> > do NOT use stacks for much of anything.
>
> I just want to make sure that you're not confusing
> IBM hardware or an IBM operating system
> with the code emitted by a C++ compiler.
> Which C++ compiler for which IBM hardware
> does not use a stack to implement automatic storage?

I haven't looked at IBM mainframes recently enough to say with
certainty what compilers are available on them right now, but, for
example, when I used PL/I compilers on them, they did NOT use a stack
for automatic variables.  PL/I requires you to declare whether a
procedure is recursive or not.  If not, it used static allocation for
"automatic" variables.  If it was declared to be recursive, it
basically called a system equivalent of malloc on each entry to the
routine, and an equivalent of free on exit.

I don't know for sure, not having looked more recently, but given the
constraints of the hardware, I'd guess that C and C++ compilers do
roughly the same thing now.  The basic fact of the matter is that
these machines simply DON'T HAVE a stack pointer register.

[ ... ]

> I don't have a copy of the standard.
> Does it actually mention the stack anywhere?

Yes and no.  It defines "stack unwinding" as the process of calling
destructors of automatic variables when an exception is thrown.

The only place the standard appears to define "stack" as a concept in
its own right is with respect to the stack class in the standard
library.


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Daniel Kaeps <please@no.spam>
Date: 1999/03/16
Raw View
E. Robert Tisdale schrieb:
>
> Daniel Kaeps wrote:
>
[...]
> >(I don't see the correctness on the first look).
>
> It is really difficult to understand exactly what
> you are trying to tell us here.
> Could you code some examples for us using class X?
>

...I've no idea whats with the exception issue, because I
don't know
how exception mechanisms are exactly implemented

BUT... the problem comes with placement-new...

just call " func1(SomeX);"

void func1 (const X & Input)
{
 char CharArray [sizeof(X)+16]; // +16 maybe necessary for
     // some implementations?
 func2 (Input, CharArray);

 Input.doSomething (); // uh, where is Input?
}

void func2 (const X & Input, void *CharArray)
{
 // placement new
 X *NewedX = new (CharArray) X (Input);

 // here Input(=Source) is more on top than
 // CharArray(=Destination)!!

 // accidently your condition votes for *relocation*
 // which is wrong

 // condition was the following:
 // if (sp <= &x && &x <= this) // x is on top of *this
     // on the stack
};

Placement new is actually used by some STL implementations I
think.

Conclusion: There is no way to determine, depending on
whether Source or Destination is higher/lower than the other
on the stack, whether the copy-ctor should do relocation or
deep-copy (and, in the name of flexibility, this freedom
should not be changed IMO).

> > If you don't need to return your objects as *function return value*,
> > you could also use ref.-parameters to return the objects.
> > Then it's really easy to call a function, lets say, "relocate",
> > to return your value (instead of using "operator=").
> > By explicit distinction between copy and relocation,
> > you can avoid the magic.
> >
> > Then the copy can be avoided also for f1() in your example.

My typo: I meant e.g. f0() (and others)

> ... for application programmers to use.
> They should be able to write functions which return X by value
> without concerning themselves with the extra cost imposed
> by an optimizing  C++ compiler which fails to elide
> an unnecessary copy constructor.  Don't you agree?

And what's with the other uncatched cases, where a
relocation is conceptually needed? (See thread "object
RELOCATION (MOVE) operation (instead of copy) ..." recently
here).

Daniel Kaeps
(kaeps _at_ informatik.uni-leipzig.de)
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: AllanW@dejanews.com (formerly AllanW@my-dejanews.com <allan_w@my-dejanews.com>)
Date: 1999/03/18
Raw View
In article <36E9DC90.59FA9DA8@netwood.net>,
  "E. Robert Tisdale" <edwin@netwood.net> wrote:
> Francis Glassborow wrote:
>
> > In article <36E6DF50.D0D6E975@netwood.net>, E. Robert Tisdale
> > <edwin@netwood.net> writes
> > >I assume that you meant to add,
> > >"if the program must be portable to all platforms."
> > >But I'd be happy if I just knew which platforms
> > >would permit me to use this or any other technique
> > >to determine when the argument of a copy constructor is doomed
> > >and the data which is referenced by it can be salvaged.
> >
> > But the problem of portability even applies to different releases
> > of the same compiler on the same platform.
>
> Of course.  I'd be much happier with a standard technique
> for identifying "salvageable" objects.

    class X {
        int size;
        int *data;
        bool death_row;
    public:
        // Marks the object as "about to be copied and then deleted"
        // Makes it okay for copy ctor to "rob" data instead of deep copy
        X& condemn() { death_row = true; }

        X(X&x) // Copy constructor
            : size(x.size)
            , data(0)
            , death_row(false)
        {
            // Okay to steal the data?
            if (x.death_row) {
                // Ours for the takin'
                data = x.data;
                x.data = 0;
            }
            else
            {
                // Data is live; must make deep copy
                data = new int[size];
                // Error check elided

                // Assumes data points to POD:
                memcpy(data, x.data, size*sizeof(*data));
            }
        }
        // ...
    };

    // A function that returns an X by value
    X func() {
        X my_X;
        // ...

        // Mark object as near-death, then return by value
        return my_X.condemn();
    }

> But it appears to me that automatic storage is like the free store.
> It is "mystery memory" and the process stack is a dirty little secret
> that C++ programmers really shouldn't talk about
> even though Stroustrup does mention it from time to time.

The hallmark of a good interface is that both sides have a good deal of
flexibility, with little or no knowledge about the internals of the other
side. As a consumer of the free store, we can (for all practical
purposes) allocate objects of any size at any time, limited only by the
amount of memory available to the program. There are also rules that
must be followed, such as deleting the memory when finished with it
[with certain exceptions that are off-subject]. So long as the rules are
followed rigidly, we can expect the implementation of the free store to
handle our needs.

The implementation also has flexibility. It has nearly complete autonomy
to decide the physical allocation scheme; it can be pre-allocated, or
handled through OS calls at runtime, or it can even come from a dedicated
section of the stack area. There are also rules that must be followed;
for instance, any system which reclaims memory when deleted must be able
to determine the size of an allocation given only it's address. So long
as the rules are followed rigidly, we can expect the implementation to
work correctly with every compliant program.

The same thing can be said of the process stack, except that the allocation
routines are always private and typically inline. For most CPUs this
involves adjusting the hardware stack pointer and noting the new address,
and then performing compatible cleanup on the way out. Once again, these
(compiler-private) routines have autonomy for the actual operation.

Where do you keep your car keys, when you're not in your car? This is up
to you, but I doubt that you consider it a "mystery" or a "dirty little
secret." Indeed, all I have to do is observe you getting out of your car,
and I can determine this for myself. And yet if I had some need to keep
track of your keys, it would be foolish for me to assume that they are
always in your left front pocket. Someday you may wear pants that have
no left front pocket; someday you may loan them to a friend for some
purpose that's none of my business; someday you might get a gift of a
keychain that clips on to your belt; someday you might just get tired of
keeping your keys in the same place.

The memory allocated for you by the compiler and library is also subject
to change. Computers are deterministic, and it's unlikely that the same
compiled program would do this differently from one execution to the
next. But it's certainly possible (what if different "shared memory"
objects are in place tomorrow?). It's even more possible that the next
time you compile the program, the implementation details are different
than they were yesterday. It's even more possible that the next version
of this compiler is different than the current one. By the time you get
to different brands of compiler, we can start calling the event likely;
by the time we get to a different platform, we can expect it. But even
then, there are no guarantees!

> Do you agree?
[that programmers shouldn't talk about the process stack]

This whole message is talking about it.

Of course, the question is void. Anyone that agreed that it shouldn't
be talked about, wouldn't answer the question, because that would be
talking about it, which that person would have to agree is bad.

Or, to put it another way:

Is there anyone out there that won't respond to this question? Note
that by responding you automatically disqualify yourself.

----
Allan_W@my-dejanews.com is a "Spam Magnet" -- never read.
Please reply in USENET only, sorry.

-----------== Posted via Deja News, The Discussion Network ==----------
http://www.dejanews.com/       Search, Read, Discuss, or Start Your Own
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: Christopher Eltschka <celtschk@physik.tu-muenchen.de>
Date: 1999/03/10
Raw View
AllanW@my-dejanews.com wrote:

[...]

> may be completely different. In any case, the symbol "sp" for
> "Stack Pointer" is generally not available anywhere.

It's probably not available in his implementation either.
He was doing a trick: He took the address of the variable
he was just defining, and casted it to type X*.
Of course, casting X** to X* is a reinterpret_cast...

Avoiding the cast, he could have written:

void* pc = &pc;

[...]
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: Daniel Kaeps <please@no.spam>
Date: 1999/03/10
Raw View
E. Robert Tisdale wrote:
>
> It may be possible to avoid copying a huge object when your C++ compiler
> fails to optimize away copy constructors which may be elided.
> The following code helps to illustrate the problem:
>
[snip]
>
> X::X(const X& x): n(x.n) {      // copy constructor definition
>   cerr << "The copy constructor was called!" << endl;
>   X*    sp = (X*)&sp;           // stack pointer.
>   if (sp <= &x && &x <= this) { // x is on top of *this on the stack
>     p = x.p;                    // salvage the array and
>     ((X&)x).p = 0;              // prevent its destruction with x
>     cerr << "But the array copy was avoided." << endl;
>     }
>   else {
[...]

I think there is also a portable method for determining how deep
something is one stack. Just using a global (exactly one for each
thread) integer-variable that shows the *current* depth plus an integer
that shows the depth *for each object* (on the stack).

[snip]
>
> But just how portable is this method?
[snip]
>
> Does anyone know of any exceptions?

I think at least the intel-protected mode had pointers, where its
possible to determine whether they are on the stack
(Segment(Pointer)==SS or so), and how deep.

If the machine/compiler supports to determine these things, then there
should be a portable method, I would appreciate this.

Daniel Kaeps
(kaeps _at_ informatik.uni-leipzig.de)


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: "E. Robert Tisdale" <edwin@netwood.net>
Date: 1999/03/10
Raw View
AllanW@my-dejanews.com wrote:

> In article <36E48DBD.961264A@netwood.net>,
>   "E. Robert Tisdale" <edwin@netwood.net> wrote:
> > But just how portable is this method?
>
> If portable is the North Pole,
> then this method isn't even in the Milky Way.
>
> > I don't think that the standard actually specifies
> > that automatic variables are allocated on the process stack
> > or that a stack even exists.
>
> Not all computers that implement C++ have a hardware stack. The standard
> does make some requirements that need a stack for implementation; for
> instance, a function may be re-entrant, in which case it's auto variables
> have a separate instance. However, there is nothing to force the compiler
> to use the stack in all places that you would normally expect it.
>
> > But Stroustrup makes reference
> > to the stack and I believe that virtually every compiler
> > allocates storage for automatic variables on the stack.
>
> Conceptually, they all do. But the actual behind-the-scenes mechanics
> may be completely different. In any case, the symbol "sp" for
> "Stack Pointer" is generally not available anywhere.
>
> > In the typical implementation, the bottom of the stack
> > corresponds to the top of the virtual memory space
> > available to the process and the stack pointer decreases
> > as variables are pushed on the stack.
>
> This is by no means universal.
>
> > Does anyone know of any exceptions?
>
> IBM 370 has no stack. When you call a function at address 0x1000, you
> actually save the address of your next instruction at address 0x1000
> and then jump to address 0x1001. [I may have some detail wrong, because
> it's been 10 years since I worked on them, but the concept is right.]
>
> There is a portable alternative to the method you suggest.
> It's called Copy-On-Write (or COW) semantics.

Whoa Allan,

I wasn't soliciting a tutorial on copy-on-write.
No, cow is not the way to solve this problem.
It introduces an unnecessary level of indirection
and unnecessary overhead from reference counting
which I want to avoid.
I know that the stack is not a requirement for C++ and I said so.

I'm not sure if there are any architectures
except for a few old hp machines
which actually have a "hardware stack".  On most systems,
the process stack is usually just a segment of data memory
and it is customary to designate one of the address registers
to be the pointer to the "top-of-stack" in this data segment.
I really am looking for reliable information
about how popular C++ compilers use the stack.
I don't mean to put you down but, apparently,
you don't have any of this information
and I'm not looking for vague generalizations.
But thank you for your response anyway.
It may be of help to someone else.

E. Robert Tisdale <edwin@netwood.net>
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: "E. Robert Tisdale" <edwin@netwood.net>
Date: 1999/03/10
Raw View
Christopher Eltschka wrote:

> AllanW@my-dejanews.com wrote:
>
> > may be completely different. In any case, the symbol "sp" for
> > "Stack Pointer" is generally not available anywhere.
>
> It's probably not available in his implementation either.
> He was doing a trick: He took the address of the variable
> he was just defining, and casted it to type X*.
> Of course, casting X** to X* is a reinterpret_cast...
>
> Avoiding the cast, he could have written:
>
> void* pc = &pc;

You probably meant

    void* sp = &sp;

but I want to compare sp with this and &x which are of type X*
so I defined sp to be of type X* also
just to avoid (explicit) conversion of this and &x to type void*.

The copy constructor must not be inline'd
because the optimizing compiler may create and initialize sp
before either *this or x.

E. Robert Tisdale <edwin@netwood.net>
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: fjh@cs.mu.OZ.AU (Fergus Henderson)
Date: 1999/03/10
Raw View
James Kuyper <kuyper@wizard.net> writes:

>"E. Robert Tisdale" wrote:
>...
>> The copy constructor is called by my C++ compiler when it may be elided
>> to return by value from user defined function X f1(int) and
>> to initialize X y0 in function int main().
>> In both cases, because the argument of the copy constructor, x,
>> is on top of *this on the process stack and will be destroyed
>> without ever being referenced again after the copy constructor returns,
>> the copy constructor can salvage the array referenced by x
>> and avoid making a copy of it.  The following substitution
>> for the copy constructor works around the problem for my C++ compiler:
>>
>> //--------------------------------------------------------------------
>> X::X(const X& x): n(x.n) {      // copy constructor definition
>>   cerr << "The copy constructor was called!" << endl;
>>   X*    sp = (X*)&sp;           // stack pointer.
>>   if (sp <= &x && &x <= this) { // x is on top of *this on the stack
>>     p = x.p;                    // salvage the array and
>>     ((X&)x).p = 0;              // prevent its destruction with x
>>     cerr << "But the array copy was avoided." << endl;
>>     }
>>   else {
>>     p = new int[n];
>>     for (int j = 0; j < n; j++) // copy the array
>>       p[j] = x.p[j];
>>     }
>>   }
>> //--------------------------------------------------------------------
>...
>> But just how portable is this method?
>
>Pretty much completely non-portable. For one thing, you're doing
>inequality comparisons on pointers that aren't from the same object.

Yes, that might fail on segmented memory systems like large model 8086.

An alternative is to cast the pointers to an unsigned integer type
of sufficient size (i.e. like C9X's uintptr_t), if one exists,
before comparing them.  Since C++ doesn't have C9X's uintptr_t
and that macros that go with it, I suppose you'd need to use autoconf
(or something equivalent) to figure out whether an appropriate
integer type exists.  At a pinch you could just always cast to
unsigned long,

 typedef unsigned long my_uintptr_t;

and use something like

 bool assertion[sizeof(my_uintptr_t) >= sizeof(void *)];

to catch things at compile time if unsigned long isn't long enough.

(In theory of course even that is not sufficient, because unsigned long
might have holes...)

>> I don't think that the standard actually specifies
>> that automatic variables are allocated on the process stack
>> or that a stack even exists.  But Stroustrup makes reference
>
>Stack semantics are guaranteed. Whether or not a physical stack is used,
>and if so, whether addresses increase or decrease as you add items to
>the stack, are implementation-dependent.

Yes.  Relying on a particular stack direction is quite non-portable.
At very least you should check (either dynamically, or via autoconf
or something similar) the direction of stack growth.

This will still fail on machines like the Cray series (CRAY 1, CRAY 2,
CRAY X-MP, and CRAY Y-MP), because those machines use multiple stack segments.

In addition there are other problems with the proposed technique, e.g.
consider

 void foo(const X& x0);
  X x1(x0);
  X x2(x1);
  ...
 }

Even if the stack grows down, the compiler might well put x1 lower
in memory than x2, and then your code will do the wrong thing.

So I really wouldn't recommend this technique.

--
Fergus Henderson <fjh@cs.mu.oz.au>  |  "I have always known that the pursuit
WWW: <http://www.cs.mu.oz.au/~fjh>  |  of excellence is a lethal habit"
PGP: finger fjh@128.250.37.3        |     -- the last words of T. S. Garp.
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: "E. Robert Tisdale" <edwin@netwood.net>
Date: 1999/03/11
Raw View
Fergus Henderson wrote:

> James Kuyper <kuyper@wizard.net> writes:
>
> >"E. Robert Tisdale" wrote:
> >> But just how portable is this method?
> >
> >Pretty much completely non-portable. For one thing, you're doing
> >inequality comparisons on pointers that aren't from the same object.
>
> Yes, that might fail on segmented memory systems like large model 8086.
>
> An alternative is to cast the pointers to an unsigned integer type
> of sufficient size (i.e. like C9X's uintptr_t), if one exists,
> before comparing them.  Since C++ doesn't have
> C9X's uintptr_t and that macros that go with it,
> I suppose you'd need to use autoconf (or something equivalent)
> to figure out whether an appropriate integer type exists.
> At a pinch you could just always cast to unsigned long,
>
>         typedef unsigned long my_uintptr_t;
>
> and use something like
>
>         bool assertion[sizeof(my_uintptr_t) >= sizeof(void *)];
>
> to catch things at compile time if unsigned long isn't long enough.
>
> (In theory of course even that is not sufficient,
> because unsigned long might have holes...)
>
> >> I don't think that the standard actually specifies
> >> that automatic variables are allocated on the process stack
> >> or that a stack even exists.  But Stroustrup makes reference
> >
> >Stack semantics are guaranteed. Whether or not a physical stack is used,
> >and if so, whether addresses increase or decrease as you add items to
> >the stack, are implementation-dependent.
>
> Yes.  Relying on a particular stack direction is quite non-portable.
> At very least you should check
> (either dynamically, or via autoconf or something similar)
> the direction of stack growth.
> This will still fail on machines like the Cray series
> (CRAY 1, CRAY 2, CRAY X-MP, and CRAY Y-MP),
> because those machines use multiple stack segments.

I'm not going to worry about Cray.
I expect the Cray C++ compiler to elide the copy constructor.
Do you know whether it elides copy constructors ot not?

> In addition there are other problems with the proposed technique,
> e.g. consider
>
>         void foo(const X& x0);
>                 X x1(x0);
>                 X x2(x1);
>                 ...
>         }
>
> Even if the stack grows down,
> the compiler might well put x1 lower in memory than x2,
> and then your code will do the wrong thing.

Yes, I suppose that it is possible for the compiler to do this
but I don't know if any C++ compiler actually does do this.
Do you?

> So I really wouldn't recommend this technique.

I assume that you meant to add,
"if the program must be portable to all platforms."
But I'd be happy if I just knew which platforms
would permit me to use this or any other technique
to determine when the argument of a copy constructor is doomed
and the data which is referenced by it can be salvaged.

Anyway, Thank you for your reply.
I thought it was right on target.
E. Robert Tisdale <edwin@netwood.net>




[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Francis Glassborow <francis@robinton.demon.co.uk>
Date: 1999/03/13
Raw View
In article <36E6DF50.D0D6E975@netwood.net>, E. Robert Tisdale
<edwin@netwood.net> writes
>I assume that you meant to add,
>"if the program must be portable to all platforms."
>But I'd be happy if I just knew which platforms
>would permit me to use this or any other technique
>to determine when the argument of a copy constructor is doomed
>and the data which is referenced by it can be salvaged.

But the problem of portability even applies to different releases of the
same compiler on the same platform.

Francis Glassborow      Chair of Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA          +44(0)1865 246490
All opinions are mine and do not represent those of any organisation
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: "E. Robert Tisdale" <edwin@netwood.net>
Date: 1999/03/13
Raw View
Francis Glassborow wrote:

> In article <36E6DF50.D0D6E975@netwood.net>, E. Robert Tisdale
> <edwin@netwood.net> writes
> >I assume that you meant to add,
> >"if the program must be portable to all platforms."
> >But I'd be happy if I just knew which platforms
> >would permit me to use this or any other technique
> >to determine when the argument of a copy constructor is doomed
> >and the data which is referenced by it can be salvaged.
>
> But the problem of portability even applies to different releases
> of the same compiler on the same platform.

Of course.  I'd be much happier with a standard technique
for identifying "salvageable" objects.
But it appears to me that automatic storage is like the free store.
It is "mystery memory" and the process stack is a dirty little secret
that C++ programmers really shouldn't talk about
even though Stroustrup does mention it from time to time.

Do you agree?

E. Robert Tisdale <edwin@netwood.net>
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: Gabriel Dos_Reis <gdosreis@korrigan.inria.fr>
Date: 1999/03/13
Raw View
Francis Glassborow <francis@robinton.demon.co.uk> writes:

| In article <36E6DF50.D0D6E975@netwood.net>, E. Robert Tisdale
| <edwin@netwood.net> writes
| >I assume that you meant to add,
| >"if the program must be portable to all platforms."
| >But I'd be happy if I just knew which platforms
| >would permit me to use this or any other technique
| >to determine when the argument of a copy constructor is doomed
| >and the data which is referenced by it can be salvaged.
|
| But the problem of portability even applies to different releases of the
| same compiler on the same platform.

Right. But unless I'm mistaken Robert Tisdale don't care. He is asking
for a particular version of a particular compiler running on a
particular plateform that does what he wants.
(I'm not sure whether or not comp.std.c++ is the right place.)

--
Gabriel Dos Reis, dosreis@cmla.ens-cachan.fr
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: Francis Glassborow <francis@robinton.demon.co.uk>
Date: 1999/03/13
Raw View
In article <xaj6786ywnh.fsf@korrigan.inria.fr>, Gabriel Dos_Reis
<gdosreis@korrigan.inria.fr> writes
>Francis Glassborow <francis@robinton.demon.co.uk> writes:
>
>| In article <36E6DF50.D0D6E975@netwood.net>, E. Robert Tisdale
>| <edwin@netwood.net> writes
>| >I assume that you meant to add,
>| >"if the program must be portable to all platforms."
>| >But I'd be happy if I just knew which platforms
>| >would permit me to use this or any other technique
>| >to determine when the argument of a copy constructor is doomed
>| >and the data which is referenced by it can be salvaged.
>|
>| But the problem of portability even applies to different releases of the
>| same compiler on the same platform.
>
>Right. But unless I'm mistaken Robert Tisdale don't care. He is asking
>for a particular version of a particular compiler running on a
>particular plateform that does what he wants.
>(I'm not sure whether or not comp.std.c++ is the right place.)

read what I quoted.  He did not talk about a particular version but
'which platforms would permit...'
>

Francis Glassborow      Chair of Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA          +44(0)1865 246490
All opinions are mine and do not represent those of any organisation


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: jcoffin@taeus.com (Jerry Coffin)
Date: 1999/03/14
Raw View
In article <36E9DC90.59FA9DA8@netwood.net>, edwin@netwood.net says...

[ ... ]

> Of course.  I'd be much happier with a standard technique
> for identifying "salvageable" objects.
> But it appears to me that automatic storage is like the free store.
> It is "mystery memory" and the process stack is a dirty little secret
> that C++ programmers really shouldn't talk about
> even though Stroustrup does mention it from time to time.
>
> Do you agree?

I, for one, do not.  It's no secret that many implementations of C++
use a stack to implement automatic variables, and a heap to implement
the free store.  I don't think there's a thing wrong with programmers
discussing that, or even thinking in those terms about how things
typically work.

OTOH, there's also no secret of the fact that _some_ machines (most
IBM mainframes for a prominent example) do NOT use stacks for much of
anything.

Now, as far as the standard goes, _most_ of these things could just
about as easily fall under the notorious as-if rule -- as long as the
implementation _acts_ like it's using a stack, it doesn't really
matter to a portable program that it's imitating a stack in some other
fashion.  By this figuring, it would be perfectly fine for the
standard to talk about the implementation using a stack for automatic
storage.

IMO, this would be a mistake though: in particular, it could make the
exposition LESS rather than more informative, and quite possibly more
difficult to sort out some of the details as well.

Just for example, the standard says that if you compare two pointers
to each other, the results are defined if and only if the pointers are
into parts of the same object.  If the standard talked about using a
stack, it could easily leave the impression that "the stack" is a
single object, and comparing pointers to things that are (in reality)
different objects was defined as long as both were automatic
variables, which, by extension, are allocated as parts of the single
"stack" object.

Thus, without extensive reading about the definitions of the words
involved, a programmer reading the standard would typically be badly
mislead if it treated a stack as a synonym for automatic storage.


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Daniel Kaeps <please@no.spam>
Date: 1999/03/14
Raw View
E. Robert Tisdale schrieb:
>
> It may be possible to avoid copying a huge object when your C++ compiler
> fails to optimize away copy constructors which may be elided.
> The following code helps to illustrate the problem:
[snip]

Are you sure, that the magic really works, e.g. if X is itself part of a
vector? The copy c-tor may be called by the vector implementation
assuming a deep copy... What is if X is used for exception-classes,
inside an exception handler etc. (I don't see the correctness on the
first look).

If you don't need to return your objects as *function return value*, you
could also use ref.-parameters to return the objects. Then it's really
easy to call a function, lets say, "relocate", to return your value
(instead of using "operator="). By explicit distinction between copy and
relocation, you can avoid the magic.

Then the copy can be avoided also for f1() in your example.

(I use for function return values usually only *booleans* (or pointer or
references) because of the unodd C++ function return value concept,
since I've realized some of the problems (by reading Scott Meyers book
"Effective C++ programming")).

Daniel Kaeps
(kaeps _at_ informatik.uni-leipzig.de)


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Francis Glassborow <francis@robinton.demon.co.uk>
Date: 1999/03/14
Raw View
In article <36E9DC90.59FA9DA8@netwood.net>, E. Robert Tisdale
<edwin@netwood.net> writes
>Of course.  I'd be much happier with a standard technique
>for identifying "salvageable" objects.
>But it appears to me that automatic storage is like the free store.
>It is "mystery memory" and the process stack is a dirty little secret
>that C++ programmers really shouldn't talk about
>even though Stroustrup does mention it from time to time.
>
>Do you agree?

I am not sure exactly what you mean.  automatic storage can be provided
in more than one way.  I know at least one oldish implementation of C++
that uses completely different mechanisms for release and debug versions
(the debug versions never re-use memory so hanging pointers etc. can be
detected.)  I do not consider it a 'dirty secret' but a desirable
abstraction.


Francis Glassborow      Chair of Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA          +44(0)1865 246490
All opinions are mine and do not represent those of any organisation
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: Bruce Visscher <bvisscher@mindspring.com>
Date: 1999/03/14
Raw View
"E. Robert Tisdale" wrote:

>
> Of course.  I'd be much happier with a standard technique
> for identifying "salvageable" objects.
> But it appears to me that automatic storage is like the free store.
> It is "mystery memory" and the process stack is a dirty little secret
> that C++ programmers really shouldn't talk about
> even though Stroustrup does mention it from time to time.

I think alloca may be what you are looking for.  I've never used it myself,
but I think it's basically a stack based version of malloc (someone with more
experience can comment).  Perhaps alloca should be standardized, I don't
know.  If so then I think it should be a C standard not a C++ standard.

Bruce Visscher
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: James Kuyper <kuyper@wizard.net>
Date: 1999/03/09
Raw View
"E. Robert Tisdale" wrote:
...
> The copy constructor is called by my C++ compiler when it may be elided
> to return by value from user defined function X f1(int) and
> to initialize X y0 in function int main().
> In both cases, because the argument of the copy constructor, x,
> is on top of *this on the process stack and will be destroyed
> without ever being referenced again after the copy constructor returns,
> the copy constructor can salvage the array referenced by x
> and avoid making a copy of it.  The following substitution
> for the copy constructor works around the problem for my C++ compiler:
>
> //--------------------------------------------------------------------
> X::X(const X& x): n(x.n) {      // copy constructor definition
>   cerr << "The copy constructor was called!" << endl;
>   X*    sp = (X*)&sp;           // stack pointer.
>   if (sp <= &x && &x <= this) { // x is on top of *this on the stack
>     p = x.p;                    // salvage the array and
>     ((X&)x).p = 0;              // prevent its destruction with x
>     cerr << "But the array copy was avoided." << endl;
>     }
>   else {
>     p = new int[n];
>     for (int j = 0; j < n; j++) // copy the array
>       p[j] = x.p[j];
>     }
>   }
> //--------------------------------------------------------------------
...
> But just how portable is this method?

Pretty much completely non-portable. For one thing, you're doing
inequality comparisons on pointers that aren't from the same object.

> I don't think that the standard actually specifies
> that automatic variables are allocated on the process stack
> or that a stack even exists.  But Stroustrup makes reference

Stack semantics are guaranteed. Whether or not a physical stack is used,
and if so, whether addresses increase or decrease as you add items to
the stack, are implementation-dependent.


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: AllanW@my-dejanews.com
Date: 1999/03/09
Raw View
In article <36E48DBD.961264A@netwood.net>,
  "E. Robert Tisdale" <edwin@netwood.net> wrote:
> It may be possible to avoid copying a huge object when your C++ compiler
> fails to optimize away copy constructors which may be elided.
> The following code helps to illustrate the problem:
[snip]
> The copy constructor is called by my C++ compiler when it may be elided
> to return by value from user defined function X f1(int) and
> to initialize X y0 in function int main().
> In both cases, because the argument of the copy constructor, x,
> is on top of *this on the process stack and will be destroyed
> without ever being referenced again after the copy constructor returns,
> the copy constructor can salvage the array referenced by x
> and avoid making a copy of it.  The following substitution
> for the copy constructor works around the problem for my C++ compiler:
[snip]
> But just how portable is this method?

If portable is the North Pole, then this method isn't even in the
Milky Way.

> I don't think that the standard actually specifies
> that automatic variables are allocated on the process stack
> or that a stack even exists.

Not all computers that implement C++ have a hardware stack. The standard
does make some requirements that need a stack for implementation; for
instance, a function may be re-entrant, in which case it's auto variables
have a separate instance. However, there is nothing to force the compiler
to use the stack in all places that you would normally expect it.

> But Stroustrup makes reference
> to the stack and I believe that virtually every compiler
> allocates storage for automatic variables on the stack.

Conceptually, they all do. But the actual behind-the-scenes mechanics
may be completely different. In any case, the symbol "sp" for
"Stack Pointer" is generally not available anywhere.

> In the typical implementation, the bottom of the stack
> corresponds to the top of the virtual memory space
> available to the process and the stack pointer decreases
> as variables are pushed on the stack.

This is by no means universal.

> Does anyone know of any exceptions?

IBM 370 has no stack. When you call a function at address 0x1000, you
actually save the address of your next instruction at address 0x1000
and then jump to address 0x1001. [I may have some detail wrong, because
it's been 10 years since I worked on them, but the concept is right.]

There is a portable alternative to the method you suggest. It's called
Copy-On-Write (or COW) semantics. By the way, I didn't invent this
technique.

    Does anyone know who invented Copy-On-Write? I would love to
    give due credit to this true genius.

The way that COW works is to have an embedded class X::impl which
contains all of the "real" data elements, plus a reference count,
while X contains only a pointer to the X::impl class. When X is copied,
all we do is copy the pointer and increment the reference count. When X
is destroyed we decrement the reference count, but we don't delete the
X::impl unless there are no other references.

When you return an X by value, you make a copy of the X::impl pointer,
and the X::impl reference count jumps to 2. Then the original X object
is destroyed, resetting the X::impl reference count to 1. You now have
a new X object which points to the original X::impl object, which did
not have to be copied.

To be robust, we must consider cases where the copying was done
intentionally, and both copies must survive. Once we attempt any
operation that would modify the data, we first check the reference
count. If the count is greater than zero, we must first make a
local copy of the data and adjust the reference counts. Then we are
free to modify our local data, safe in the knowledge that it
(currently) isn't shared with other instances of X.

Here is a sample implementation which assumes single-threading.

    #include<iostream>
    using std::cout;
    using std::endl;

    class X {
        class impl {
            int*    p;      // Points to array
            int     n;      // Size of array p[]
            int     r;      // Reference count

            int     id;     // For exposition
            static int count; // For exposition
        public:
            explicit impl(int k)
                : p(new int[k])
                , n(k)
                , r(1)
                , id(++count)
            { cout << "Array " << id << " created in "; }
            impl(impl&i)
                : p(new int[i.n])
                , n(i.n)
                , r(1)
                , id(++count)
            { cout << "Array " << id << " deep-copy from "
                << i.id << " for";
              memcpy(p, i.p, n*sizeof(*p));
              i.removeRef(); }
            ~impl() { delete[] p;
              cout << ", array " << id << " deleted!"; }
            void addRef() { ++r; }
            void removeRef() { if (0 == --r) delete this; }
            bool needCow() { return 1 != r; }
            int length() const { return n; }
            int get(int j) const { return p[j]; }
            int &get(int j) { return p[j]; }
            // Pure exposition
            void tell() { cout<<"array "<<id; }
        } *i;               // Points to implementation
        const char*name; // Normally wouldn't be used
    protected:
        // void cow() { if (i->needCow()) i=new impl(*i); }
        // Added parameter for exposition purposes:
        void cow(const char*x) {
            if (i->needCow()) {
                i = new impl(*i);
                cout <<" COW() from " << x << endl;
            } }
    public:
        X() : i(new impl(10)), name("unknown")
            { cout << "default constructor!" << endl; }
        explicit X(int k,const char*n) : i(new impl(k)), name(n)
            { cout << "int constructor!" << endl; }
        X(const X&x) : i(x.i), name("unknown copy")
            { i->addRef();
              cout << "Copy constructor(" << x.name << ") called" << endl; }
        ~X() {
            cout << "Destructor for " << name << "(";
            i->tell();
            cout << ") called";
            i->removeRef();
            cout<<endl; }
        X&operator=(X&x) {
            cout << "Assignment";
            x.i->addRef();
            i->removeRef();
            i=x.i;
            cout<<endl;
            return *this; }
        // For exposition only
        void setname(const char*n) { name=n; }
        // const functions simply dispatch to X::impl class
        // Note we don't return pointers or references, just values
        int length() const { return i->length(); }
        int operator[](int j) const { return i->get(j); }
        // non-const functions must apply cow() first, then dispatch
        //int &operator[](int j) { cow(); return i->get(j); }
        // For exposition we pass a parameter to cow()
        int &operator[](int j) { cow("op[]"); return i->get(j); }
    };
    int X::impl::count = 0;

    // user defined functions
    inline X f0(int n) {
      cout << "The copy constructor may be called for f0(int)."
        << endl;
      return X(n,"f0::anonymous");
    }

    inline X f1(int n) {
      X x(n,"f1::x");
      for (int j = 0; j < n; j++) x[j] = j;
      cout << "The copy constructor may be called for f1(int)."
        << endl;
      return x;
    }

    inline X g0(X x) {

      cout << "The copy constructor may be called for g0(X)."
        << endl;
      return x;
    }

    inline X g1(const X& x) {
      cout << "The copy constructor must be called for g1(const X&)."
        << endl;
      return x;
    }

    int main () {
      cout << endl
           << "The copy constructor may be called for X x0 = f0(32);"
        << endl;
      X x0 = f0(32);
      x0.setname("x0");

      cout << endl
           << "The copy constructor may be called for X x1 = f1(32);"
        << endl;
      X x1 = f1(32);
      x1.setname("x1");

      cout << endl
           << "The copy constructor may be called for X y0 = g0(f0(32));"
        << endl;
      X y0 = g0(f0(32));
      y0.setname("y0");

      cout << endl
           << "The copy constructor may be called for X y1 = g1(x1);"
        << endl;
      X y1 = g1(x1);
      y1.setname("y1");

      cout << endl
           << "The copy constructor must be called for X z0 = y0;"
        << endl;
      X z0 = y0;
      z0.setname("z0");

      // I added this section to demonstrate making a copy on purpose
      cout << endl
           << "The copy constructor must be called for X allanw(z0);"
        << endl;
      X allanw(z0);
      allanw.setname("allanw");
      cout << endl
      cout << endl
           << "About to modify allanw but not z0, need deep copy"
        << endl;
      allanw[1]=5;

      cout << endl
           << "All done, must destruct all 6 X objects"
        << endl;
      return 0;
    }

    /*** Output
      The copy constructor may be called for X x0 = f0(32);
      The copy constructor may be called for f0(int).
      Array 1 created in int constructor!

      The copy constructor may be called for X x1 = f1(32);
      Array 2 created in int constructor!
      The copy constructor may be called for f1(int).
      Copy constructor(f1::x) called
      Destructor for f1::x(array 2) called

      The copy constructor may be called for X y0 = g0(f0(32));
      The copy constructor may be called for f0(int).
      Array 3 created in int constructor!
      The copy constructor may be called for g0(X).
      Copy constructor(f0::anonymous) called
      Destructor for f0::anonymous(array 3) called

      The copy constructor may be called for X y1 = g1(x1);
      The copy constructor must be called for g1(const X&).
      Copy constructor(x1) called

      The copy constructor must be called for X z0 = y0;
      Copy constructor(y0) called

      The copy constructor must be called for X allanw(z0);
      Copy constructor(z0) called

      About to modify allanw but not z0, need deep copy
      Array 4 deep-copy from 3 for COW() from op[]

      All done, must destruct all 6 X objects
      Destructor for allanw(array 4) called, array 4 deleted!
      Destructor for z0(array 3) called
      Destructor for y1(array 2) called
      Destructor for y0(array 3) called, array 3 deleted!
      Destructor for x1(array 2) called, array 2 deleted!
      Destructor for x0(array 1) called, array 1 deleted!
    ***/

Note that there is nothing in this whole program which is machine-
or compiler-dependant. However, we do assume single-threaded code.
(To be safe in an MT environment, we would need to wrap a "critical
section" or similar around the entire contents of member function
cow().)

As with any other container class, certain operations are not valid.
For instance, you should not have any reference or pointer variables
which point to an element in the X class:
    int *pint = &x[5];
    X y = x;
    ++(*pint); // ERROR! Modifies both X objects!

----
AllanW@my-dejanews.com is a "Spam Magnet" -- never read.
Please reply in USENET only, sorry.

-----------== Posted via Deja News, The Discussion Network ==----------
http://www.dejanews.com/       Search, Read, Discuss, or Start Your Own


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: "E. Robert Tisdale" <edwin@netwood.net>
Date: 1999/03/09
Raw View
It may be possible to avoid copying a huge object when your C++ compiler
fails to optimize away copy constructors which may be elided.
The following code helps to illustrate the problem:

$ cat test.cc
//--------------------------------------------------------------------
#include<iostream>

class X {                       // a dynamic array class
  public:
  // representation
  int*  p;                      // pointer to a huge array
  int   n;                      // length of the huge array
  // constructors
  explicit
  X(int k):                     // explicit constructor definition
    p(new int[k]), n(k) { }
  X(const X& x);                // copy constructor declaration
 ~X(void) {                     // destructor definition
    if (p) delete [] p; }
  // functions
  int   length(void) const { return n; }
  // operators
  int&  operator [](int j) const { return p[j]; }
  int&  operator [](int j)       { return p[j]; }
  };

X::X(const X& x):               // copy constructor definition
  p(new int[x.n]), n(x.n) {
  cerr << "The copy constructor was called!" << endl;
  for (int j = 0; j < n; j++)   // copy the array
    p[j] = x.p[j];
  }

// user defined functions

inline
X       f0(int n) {
  cerr << "The copy constructor may be called for f0(int)." << endl;
  return X(n);
  }
inline
X       f1(int n) {
  X     x(n);
  for (int j = 0; j < n; j++)   // Initialize x.
    x[j] = j;
  cerr << "The copy constructor may be called for f1(int)." << endl;
  return x;
 }
inline
X       g0(X x) {
  cerr << "The copy constructor may be called for g0(X)." << endl;
  return x;
  }
inline
X       g1(const X& x) {
  cerr << "The copy constructor must be called for g1(const X&)." << endl;
  return x;
  }

int
main () {
  cerr << endl
       << "The copy constructor may be called for X x0 = f0(32);" << endl;
  X x0 = f0(32);
  cerr << endl
       << "The copy constructor may be called for X x1 = f1(32);" << endl;
  X x1 = f1(32);
  cerr << endl
       << "The copy constructor may be called for X y0 = g0(f0(32));" << endl;
  X y0 = g0(f0(32));
  cerr << endl
       << "The copy constructor may be called for X y1 = g1(x1);" << endl;
  X y1 = g1(x1);
  cerr << endl
       << "The copy constructor must be called for X z0 = y0;" << endl;
  X z0 = y0;
  return 0;
  }
//--------------------------------------------------------------------
$
$ uname -mrs
Linux 2.0.36 i686
$ g++ --version
egcs-2.90.29 980515 (egcs-1.0.3 release)
$ g++ -felide-constructors -O2 -o test test.cc
$ ./test

The copy constructor may be called for X x0 = f0(32);
The copy constructor may be called for f0(int).

The copy constructor may be called for X x1 = f1(32);
The copy constructor may be called for f1(int).
The copy constructor was called!

The copy constructor may be called for X y0 = g0(f0(32));
The copy constructor may be called for f0(int).
The copy constructor may be called for g0(X).
The copy constructor was called!

The copy constructor may be called for X y1 = g1(x1);
The copy constructor must be called for g1(const X&).
The copy constructor was called!

The copy constructor must be called for X z0 = y0;
The copy constructor was called!
$
//--------------------------------------------------------------------

The copy constructor is called by my C++ compiler when it may be elided
to return by value from user defined function X f1(int) and
to initialize X y0 in function int main().
In both cases, because the argument of the copy constructor, x,
is on top of *this on the process stack and will be destroyed
without ever being referenced again after the copy constructor returns,
the copy constructor can salvage the array referenced by x
and avoid making a copy of it.  The following substitution
for the copy constructor works around the problem for my C++ compiler:

//--------------------------------------------------------------------
X::X(const X& x): n(x.n) {      // copy constructor definition
  cerr << "The copy constructor was called!" << endl;
  X*    sp = (X*)&sp;           // stack pointer.
  if (sp <= &x && &x <= this) { // x is on top of *this on the stack
    p = x.p;                    // salvage the array and
    ((X&)x).p = 0;              // prevent its destruction with x
    cerr << "But the array copy was avoided." << endl;
    }
  else {
    p = new int[n];
    for (int j = 0; j < n; j++) // copy the array
      p[j] = x.p[j];
    }
  }
//--------------------------------------------------------------------

$ g++ -felide-constructors -O2 -o test test.cc
$ ./test

The copy constructor may be called for X x0 = f0(32);
The copy constructor may be called for f0(int).

The copy constructor may be called for X x1 = f1(32);
The copy constructor may be called for f1(int).
The copy constructor was called!
But the array copy was avoided.

The copy constructor may be called for X y0 = g0(f0(32));
The copy constructor may be called for f0(int).
The copy constructor may be called for g0(X).
The copy constructor was called!
But the array copy was avoided.

The copy constructor may be called for X y1 = g1(x1);
The copy constructor must be called for g1(const X&).
The copy constructor was called!

The copy constructor must be called for X z0 = y0;
The copy constructor was called!
$

But just how portable is this method?
I don't think that the standard actually specifies
that automatic variables are allocated on the process stack
or that a stack even exists.  But Stroustrup makes reference
to the stack and I believe that virtually every compiler
allocates storage for automatic variables on the stack.
In the typical implementation, the bottom of the stack
corresponds to the top of the virtual memory space
available to the process and the stack pointer decreases
as variables are pushed on the stack.

Does anyone know of any exceptions?

Thanks in advance, E. Robert Tisdale <edwin@netwood.net>
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]