Thread overview
Order of operator execution at function exit
Jan 26, 2010
MR
Jan 27, 2010
Heinz Saathoff
Jan 27, 2010
MR
Jan 27, 2010
Bertel Brander
Jan 28, 2010
Heinz Saathoff
Jan 28, 2010
Bertel Brander
January 26, 2010
Given the class String.

String contains functions including constructors, a destructor, nd a number of general functions that includes a char* casting operator and an assignment operator=() function.

Define two functions in a program:

char* charFn()
{
   String rStr;

   <Process to create data in rStr>
   <rStr contains corrrect code here>
   return (char*)rStr;
}


String strFn()
{
   String rStr;

   <Process to create data in rStr>
   <rStr contains corrrect code here>
   return rStr;
}


-- MAIN Code --


String   str;

str = strFn();

// Code works correctly - str contains expected data.

str = charFn();

// This FAILS - str is empty.

Investigations show that in the charFn() case, the destructor for 'rStr' is called BEFORE the completion of the assignment operator in the main code.

This is NOT the case for the call to strFn(). In this case, the assignment operator is called before the destructor for the internal variable 'rStr'.

I believe that this is incorrect semantics. The assignment operation should be invoked before the destructor for the data being returned via the (char*) casting operator.
January 27, 2010
Hello,

MR wrote...
> Given the class String.
> 
> String contains functions including constructors, a destructor, nd a number of general functions that includes a char* casting operator and an assignment operator=() function.
> 
> Define two functions in a program:
> 
> char* charFn()
> {
>    String rStr;
> 
>    <Process to create data in rStr>
>    <rStr contains corrrect code here>
>    return (char*)rStr;
> }

When charFn() ends rStr is no longer valid (destroyed by Destructor). The compiler can implement charFn() this way:

   void charFn(char* & __result)
   {
       String rStr;
       // .....
       __result = (char*)rStr;
       // leave charFn, destruct rStr
       rStr.~rStr();
       // Storage allocated by rStr is freed
   }


   void main()
   {
       char *cpt;
       charFn(cpt);   // compiler generates this from cpt=charFn();
       // the call to charFn is a sequence point so all
       // objects in charFn are destroyed now. That's why
       // cpt points to non existing memory now (assuming
       // String class did not use static or non freed heap space
   }

> 
> String strFn()
> {
>    String rStr;
> 
>    <Process to create data in rStr>
>    <rStr contains corrrect code here>
>    return rStr;
> }

again compiler generated code similar as above:


   void strFn(String & __result)
   {
       String rStr;
       // .....
       __result.operator=(rStr);     // copy rStr
       // leave strFn, destruct rStr
       rStr.~rStr();
       // Storage allocated by rStr is freed,
	// but __result still has copy of rStr!
   }


   void main()
   {
       String str;	  // construct by default constructor
       strFn(str);    // compiler generates this from str=strFn();
       // strFn is a sequence point but this has no influence
       // on str. So str is still valid and can be used here
   }


> 
> -- MAIN Code --
> 
> 
> String   str;
> 
> str = strFn();
> 
> // Code works correctly - str contains expected data.

so it should.

> 
> str = charFn();
> 
> // This FAILS - str is empty.

undifined because returnd char* may point to
arbitrary memory location.


> 
> Investigations show that in the charFn() case, the destructor for 'rStr' is called BEFORE the completion of the assignment operator in the main code.

Right, the destructor is called at last part of the function. Function return defines a sequnce point (see ISO 14882, 1.19.17)


> This is NOT the case for the call to strFn(). In this case, the assignment operator is called before the destructor for the internal variable 'rStr'.

Because the returned String is returned by value! if it were returned by reference it would also be undefined.



> I believe that this is incorrect semantics. The assignment operation should be invoked before the destructor for the data being returned via the (char*) casting operator.

The behaviour is correct from the standard point of view.


- Heinz
January 27, 2010
Thanks for the clarification. I now must buy a copy of the standard. I certainly
believe your interpretation; I just think the standard is wrong :)
In all cases where an object is returned through an expression, the object life
should extend to the completion of the expression.

In my case if I were to use a expression such as:

char* ptr;

ptr = charFn();

...then use ptr later, it should be undefined.

For the case under consideration

String nStr;

nStr = charFn();

... where the semantics of the assignment makes a copy of the char* data, the
object, were the object life to extend through the assignment, there would be no
problem.
It seems logical for the semantics to follow this logic rather than that defined
by the standard.

Again, thanks for the clarification.

Milt


January 27, 2010
MR skrev:
> Thanks for the clarification. I now must buy a copy of the standard. I certainly
> believe your interpretation; I just think the standard is wrong :)

The standard is always, by definition, right.

> In all cases where an object is returned through an expression, the object life
> should extend to the completion of the expression.

That is not the case for C or C++, more over it would
be virtually impossible to do in C and C++,

> In my case if I were to use a expression such as:
> 
> char* ptr;
> 
> ptr = charFn();
> 
> ...then use ptr later, it should be undefined.
> 
> For the case under consideration
> 
> String nStr;
> 
> nStr = charFn();
> 
> ... where the semantics of the assignment makes a copy of the char* data, the
> object, were the object life to extend through the assignment, there would be no
> problem.
> It seems logical for the semantics to follow this logic rather than that defined
> by the standard.

If your code should work as you think it should, when should
the object then be freed? The compiler has no ways to know
when to free the object.

Lets look at a bit more advanced example:

one.h:
struct SomeStruct
{
   char* p;
};
void func(SomeStruct& someStruct);

one.cpp:
char* charFn()
{
   String rStr;

   <Process to create data in rStr>
   <rStr contains corrrect code here>
   return (char*)rStr;
}
void func(SomeStruct& someStruct)
{
  if(something)
     someStruct.p = charFn();
}

another.cpp:
void pop()
{
   SomeStruct someStruct;
   func(someStruct);
}

If the compiler were to make a copy of the string in charFn,
it must free that string at some point. In the example above
that would have to happen in the function pop, but the compiler
has absolutely no way of knowing if someStruct.p points at
something that it must free.

There is to ways to overcome this, you could call new[]
in charFn and delete[] in pop. Or the more easy way,
make charFn return a string.

Also note that the cast in charFn is not valid.


January 28, 2010
Hello,

Bertel Brander wrote...
> 
> Also note that the cast in charFn is not valid.

His own String class might have a "operator char*()"
as a user defined conversion operator defined.


- Heinz
January 28, 2010
Heinz Saathoff skrev:
> Hello,
> 
> Bertel Brander wrote...
>> Also note that the cast in charFn is not valid.
> 
> His own String class might have a "operator char*()"
> as a user defined conversion operator defined. 

Sorry, my fault, I had read String as std::string