October 18, 2007
Hello,

DQNOK wrote...

> Heinz: I reworked your code a little to make it more reflective of my actual code.  I've attached it but don't know if it'll come thru since I've never used attachments before.

Got it.


> Surprisingly, it works as expected; the destructors are called when I think they should be called; just like in your code.

It also does here.


> Also
> very surprising is that there are NO CALLS TO THE COPY
> CONSTRUCTOR.  I thought that when an object was passed as a
> function parameter (like to printf), the copy constructor was
> always called!  ALWAYS CALLED!  Therefore, to make things work
> correctly, my original copy constructor alerted the copy that it
> was in fact, a copy; a hack I needed to prevent the copy from
> misbehaving when it got destroyed.  I'm thinking THIS may be the
> actual source of my mis-behaving code, not that the destructor is
> not being called.
> 
> Is the compiler permitted to optimize away a copy-constructor call???

Yes, constructor-optimization is allowed, but not required, in certain cases. I assume it's also allowed here (your second printf). It would't be allowed if you've passed 'm' directly.


- Heinz
October 18, 2007
Hello again,

Just for your information, another problem when passing objects to ellipsis (as in printf). Compiling and running this code:


   class MyType
   {
   public:
       MyType(char *s) : str(s), len(strlen(s))
         { printf("ctor\n"); }
       MyType(const MyType &m) : str(m.str), len(m.len)
         { printf("copy_ctor\n"); }
       ~MyType() {  printf("dtor\n");

       char *str;
       int len;
   private:
      MyType();
   };//MyType


   void Foo(int x, ...) {}

   int main()
   {
     char c_str[] = "Ein C-String";
     MyType mt(c_str);
     Foo(1, mt);
     return 0;
   }


will give this output:

As you see there is one destructor call missing. The reason is that Foo() should call the destructor but due to the ellipsis doesn't know what was passed to it as second parameter.


PC-Lint explains it this way:

      Passing struct 'Symbol' to ellipsis  -- A struct is being
      passed to a function at a parameter position identified by
      an ellipsis.  For example:

                void g()
                 {
                 struct A { int a; } x;
                 void f( int, ... );
                 f( 1, x );
                 ...
                 }

      This is sufficiently unusual that it is worth pointing out
      on the likelihood that this is unintended.  The situation
      becomes more severe in the case of a Non-POD struct  [10].
      In this case the behavior is considered undefined.



- Heinz
October 22, 2007
== Quote from Heinz Saathoff (newshsaat@arcor.de)'s article
> Yes, constructor-optimization is allowed, but not required, in certain cases.

Thanks, Heinz.  I didn't know that.

I'm in the process of redesigning my code to work regardless of whether the constructor gets called.

For reasons like this issue we've been discussing (different compilers behaving differently), I abandoned C++ about a dozen years ago (before STL), and went to coding in straight C.  My productivity soared once I stopped trying to "think in objects". Guess my brain is better wired for procedural programming.

More recently though, I've been lured back to the dark side, and have been dabbling in OOP.  There are nuances about C++ that I just didn't remember (like optimizing out constructor calls).  I recently purchased "Effective C++" by Scott Meyers, and guess I'll be reading that too.

Before C++ was all the rage, I eagerly anticipated PC Lint's ad in each issue of the C Users Journal.  I always spotted the error within seconds.  Once PC Lint supported C++ however, I could barely even understand their explanation of why the code was errant.  Like I said, not wired for it (or maybe I'm just being lazy and don't want to learn it...)
October 22, 2007
== Quote from Heinz Saathoff (newshsaat@arcor.de)'s article
>
> Just for your information, another problem when passing objects
> to ellipsis (as in printf). Compiling and running this code:
>    class MyType
>    {
>    public:
>        MyType(char *s) : str(s), len(strlen(s))
>          { printf("ctor\n"); }
>        MyType(const MyType &m) : str(m.str), len(m.len)
>          { printf("copy_ctor\n"); }
>        ~MyType() {  printf("dtor\n");
>        char *str;
>        int len;
>    private:
>       MyType();
>    };//MyType
>    void Foo(int x, ...) {}
>    int main()
>    {
>      char c_str[] = "Ein C-String";
>      MyType mt(c_str);
>      Foo(1, mt);
>      return 0;
>    }
> ...
> As you see there is one destructor call missing. The reason is
> that Foo() should call the destructor but due to the ellipsis
> doesn't know what was passed to it as second parameter.

I don't think it's Foo() that should call the destructor, is it?
Since it is main() that calls the constructor to make the
temporary available for the call to Foo(), then it should be main
() that also calls the destructor .. I think.  At least, this is
the way VC works (yeah, I know they're not the standard, but it
makes sense that it should work this way).  This looks to me like
a possible error in DMC.

Turns out, this is exactly what was causing the problem in my original code.  I THOUGHT that the destructor call for the temporary was being deferred, but in fact, it was never being called at all!  After enough printf's I finally figured it out.

David
November 01, 2007
Hello,


DQNOK wrote ...
> == Quote from Heinz Saathoff (newshsaat@arcor.de)'s article
> >
> > Just for your information, another problem when passing objects
> > to ellipsis (as in printf). Compiling and running this code:
> >    class MyType
> >    {
> >    public:
> >        MyType(char *s) : str(s), len(strlen(s))
> >          { printf("ctor\n"); }
> >        MyType(const MyType &m) : str(m.str), len(m.len)
> >          { printf("copy_ctor\n"); }
> >        ~MyType() {  printf("dtor\n");
> >        char *str;
> >        int len;
> >    private:
> >       MyType();
> >    };//MyType
> >    void Foo(int x, ...) {}
> >    int main()
> >    {
> >      char c_str[] = "Ein C-String";
> >      MyType mt(c_str);
> >      Foo(1, mt);
> >      return 0;
> >    }
> > ...
> > As you see there is one destructor call missing. The reason is
> > that Foo() should call the destructor but due to the ellipsis
> > doesn't know what was passed to it as second parameter.
> 
> I don't think it's Foo() that should call the destructor, is it?
> Since it is main() that calls the constructor to make the
> temporary available for the call to Foo(), then it should be main
> () that also calls the destructor .. I think.  At least, this is
> the way VC works (yeah, I know they're not the standard, but it
> makes sense that it should work this way).  This looks to me like
> a possible error in DMC.

I have posted this question in the german usenet group
de.comp.lang.iso-c++
and got the answer that passing a non-POD to ... is undefined behaviour.
Falk Tannhäuser quoted this text from the standard:
  [expr.call]/7:
   "When there is no parameter for a given argument, the argument is
   passed in such a way that the receiving function can obtain the
   value of the argument by invoking va_arg. The lvalue-to-rvalue,
   array-to-pointer, and function-to-pointer standard conversions
   are performed on the argument expression. After these conversions,
   if the argument does not have arithmetic, enumeration, pointer,
   pointer to member, or class type, the program is ill-formed. If
   the argument has a non-POD class type, the behavior is undefined."


- Heinz
November 02, 2007
== Quote from Heinz Saathoff (newshsaat@arcor.de)'s article
> I have posted this question in the german usenet group
> de.comp.lang.iso-c++
> and got the answer that passing a non-POD to ... is undefined
behaviour.
> Falk Tannhäuser quoted this text from the standard:
>   [expr.call]/7:
>    "When there is no parameter for a given argument, the
argument is
>    passed in such a way that the receiving function can obtain
the
>    value of the argument by invoking va_arg. The lvalue-to-
rvalue,
>    array-to-pointer, and function-to-pointer standard
conversions
>    are performed on the argument expression. After these
conversions,
>    if the argument does not have arithmetic, enumeration,
pointer,
>    pointer to member, or class type, the program is ill-formed.
If
>    the argument has a non-POD class type, the behavior is
undefined."
> - Heinz


Well, I don't believe that "undefined" and erroneous are the same thing.

From the reading I've done since my last post, I am now convinced that if a C++ compiler calls a constructor, it MUST also call a matching destructor.  We have demonstrated code where DMC does not follow this behavior.  THAT is a bug.

I knew when I wrote printf("%p", myType(x)) it was a hack; it was just easier than doing the proper cast.  DMC was free to choose how to push the values of x onto the stack.  That behavior IS undefined.  The fact that it pushed the cptr member when it did was the only reason my hack even sort-of worked.  If it had pushed the values in a different order, I would not have had reason to complain.  However, once it called a myType constructor, it should have also called a myType destructor on the same variable.  That requirement is not undefined.

Regarding what you said about Foo() calling the destructor, well, I'd be shocked if Walter really attempted to make a variadic function do it's own stack-cleanup.  It's not illegal, more like just impossible.

Anyway, I've definitely learned something with your help regarding not using non-POD types in variadic functions.  I'll remove the hack from my test code.

Thanks for your help, and for doing the research.

David

BTW, here's a little quote from a site that Walter pointed out.  I think it's relevant.
-------------
Empirical studies indicate that 20% of the people drink 80% of the
beer. With C++ developers, the rule is that 80% of the developers
understand at most 20% of the language. It is not the same 20% for
different people, so don't count on them to understand each
other's code.
http://yosefk.com/c++fqa/picture.html#fqa-6.6
-------------
November 15, 2007
== Quote from DQNOK (davidlqualls@yahoo.com)'s article
> Well, I don't believe that "undefined" and erroneous are the same thing.

You are correct that they are not the same thing. There are erroneous constructs that are not undefined.  But, undefined behavior means the compiler implementation has no requirements regarding what it must do.  It doesn't need to issue a diagnostic and it doesn't have to behave in a sensible way (indeed, there may be no sensible way to behave).  In other words, if your program has an undefined construct in it, you cannot count on what it may or may not do.

The standard defines "undefined behavior":

1.3.12 undefined behavior [defns.undefined]

behavior, such as might arise upon use of an erroneous program construct or erroneous data, for which this International Standard imposes no requirements. Undefined behavior may also be expected when this International Standard omits the description of any explicit definition of behavior. [Note: permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message). Many erroneous program constructs do not engender undefined behavior; they are required to be diagnosed. ]

1 2
Next ›   Last »