www.digitalmars.com         C & C++   DMDScript  

c++ - Order of operator execution at function exit

reply MR <web rnetf.com> writes:
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.
Jan 26 2010
parent reply Heinz Saathoff <newshsaat arcor.de> writes:
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
Jan 27 2010
parent reply MR <web rnetf.com> writes:
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
Jan 27 2010
parent reply Bertel Brander <bertel post4.tele.dk> writes:
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.
Jan 27 2010
parent reply Heinz Saathoff <newshsaat arcor.de> writes:
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
Jan 28 2010
parent Bertel Brander <bertel post4.tele.dk> writes:
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
Jan 28 2010