Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Bug++ of the Month


December 2000/Bug++ of the Month



Every so often, a reader just insists on doing all my work for me. Such is the case this month with a VC++ bug handed to me along with a description of the real-life code that led to it. It’s such a fundamental bug that I couldn’t believe we hadn’t covered it before, but after some searching, I can’t find any evidence it’s ever appeared in WDJ, so I’ll just turn this over to this month’s bug submitter.

John Burger Writes...

I’m not sure if this is a known bug, but was I surprised when I caused it! I’m using Microsoft Visual C++ v6.0 SP3. (It’s not a problem with Borland C++ v5.5.) The Microsoft Knowledgebase (Q151168) has this bug listed against VC++ v4.0 and reports it fixed under version 4.1, but the report is dated March 2000, so I’m confused! I’ll give some background first.

I’m the designer of most of the foundation classes we use at work, for other programmers to use in applications. So I’m careful to use C++ features to ensure that my “customers” (the other programmers) don’t misuse my classes. The most important of these is the use of the pure specifier (=0) on virtual functions that I really want people that are deriving from my classes to think about — even if it means simply calling the base class’ pure function. (Some programmers don’t realize that pure virtual functions can still have a body defined, although they can only be called via fully-specified names.)

As an aside, I find the syntax notation for pure functions difficult to isolate if I ever ask myself the question, “Which functions are pure?” To that end, our style guide has the following definition:

#define pure = 0

which allows a pure function to be defined as follows:

virtual void Fn() pure;

making pure nearly a keyword. Unfortunately, PC-Lint (a very useful tool!) complains about the #define — it warns that the equal sign is not required, which is a useful tip for newbies, but an irritation in this case!

Anonymous Variables. Another design style we use is the defining of classes that are complete within themselves. Merely constructing an instance of such class does all of the work that is needed. (The destructor, of course, cleans up as well.) A perfect example of this would be a message box: the constructor creates and displays it, waits for the user to click OK, and then returns. The destructor hides it and then destroys it. So, the line:

MessageBox message("The operation is complete");

does all of the work I need. Some compilers, however, complain about this with “Variable ’message’ is defined but never used”. To placate them, we make the variable anonymous:

MessageBox("The operation is complete");

which looks a lot like a function call, but is actually the creation and immediate destruction of an anonymous variable (more than a temporary variable, but not a full-blown named one).

Why don’t we use a function then? Using a class gives us the ability to use inheritance, it means that the different aspects of the “function” can be kept in different methods, and the state of the “function” can be kept in member variables, as close as you can get with C++ to Pascal’s nested functions.

That practice led me to the compiler bug, which can be boiled down to this example:

class Pure {
public:
    inline Pure(bool) {
    } // Pure(bool)
    virtual int Fn() = 0;
}; // Pure

int main() {
    Pure(false);
//  Pure test(true);
    return Pure(true).Fn();
} // main()

Note that Pure has a pure virtual function, which means it’s abstract. The first line of main() tries to instantiate an anonymous Pure, so it should fail. VC++ doesn’t complain! The last line of main() goes one step further — it tries to instantiate an anonymous Pure and then call Fn() on the anonymous variable. Again, VC++ doesn’t complain! If I un-comment the middle line, VC++ correctly complains about test being abstract. If I derive a class from Pure, but don’t override Fn(), then that class will also be abstract, and all of the above tests on the new class still behave the same.

OK, so it compiles and links (although it shouldn’t). How does it run? Not surprisingly, not very well. Using the debugger to follow the program flow, the first line jumps into the constructor and leaves again. The last line jumps into the constructor, leaves again, and then enters a runtime library function called void _purecall(void), a function that reports to the user that “a pure virtual function was called” and then terminates the program.

This is not a show-stopper, but the whole point of making a virtual function pure is so that the compiler will complain if a derived class doesn’t override it and prevent any instances of the abstract class. In this case, it doesn’t complain.

Microsoft’s Response

Sometimes I have to pore over language standards until my eyes hurt to research a bug, but this one is pretty easy. Not instantiating abstract classes is a very basic C++ language promise. I went to msdn.microsoft.com and searched for the keywords “bug C++ abstract”, but did not spot this bug in the 69 hits returned, so off the bug went to Microsoft for comment. Microsoft’s Kerry Loynd offered this response:

This is a regression of the VC 4.0 bug described in the article support.microsoft.com/support/kb/articles/Q151/1/68.asp. Note that it is only temporary objects that fail to generate the C2259 diagnostic. This bug will be fixed in the next major release of Visual C++.

I had discovered the bug report (Q151168) that both Kerry and John refer to, but did not consider it because when I compiled that bug report’s example on my machine, Visual C++ 6.0 quite correctly flagged the attempts to instantiate an abstract object. Unlike John’s bug, the bug in Q151168 looks like this:

/* Compile options needed: /c /GX
*/ 

class Exception    //abstract class
{
    int m_nCause;
    char * m_pszMsg;
public:
    Exception();
    Exception( int, const char * = 0);
    Exception( const Exception &);
    ~Exception();
    // NOTE: two pure virtual functions
    inline virtual int Cause() = 0;
    inline virtual
       const char * Msg() = 0;
};

int Exception::Cause()
    {
    return m_nCause;
    }

const char * Exception::Msg()
    {
    return m_pszMsg;
    }

void main()
    {
    try
    {
    int r = Exception(55,"no error")
          .Cause(); // ILLEGAL
    throw Exception(-1,
       "error"); // ILLEGAL
    }
    catch (Exception&e)
    {
    }
}

So, I would have to say that the bug in Q151168 really was fixed, while John’s bug is not. However, that raises an interesting question: how exactly was Microsoft able to fix the bug in Q151168 and still leave the bug that John found? Precisely, how are the two different? I spent one evening tweaking the two sources to try to figure out exactly what the conditions are under which the compiler suddenly starts realizing you’re trying to instantiate an abstract class, but couldn’t isolate the conditions. Maybe some reader with more ambition (and coffee) will solve the riddle. In any case, no matter how you cut it, Q151168 does not do a creditable job of telling Microsoft customers that John’s bug exists.

Reader Feedback

From: Tomer Abramson <[email protected]>
Subject: Bugs, bugs, bugs ...

Hi,

I found a bug in the VC++ 6 compiler. I don’t know if you already presented this bug, so I’m sending it anyway. The code is:

class Base
{
public:
    Base() {}
   ~Base() {}
};

class Derived1 : public Base
{
public:
    Derived1() {}
   ~Derived1() {}
};

class Derived2 : public Base
{
public:
    Derived2() {}
   ~Derived2() {}
};

void main()
{
int classType = 1;
Base* pBase =
    (classType == 1 ? new Derived1 : new Derived2);

}

Try to compile this code and you will get an error:

main.cpp(25) : error
C2446: ':' : no conversion from 'class Derived2 *'\
 to 'class Derived1 *'
 Types pointed to are unrelated; conversion requires
reinterpret_cast, C-style cast or function-style cast

It looks like some kind of bug in the way the compiler handles inheritance. You can easily correct the bug by changing the line:

Base* pBase =
  ( classType == 1 ? new Derived1 : new Derived2);

into :

Base* pBase =
  ( classType == 1 ? new Derived1 : (Base*)new Derived2);

Excellent question! Not a compiler bug, I’m afraid, but it is an area that a lot of us trip over sooner or later. I’m guessing you looked at the error message and thought that the compiler was complaining at the point it needed to convert the result of the conditional expression to a Base*. In fact, the compiler’s complaint has nothing to do with the assignment statement. In other words, if you change that line to just this:

  ( classType == 1 ? new Derived1 : new Derived2);

you’ll get exactly the same complaint from the compiler.

The conditional operator is a sneaky one, even in C, let alone C++. You can pretty much explain it to a new programmer in a few sentences, but if you look inside a compiler, you’ll see that there’s a surprising amount of code associated with compiling a conditional statement. In your case, you’ve written code whose intentions are obvious to any human C++ programmer — but it still isn’t legal C++.

I think the best way to get at this problem is to imagine you are a language designer. You’ve added a conditional operator because it’s simply very convenient to have in expressions. But when someone has to implement your language, they will immediately ask you a simple-sounding question: what is the type of a conditional expression?

Consider this expression:

int  a, b, c;

  (a ? b : c);

The type of the conditional operator expression is obvious here — it should be int. But suppose you have this instead:

int     a, b;
double  c;

  (a ? b : c);

Now what is the type of this conditional operator expression? If you want to be like C, which already has rules for implicitly converting expression operands into compatible types, then you’ll almost certainly want to say that the normal conversion rules are applied to get the operands into a similar type. To make this clearer, look at this C code fragment:

int    b = 1;
double c = 1.1;

    if(b == c)
        printf("Equal!\n");

You have an operator (==) with two operands that are not of the same type. C and C++ have very detailed rules that say which implicit conversions shall take place to remedy this problem. Much of the time, the rules are so commonsensical that you never have to think about them. In this case, the “littler” numeric type is implicitly converted to the “bigger” numeric type. (b is implicitly converted to double before performing the comparison.)

Of course, there are a limited number of implicit conversions in C and C++ (thank goodness!), so not everything that you can type is legal:

typedef struct A { int Count; }; A;
int  b = 45;
A    a;

    if(a == b)  // NOT LEGAL!!!!
        //...

    (b == 45 ? b : a); // NOT LEGAL EITHER

In other words, while you might reasonably want the compiler to implicitly convert between various size numerics (int, long, float, double, etc.) so that you don’t have to explicitly cast things all the time, there is just no way that the compiler can be expected to know what you wanted to happen if you’re comparing an numeric with a structure, for example.

OK, I’m finally ready to talk about your example in detail. You probably already know that the following is not legal in C++:

class Base {};
class Derived1 : public Base {};
class Derived2 : public Base {};
void Foo()
    {
    Derived1*    d1;
    Derived2*    d2;

    d1 = d2;
    }

In fact, if you compile this code, you will get exactly the same error message from the compiler that your own example evokes. There is no implicit conversion between two pointers that happen to point to objects derived from a common base class (nor would you want one). That leads me directly to the following question: given the previous declarations, what should be the type of the following expression?

(1 ? d1 : d2)

The normal implicit conversions have nothing to say about two operands that point to different derived classes, so, like a conditional operator whose operands include both an integer and a structure, this is simply illegal.

I’m not saying you couldn’t redesign C++ so that your code would be legal. But I will say that the change might not be as trivial as it appears at first glance. I am sure that you wanted the compiler to decide the type of the conditional operator based on the type of what it was being assigned to. But that would make it different than every other operator in the language — no C or C++ operators currently change their behavior depending on what happens later in the expression evaluation. (The assignment operator appears first to a human, but is last in order of execution.) You should also consider how many exceptions and special cases that C++ already has. The conditional operator already requires six fairly involved paragraphs (not counting things you have to look up to understand those paragraphs); I personally would rather live with the current behavior (which makes sense from a parser’s point of view) than add another rule.

So the short answer is simply: this is not a compiler bug. For another answer, I propose Ron’s Rule of Conditional Operators: don’t use a conditional operator unless its two operands are of exactly the same type. This rule pretty much saves you ever having to understand just how hairy the detailed behavior of the conditional operator really is.

From: “Bob Carleone” <[email protected]>
Subject: Using a Null Pointer to Call a Function

Dear Mr. Ron Burk,

This morning I was stepping through some MFC Code (AfxMessageBox(...) to be precise), and I saw something that really surprised me. It appeared as if a null pointer was being used to call a function of the CWinApp class. I decided to boil it down to a simple console application. Here is the result. I guess it’s OK to call a member function of an uninstantiated class with a null pointer as long as you use scope resolution. How can this be? I have attached a zip file so that you can see all the project settings. They are just the defaults for an empty VC6 console app. There is only one source file, and I have included the text here as well.

#include <stdio.h>
class A
{
public:
     int m_I,
         m_I2;
     void WhereAmI()
     {
     printf("I am at %p  ",this);
     printf("I have integer members at "
            “%p and %p\n",&m_I,&m_I2);
     }
 
};
void AccessA(A* pA)
{
     if(pA)
          pA->WhereAmI();
     else
          pA->A::WhereAmI();
}
int main()
{
      AccessA(0);
      A a;
      AccessA(&a);
      return 0;
}

Very neat! Even better, you don’t have to be nearly so tricky to get a NULL pointer used in this manner. For example, (because WhereAmI() is not virtual) you could have also gotten away with this:

((A*)0)->WhereAmI();

But what you’ve asked is the much deeper philosophical question: is it OK? Well, if “OK” means “Can I usually get away with it?”, then the answer is an emphatic “Yes!” If “OK” means “Does the C++ standard guarantee that I can get away with it?”, then the answer is an emphatic “I don’t think so!” The standard defines A->B as equivalent to (*(A)).B, and the section on the indirection operator doesn’t really offer any hope that an operand of NULL is within the bounds of defined behavior. However, if it makes you feel any better, consider that C’s lowly offsetof() macro (from stddef.h) is often implemented using a quite similar trick:

// stolen from Borland C++!
#define offsetof( s_name, m_name )(size_t)&(((s_name *)0)->m_name)

I believe a compiler would be well within its rights to detect your attempt to use a NULL pointer with -> and flag it as an error. However, if you’re sticking to the little world of Windows compilers, the odds of getting away with this indefinitely are excellent, as far as I know. It’s a lot more likely that a specialized bug-finding tool would complain about this than that Visual C++ will ever get aggressive enough to refuse to compile it.

Summary

Another month, another bug, another t-shirt. Join the hordes of intrepid bug reporters by boiling your favorite compiler bug down to the smallest possible example and firing it off to [email protected]. If you’re the lucky winner, then the vendor gets the bug, you get the t- shirt, and I get another column done.

Do you have access to more than one C++ compiler? Do you know how to read the C and C++ standard specifications? Can you form words into sentences, and sentences into paragraphs? Then you could be the person we’re looking for. We’re holding open auditions to locate a new Bug++ of the Month columnist to replace me. Just go to www.wdj.com/author/, and click on “Bug++ Columnist Wanted”.

Ron Burk is the editor of Windows Developer’s Journal. You can reach him at [email protected]. Send compiler bug candidates to [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.