Friday, February 8, 2008

Microsoft VS2005 C++ non-compliance issues (Part III)

3. Compiler extensions

This section describes a number of VS2005 features, which can be safely referred to as compiler extensions, but at the same time cannot be disabled by using /Za switch. I never intended to turn this into a complete list of extensions implemented in that compiler. What is listed here is what I came across while testing something that didn't work properly in VC6.

3.1. Default initialization is a bit overdone, but no value-initialization yet

The known VC6 problem with default initialization, when '()' initializer was ignored more often than it should be, is now gone. In the following code sample a '()' initializer correctly causes zero-initialization of a POD object in VS2005

struct POD { int i; }; ... POD* pod = new POD(); // '*pod' object is zero-initialized assert(pod->i == 0);

The proper support for '()' initializer extends to array initialization and constructor initializer lists

struct S { int a[10]; int* p; POD pod; S() : a(), POD() { // Both 'a' and 'pod' subobjects are zero-initialized assert(a[5] == 0 && pod.i == 0); p = new int[20](); // The 'new'ed array is zero-initialized assert(p[10] == 0); } };

It is interesting to note that an attempt to use '()' on an array in a constructor initializer list triggers warning C4351 ("new behavior..."), while no other newly supported context with '()' initializer seems to cause this warning message to appear. Maybe this behavior is only new for VS2005 as compared to VS2003, and it only appears to be illogical to me because I compare VS2005 to VC6.

Further research demonstrates that trying to provide proper support for '()' initializer VS2005 developers have actually overdone it. The C++98 standard requires zero-initialization for POD-types, while VS2005 also zero-initializes certain non-POD types, as can be seen from the following example

struct NonPOD { int i; int nonPOD::*p; private: int j; }; ... NonPOD* nonpod = new NonPOD(); // '*nonpod' object is zero-initialized assert(nonpod->i == 0);

This is, of course, not a violation of the C++98 standard, since zero value is not worse than any other indeterminate value. It's just that portable C++98 code is not supposed to rely on this behavior. Additionally, the post-TC1 specification of C++ introduces the concept of value-initialization, which actually happens to require zero-initialization of the above 'NonPOD' structure in response to '()' initializer. Make no mistake though, VS2005 does not implement value-initialization, as can be illustrated by the following code

struct NoUserConstructor { int i; std::string s; }; ... NoUserConstructor* nuc = new NoUserConstructor(); // In VS2005 'nuc->i' is holding an indeterminate value // here, while value-initialization in post-TC1 C++ // would set it to zero

It appears that in VS2005 the decision to zero-initialize an object of certain type in response to '()' initializer is based on the triviality of that type's constructor. Types without constructor or with a trivial constructor are zero-initialized. Types with non-trivial constructor are not.

3.2. Polymorphic delete[]

This extension earned a place in this blog because it was associated with a genuine bug in VC6. The bug is no longer there in VS2005, thus turning it into a plain extension.

In more detail, the issue can be illustrated by the following example

struct A { virtual ~A() {} }; struct B : A {}; ... A* p = new B; delete p; // OK A* pa = new B[20]; delete[] pa; // Undefined behavior in standard C++, but defined // behavior in VS2005

In standard C++ polymorphic deletion can only be applied to standalone dynamic objects. "Standalone" in this case refers to non-array objects, i.e. objects created with 'new' and destroyed with 'delete' (as opposed to 'new[]'/'delete[]' pair). The 'new[]'/'delete[]' pair doesn't support any kind of polymorphism with regard to deletion. This means that static type of the pointer passed to 'delete[] ' must be exactly the same as that of the pointer returned by 'new[]'. Otherwise, the behavior is undefined. MS compilers since VC6 (maybe even earlier) extend this behavior by defining it. They support polymorphic deletion of arrays by introducing a special kind of implicit virtual destructor, which they internally call 'vector deleting destructor'. However, in VC6 this extended functionality was implemented with a bug, which also caused a crash in perfectly standard code. In the context of the previous example, the following code crashes in VC6 even though it is supposed to work from the standard C++ point of view

A* pz = new A[0]; delete[] pz; // crash here in VC6

This crash no longer happens in VS2005.

3.3. Explicit specialization of member templates

This extension carries over fom VC6 without any apparent changes

template<typename T> struct S { template<typename U> struct R {}; template<> struct R<int> {}; // This explicit specialization is ill-formed in // standard C++, but supported in VS2005 };

No comments: