<<
Home , C99

and C++: Case Studies in Compatibility

Bjarne Stroustrup AT&T Labs Florham Park, NJ, USA

ABSTRACT

This article gives examples of how one might go about increasing the degree of com- patibility of C and C++. The ideal is full compatibility. Topics covered includes, vari- adic functions, vvo oi id *, bbo oo ol l , f (vvo oi id d ), cco on ns st t , iin nl li in ne e , and variable length arrays. These topics allows a demonstration of concerns that must be taken into account when trying to increase C/C++ compatibility.

A companion paper [Stroustrup,2002a] provides a ``philosophical'' view of the C/C++ relationship, and another companion paper presents a case for significantly increased C/C++ compatibility and proposed full compatibility as the ideal. [Stroustrup,2002b].

1 Introduction Making changes to a language in widespread use, such as C [C89] [C99] and C++ [C++98] is not easy. In reality, even the slightest change requires discussion and consideration beyond what would fit in this arti- cle. Consequently, each of the eleven ``case studies'' I present here lacks detail from the perspective of the C and C++ ISO standards committees. However, the point here is not to present complete proposals or to try to tell the standards committees how to do their job. The point is to give examples of directions that might (or might not) be taken and examples of the kind of considerations that will be part of any work to improve C and/or C++ in the direction of greater C/C++ compatibility. The examples are chosen to illus- trate both the difficulties and the possibilities involved. In many cases, the reader will ask ``how did the designers of C and C++ get themselves into such a mess?'' My general opinion is that the designers (not excluding me) and committees (not excluding the one on which I serve) got into those messes for reasons that looked good to competent people on the day, but weren't [Stroustrup,2002]. Let me emphasize that my answer is not a variant of ``let C adopt C++'s rules''. That would be both arrogant and pointless. The opposite suggestion, ``let C++ adopt C's rules'', is equally extreme and unreal- istic. To make progress, both languages and language communities must move towards a common center. My suggested resolutions are primarily based on considerations of [1] what would break the least code [2] how easy is it to recover from code broken by a change [3] what would give the greatest benefits in the long run [4] how complicated would it be to implement the resolution. Many changes suggested here to increase C/C++ compatibility breaks some code and all involve some work from implementers. Some of the suggested changes would, if seen in isolation, be detrimental to the lan- guage in which they are suggested. That is, I see them as sacrifices necessary to achieve a greater good (C/C++ compatibility). I would never dream of suggesting each by itself and would fight many of the sug- gested resolutions except in the context of a major increase in C/C++ compatibility. A language is more than the sum of its individual features.

Part of a three-article series from "The C/C++ Users Journal" July, August, and September 2002 - 2 -

2 Varadic Function Syntax In C, a variadic function is indicated by a comma followed by an ellipsis at the end of an argument list. In C++ the elipsis suffices. For example: iin nt t ppr ri in nt tf f (cco on ns st t cch ha ar r *, ...); // C and C++ iin nt t ppr ri in nt tf f (cco on ns st t cch ha ar r * ...); // C++ The obvious resolution is for C to accept the plain ellipsis in addition to what is currently accepted. This resolution breaks no code, imposes no run-time overhead, and the additional compiler complexity is negli- gible. Any other resolution breaks lots of code without compensating advantages. C requires a variadic function to have at least one argument specified; C++ doesn't require that. For example: vvo oi id d f (...); // C++, not C This case could be dealt with either by allowing the construct in C or by disallowing it in C++. The first solution would break no user code, but could possibly cause problems for some C implementers. The latter could break some code. However, such code is likely to be rare and obscure, and there are obvious ways of rewriting it. Consequently, I suggest adopting the C rule and banning the construct in C++. Breaking code should never be done lightly. However, sometimes it is better to break code than to let a problem fester. In making such a case, the likely importance of the construct banned should be taken into account, as should the likelyhood of code using the construct hiding errors. The probable benefits of the change have to be major. Whenever possible, meaning almost always, code broken by a language change should be easily detected by a compiler. Breaking code isn't all bad. The odd easily diagnosable incompatibility that doesn't affect link compat- ibility, such as a the introduction of a new keyword, can be good for the long-term heath of the community. It reminds people that the world changes and gives encouragement to review old code. Compatibility switches are needed, though, to serve people who can't/won't touch old source code. I'm no fan of com- piler options, but they are a fact of life and a compatibility switch providing practical backwards compati- bility can be a price worth paying for progress.

3 Pointer to vvo oi id d and NNU UL LL L In C, a vvo oi id d * can be assigned to any T * without an explicit cast. In C++, it cannot. The reason for the C++ restriction is that a vvo oi id d * to T * conversion can be unsafe [Stroustrup,2002]. On the other hand, this implicit conversion is widely used in C. For example: iin nt t * p = mma al ll lo oc c (ssi iz ze eo of f (iin nt t )*n ); // malloc()'s return type is void* sst tr ru uc ct t X * p = NNU UL LL L ; // NULL is often a macro for (void*)0 From a C++ point of view, mma al ll lo oc c () is itself best avoided in favor of nne ew w , but C's use of (vvo oi id d *)0 pro- vides the benefit of distinguishing a nil pointer from plain 0 . However, C++ retained the Classic C defini- tion of NNU UL LL L and maintained a tradition for using plain 0 rather than the macro NNU UL LL L . Had assignment of (vvo oi id d *)0 to a pointer been valid C++, it would have helped in overloading: vvo oi id d f (iin nt t ); vvo oi id d f (cch ha ar r *); vvo oi id d g () { f (0 ); // 0 is an int, call f(int) f ((vvo oi id d *)0 ); // error in C++, but why not call f(char*)? } What can be done to resolve this incompatibility: [1] C++ accepts the C rule. [2] C accepts the C++ rule. [3] both languages ban the implicit conversion except for specific cases in the standard , such as NULL and mma al ll lo oc c (). [4] C++ accepts the C rule for vvo oi id d * and both languages introduce a new type, say rra aw w *, which

Part of a three-article series from "The C/C++ Users Journal" July, August, and September 2002 - 3 -

provides the safer C++ semantics. I could construct several more scenarios, involving features such as a nnu ul ll l keyword, a nne ew w operator for C, and macro magic. Such ideas may have value in their own right. However, among the alternatives listed, the right answer must be [1]. Resolution [2] breaks too much code, and [3] breaks too much code and is also messy. Note that I say this while insisting that much of the code that [3] would break deserves to be broken, and that ``a type violation'' is my primary criterion for deeming a C/C++ incompatibility ``non- gratuitous'' [Koenig,1989]. This is an example where I suggest that in the interest of the C/C++ commu- nity, we must leave our ``language theory ivory towers'', accept a wart, and get on with more important things. Alternative [4] allows programmers to preserve type safety in new code (and in code converted to use it), but don't think that benefit is sufficient to add a new feature. In addition, I would seriously consider a variant of [1] that also introduced a keyword meaning ``the NNU UL LL L pointer'' to save C++ programmers from unnecessarily depending on a macro (see [Stroustrup,2002a]).

4 wwc ch ha ar r _ t and bbo oo ol l C introduced the wwc ch ha ar r _ t for wide characters. C++ then adopted the idea, but needed a unique wide character type to guide overloading for proper stream I/O etc., so wwc ch ha ar r _ t was made a keyword. C++ introduced a Boolean type named by the keyword bbo oo ol l . C then introduced a macro bbo oo ol l (in a standard header) naming a keyword ___ _ BBo oo ol l . The C choices were made to increase C++ compatibility while avoiding breaking existing code using bbo oo ol l as an identifier. For people using both languages, this is a mess (see the appendix of [Stroustrup,2002]). Again we can consider alternative resolutions: [1] C adopts wwc ch ha ar r _ t and bbo oo ol l as keywords. [2] C++ adopts C's definitions, and abolishes wwc ch ha ar r _ t and bbo oo ol l as keywords. [3] C++ abolishes wwc ch ha ar r _ t and bbo oo ol l as keywords and adopts wwc ch ha ar r _ t and bbo oo ol l as typedefs, defined in some standard library header, for keywords ___ _ WWc ch ha ar r and ___ _ BBo oo ol l . C adopts ___ _ WWc ch ha ar r as the type for which wwc ch ha ar r _ t is a typedef. [4] Both languages adopts a new mechanism, possibly called tty yp pe en na am me e that is similar to tty yp pe ed de ef f except that it makes new type rather than just a synonym. tty yp pe en na am me e is then be used to provide bbo oo ol l and wwc ch ha ar r _ t in some standard header. The keywords bbo oo ol l , wwc ch ha ar r _ t , and ___ _ BBo oo ol l would no longer be needed. [5] C++ introduces wwc ch ha ar r as a keyword, removes wwc ch ha ar r _ t as a keyword, and introduces wwc ch ha ar r _ t as a typedef for wwc ch ha ar r in the appropriate standard header. C introduces bbo oo ol l and wwc ch ha ar r as keywords. Many C++ facilities depend on overloading, so C++ must have specific types, rather than just tty yp pe ed de ef f s. Therefore [2] is not a possible resolution. I consider [3] complicated (introducing two ``special words'' where one would do) and its compatibility advantages are illusory. If bbo oo ol l is a name in a standard header, all code had better avoid that word because there is no way of knowing whether that header might be used in the future, and any usage that differ from the standard will cause confusion and maintenance problems [Stroustrup,2002]. Suggestion [4] is an intriguing idea, but in this particular context, it shares the weak- nesses of [3]. Solution [1] is the simplest, and is a distinct possibility. However, I think that having a key- word, wwc ch ha ar r _ t , with a name that indicates that it is a typedef is also a mistake, so I suggest that [5] is the best solution. One way of looking at an incompatibility is ``what could we have done then, had we known what we know now? What is the ideal solution?'' That was how I found [5]. Preserving wwc ch ha ar r _ t as a typedef is a simple backwards compatibility hack. In addition, either C must remove ___ _ BBo oo ol l as a keyword, or C++ add it. The latter should be done because it is easy and breaks no code.

5 Empty Function Argument Specification In C, the iin nt t f (); declares a function f that may accept any number of arguments of any type (but f may not be variadic) as long as the arguments and types match the (unknown) definition of the function. In C++, the function declaration

Part of a three-article series from "The C/C++ Users Journal" July, August, and September 2002 - 4 -

iin nt t f (); declares f as a function that accepts no arguments. In both C and C++, iin nt t f (vvo oi id d ); declares f as a function that accepts no arguments. In addition, the C99 committee (following C89) deprecated iin nt t f (); This f () incompatibility could be resolved cleanly by banning the C++ usage. However, that would break a majority of C++ programs ever written and force everyone to use the more verbose notation iin nt t f (vvo oi id d ); which many consider an abomination [Stroustrup,2002]. The obvious alternative would be to break C code that relies on being able to call a function declared without arguments with arguments. For example: iin nt t f (); iin nt t g (iin nt t a ) { rre et tu ur rn n f (a ); // not C++, deprecated in C } I think that banning such calls is the right solution. It breaks C code, but the usage is most error-prone, has been deprecated in C since 1989, and have been caught by compiler warnings and lint for much longer. Thus, iin nt t f (); should declare f to be a function taking no arguments. Again, a backwards compatibility switch might be useful.

6 Prototypes In C++, no function can be called without a previous declaration. In C, a non-variadic function may be called without a previous prototype, but doing so has been deprecated since 1989. For example: iin nt t f (i ) { iin nt t x = g (i ); // error in C++, deprecated in C // ... } All compilers and lints that I know of have mechanisms for detecting such usage. The alternatives are clear: [1] Allow such calls (as in C, but deprecated) [2] Disallow calls to undeclared function (as in C++) The resolution must be [2] to follow C++ and the intent of the C committee, as represented by the depreca- tion. Choosing [1] would seriously weaking the type checking in C++ and go against the general trend of programming without compensating benefits.

7 Old-style Function Definition C supports the Classic C function declaration syntax; C++ does not. For example: iin nt t f (a ,b ) ddo ou ub bl le e b ; /* not C++ */ { /* ... */ }

Part of a three-article series from "The C/C++ Users Journal" July, August, and September 2002 - 5 -

iin nt t g (cch ha ar r *p ) { f (p ,p ); // uncaught type error // ... } The problem with the call of f arise because a function defined using the old-style function syntax has a dif- ferent semantics from a function declared using the modern (prototype-style, C++-style) definition syntax. The function f is not considered prototyped so type checking and conversion is not done. Resolving this cannot be done painlessly. I see three alternatives: [1] adopt C's rules (i.e. allow old-style definitions with separate semantics) [2] adopt C++'s rules (i.e. ban old-style definitions) [3] allow old-style definitions with exactly the same semantics as other function definitions [1] is not a possible solution because it eliminates important type checking and leads to surprises. I con- sider [2] the best solution, but see no hope for its acceptance. It has the virtues of simplicity, simple compile-time detection of all errors, and simple conversion to a more modern style. However, my impres- sion is that old-style function definitions are still widely used and sometimes even liked for aesthetic rea- sons. That leaves [3], which has the virtues of simple implementation and better type checking, but suffers from the possibility of silent changes of the meaning of code. Warnings and a backwards compatibility switch would definitely be needed.

8 Enumerations In C, an iin nt t can be assigned to a value of type an een nu um m without a cast. In C++, it cannot. For example: een nu um m E { a , b }; E x = 1 ; // error in C++, ok in C E x = 999 9 ; // error in C++, ok in C I think that the only realistic resolution would be for C++ to adopt the C rule. The C++ rule provides better type safety, but the amount of C code relying on treating an enumerator as an iin nt t is too large to change, and I don't see a third alternative. The definition of een nu um m in C and C++ also differ in several details relating to size. However, the simple fact that C and C++ code today interoperate while using een nu um m s indicates that the definitional issues can be reconciled.

9 Constants In C, the default storage class of a non-local cco on ns st t is extern and in C++ it is static. The result is one of the hardest-to-resolve incompatibilities. Consider: cco on ns st t iin nt t x ; // uninitialized const cco on ns st t iin nt t y = 2 ; iin nt t f (iin nt t i ) { ssw wi it tc ch h (i ) { cca as se e y : // use y as a constant expression rre et tu ur rn n i ; // ... } } An uninitialized cco on ns st t is not allowed in C++, and the use of a cco on ns st t in a constant expression is not allowed in C. Both of those uses are so widespread in their respective languages that examples such as the one above must be allowed. This precludes simply adopting the rule from one language or the other. It follows that some subtlety is needed in the resolution, and subtlety implies the need for experimenta- tion and examination of lots of existing code to see that undesired side effects really are absent. That said, here is a suggestion: Distinguish between initialized and uninitialized cco on ns st t s . Initialized cco on ns st t s follow the C++ rule. This preserves cco on ns st t s in constant expressions, and if an initialized cco on ns st t needs to be accessed

Part of a three-article series from "The C/C++ Users Journal" July, August, and September 2002 - 6 -

from another translation unit, eex xt te er rn n must be explicitly used: cco on ns st t iin nt t cc1 1 = 117 7 ; // local to this translation unit eex xt te er rn n cco on ns st t iin nt t cc2 2 = 7 ; // available in other translation units On the other hand, follow the C rule for uninitialized cco on ns st t s. For example: cco on ns st t iin nt t cc3 3 ; // available in other translation units sst ta at ti ic c cco on ns st t iin nt t cc4 4 ; // error: uninitialized const

10 Inlining Both C and C99 provide iin nl li in ne e . Unfortunately, the semantics of iin nl li in ne e differ [Stroustrup,2002]. Basically, the C++ rules require an to be defined with identical meaning in every translation unit, even though an implementation is not required to detect violations of this ``one definition rule'', and many implementations can't. On the other hand, C99 allows inline functions in different translation units to dif- fer, while imposing restrictions intented to avoid potential linker problems. A good resolution would [1] not impose burdens on C linker technology [2] not break the C++ type system [3] break only minimal and pathological code [4] not increase the area of undefined or implementation-specified behavior An ideal solution would strengthen [3] and [4], but that's unfortunately impossible. Requirement [2] basically implies that the C++ ODR must be the rule, even if its enforcement must ± in the Classic C tradition [Stroustrup,2002a] ± be left to a lint-like utility. This leaves the problem of what to do about uses of sst ta at ti ic c data. For example: // use of static variables in/from inlines ok in C++, errors in C: sst ta at ti ic c iin nt t a ; eex xt te er rn n iin nl li in ne e iin nt t cco ou un nt t () { rre et tu ur rn n ++a ; } eex xt te er rn n iin nl li in ne e iin nt t cco ou un nt t2 2 () { sst ta at ti ic c iin nt t b = 0 ; b +=2 ; rre et tu ur rn n b ; } Accepting such code would put a burden on C linkers; not accepting it would break C++ code. I think the most realistic choice is to ban such code, realizing that some implementations would accept it as an exten- sion. The reason that I can envision banning such code is that I consider it rare and relatively unimportant. Naturally, we'd have to look at a lot of code before accepting that evaluation. There is a more important use of static data in C++ that cannot be banned: static class members. How- ever, since static class members have no equivalent in C, this is not a compatibility problem.

11 Static C++ deprecates the use of sst ta at ti ic c for declaring something ``local to this translation unit'' in favor of the more general notion of namspaces. The possible resolutions are [1] withdraw that deprecation in C++ [2] deprecate or ban that use of sst ta at ti ic c in C and introduce . Only [1] is realistic.

12 Variable-Sized Data Structures Classic C arrays are too low level for many uses: They have a fixed size, specified as a constant, and an array doesn't carry its size with it when passed as a function argument. Both C++ and C99 added features to deal with that issue: [1] C++ added standard library containers. In particular, it added sst td d :: vve ec ct to or r . A vve ec ct to or r can be speci- fied by a size that is a variable, a vve ec ct to or r can be resized, and a vve ec ct to or r knows its size (that is, a vve ec ct to or r can be passed as an object with it's size included, there is a member function for examining that size, and the size can be changed). [2] C99 added Variable Length Arrays (VLAs). A VLA can be specified by a size that is a variable, but a VLA cannot be resized, and a VLA doesn't know its size. The syntax of the two constructs differ and either could be argued to be more convenient and readable than

Part of a three-article series from "The C/C++ Users Journal" July, August, and September 2002 - 7 - that the other: vvo oi id d f (iin nt t m ) { iin nt t a [m ]; // variable length array vve ec ct to or r v (m ); // standard library vector // ... } A VLA behaves much like a vve ec ct to or r without the ability to resize. On the other hand, VLAs are designed with a heavier emphasis on run-time performance. In particular, elements of a VLA can be, but are not required to be, allocated on the stack. For C/C++ compatibility, there are two obvious alternatives; [1] Accept VLAs as defined in C99 [2] Ban VLAs. Choosing [1] seems obvious. After all, VLAs are arguable the C99 committee's greatest contribution to C and the most significant language feature added to C since prototypes and cco on ns st t were imported from C with Classes. They are easy to implement, efficient, reasonably easy to use, and backwards compatible. Unfortunately, from a C++ point of view, VLAs have several serious problems²: [a] They are a very low-level mechanism, requiring programmers to remember sizes and pass them along. This is error-prone. This same lack of size information means that operations, such as copy- ing, and range checking cannot be simply provided. [b] A VLA can allocate an arbitrary amount of memory, specified at run time. However, there is no standard mechanism for detecting or handling memory exhaustion. This is particularly bothersome because a VLA looks so much like an ordinary array, for which the memory requirements can be calculated at compile time. For example: #dde ef fi in ne e MM1 1 999 9

iin nt t f (iin nt t mm2 2 ) { iin nt t a [MM1 1 ]; // array, space requirement known iin nt t b [mm2 2 ]; // VLA, space requirement unknown // ... } This can lead to undefined behavior and obscure bugs. [c] There is no guarantee that memory allocated for elements of a VLA are freed if a function contain- ing it is exited abnormally (such as by an exception or a llo on ng gj jm mp p ). Thus, use of VLAs can lead to memory leaks. [d] By using the array syntax, many programmers will see VLAs as ``favored by the language'' or ``rec- ommended'' over alternatives, such as sst td d :: vve ec ct to or r , and as more efficient (even if only potentially so). [e] VLAs are part of C, and sst td d :: vve ec ct to or r is not, so if VLAs were accepted for the sake of C/C++ com- patibility, people would accept the problems with VLAs and use them in the interest of maximal portability. The net effect is that by accepting VLAs, the result would be a language that encouraged something that, from a C++ point of view, is unnecessarily low-level, unsafe, and can leak memory. It follows that a third alternative is needed. Consider: [3] Ban VLAs and replace it with vve ec ct to or r (possibly provided as a built-in type). [4] Define a VLA to be equivalent and interchangeable with a suitably designed container, aar rr ra ay y . Naturally, [3] is unacceptable because VLAs exist in the C standard, but it would have been a close-to-ideal ______² It has been suggested that considering VLAs from a C++ point of view is unfair and disrespectful to the C committee because C is not C++ and C/C++ compatibility isn't part of the C standard committee's charter. I mean no disrespect to the C committee, its mem- bers, or to the ISO process that the C committee is part of. However, given that a large C/C++ community exist and that VLAs will in- evitably be used together with C++ code (either through linkage or through permissive compiler switches), an analysis is unavoidable and needed.

Part of a three-article series from "The C/C++ Users Journal" July, August, and September 2002 - 8 - solution. However, we can use the idea as a springboard to a more acceptable resolution. How would aar rr ra ay y have to be designed to bridge the gap between VLAs and C++ standard library containers? Consider possible implementations of VLAs. For C-only, a VLA and its size are needed together only at the point of allocation. If extended to support C++, destructors must be called for VLA elements, so the size must (con- ceptually, at least) be stored with a pointer to the elements. Therefore, a naive implementation of aar rr ra ay y would be something like this: tte em mp pl la at te e ccl la as ss s aar rr ra ay y { iin nt t s ; T * eel le em me en nt ts s ; ppu ub bl li ic c : aar rr ra ay y (iin nt t n ); // allocate "n" elements and let "elements" refer to them aar rr ra ay y (T * p , iin nt t n ); // make this array refer to p[0..n-1] oop pe er ra at to or r T *() { rre et tu ur rn n eel le em me en nt ts s ; } iin nt t ssi iz ze e () cco on ns st t { rre et tu ur rn n s ; } // the usual container operations, such as = and [], much like vector }; Apart from the two-argument constructor, this would simply be an ordinary container which could be designed to allocate from the stack, just like some VLA implementations. The key to compatibility is its integration with VLAs: vvo oi id d h (aar rr ra ay y a ); // C++ vvo oi id d g (iin nt t m , ddo ou ub bl le e vvl la a [m ]); // C99 vvo oi id d f (iin nt t m , ddo ou ub bl le e vvl la a1 1 [m ], aar rr ra ay y aa1 1 ) { aar rr ra ay y aa2 2 (vvl la a1 1 ,m ); // a2 refers to vla1 ddo ou ub bl le e * p = aa1 1 ; // p refers to a1's elements h (aa1 1 ); h (aar rr ra ay y (vvl la a1 1 ,m )); // a bit verbose h (m ,vvl la a1 1 ); // ??? g (m ,vvl la a1 1 ); g (aa1 1 .ssi iz ze e (), aa1 1 ); // a bit verbose g (aa1 1 ); // ??? } The calls marked with ??? cannot be written in C++. Had they gotten past the type checking, the result would have executed correctly because of structural equivalence. If we somehow accept these calls, by a general mechanism or by a special rule for aar rr ra ay y and VLAs, aar rr ra ay y s and VLAs would be completely inter- changeable and a programmer could choose whichever style best suited taste and application. Clearly, the aar rr ra ay y idea is not a complete proposal, but it shows a possible direction for coping with a particularly nasty problem of divergent language evolution.

13 Afterword There are many more compatibility issues that must be dealt with by a thorough description of C/C++ incompatibilities and their possible resolution. However, the examples here should give a concrete basis for a debate both on principles and practical resolutions. Again, please note that the suggested resolutions don't make much sense in isolation, I see them as part of a comprehensive review to eliminate C/C++ incompatibilities. I suspect that the main practical problem in eliminating the C/C++ incompatibilities, would not be one of the compatibility problems listed above. The main problem would be that starting from Dennis Ritchie's original text, the two standards have evolved independently using related but subtly different vocabularies, phrases, and styles. Reconciling those would take painstaking work of several experienced people for sev- eral months. The C++ standard is 720 pages, the C99 standard is 550 pages. I think the work would be worth it for the C/C++ community. The result would be a better language for all C and C++ programmers.

Part of a three-article series from "The C/C++ Users Journal" July, August, and September 2002 - 9 -

14 References [C89] ISO/IEC 9899:1990, Programming Languages ± C. [C99] ISO/IEIC 9899:1999, Programming Languages ± C. [C++98] ISO/IEC 14882, Standard for the C++ Language. [Koenig,1989] Andrew Koenig and Bjarne Stroustrup: C++: As close to C as possible ± but no closer. The C++ Report. July 1989. [Stroustrup,2002] Bjarne Stroustrup: Sibling Rivalry: C and C++. AT&T Labs - Research Technical Report TD-54MQZY, January 2002. http://www.research.att.com/Äbs/sibling_rivalry.pdf. [Stroustrup,2002a] Bjarne Stroustrup: C and C++: Siblings. The C/C++ Journal. [Stroustrup,2002b] Bjarne Stroustrup: C and C++: A Case for Compatibility. The C/C++ Journal.

Part of a three-article series from "The C/C++ Users Journal" July, August, and September 2002