Standard C: An Update

Whither goest Standard C?

Rex Jaeschke

Rex is the chair of X3J11, the committee responsible for the ANSI C Standard. Rex can be reached at rex@aussie.com.


ANSI and ISO rules require that standards be reviewed five years after their adoption. Because the original ANSI C Standard was adopted in 1989 and replaced by the ANSI/ISO C Standard in 1990, the standard must be reviewed this year. There are three possible outcomes from such a review:

In 1994, at the request of national member bodies, P.J. Plauger, convener of SC22/WG14 (the committee responsible for the ISO C Standard), obtained permission to begin a review of ISO C prior to the required review. Serious discussion regarding a review began at the Tokyo meeting in June 1994, where the committee decided to revise the standard. The committee also decided not to let C be constrained to a subset of C++. To paraphrase an unofficial C++ Standard principle, we want to be as compatible with C++ as possible, but no more so.

Consequently, we re-endorsed the guiding principles used in defining the original ANSI C Standard:

To these, we added some new principles:

Although there has been no real opposition within the standard's committee, more than a few have criticized our decision to revise the C Standard. The most common complaint I hear is: "Why bother? It is obvious that C++ is the future of C!" Our response is: "Regarding our relationship with C++, we are content to let C++ be the big and ambitious language. While we may well embrace some features of C++, it is not our intention that C become C++."

At a previous meeting, I volunteered to draft the charter for C9X (as the revision is unofficially known). This charter was refined at the Tokyo meeting, then again at the Plano, Texas, meeting in December 1994.

To help us focus on the job at hand and give the user community a good idea of our timetable, we propose to spell out, in detail, what C9X will look like, by December 1996, and to have a revised standard adopted within three years of that date.

To help reduce the amount of pure invention, we adopted the principle "Codify existing practice to address evident deficiencies." That is, we accept only those concepts that have some prior art (not necessarily from C implementations). Unless some proposed new feature addresses a deficiency felt by more than a few C programmers, we will not entertain it. To achieve this essential goal, some good (but inventive) proposals will probably have to be rejected.

By the time you read this, we will have met in Copenhagen, Denmark (June 12-16, 1995) to debate technical proposals for C9X. At this stage, eight major proposals are expected:

The first seven are the result of the Numerical C Extensions Group work I started back in 1989. Eventually, this project became part of X3J11's charter. This project has been completed and is in the process of being accepted by ANSI as a Technical Report (TR). The "Data Parallel C Extensions" component of the TR was not proposed for inclusion in C9X. We felt this field was rapidly evolving and that a standard for these features was premature.

Restricted Pointers

The proposal concerning restricted pointers involves the addition of a type qualifier called restrict. In some sense, restrict is complementary to volatile; whereas volatile inhibits certain optimizations, restrict explicitly allows some optimizations.

A significant obstacle to optimization is aliases to objects, via the use of pointers. Often, the information available in a function, or even within a compilation unit, is insufficient to determine whether two pointers can point to the same object.

By declaring a pointer restrict-qualified, we indicate that it points to a unique object, as if that object were allocated by a call to malloc. Note that the restrict qualifier can only be applied to a data pointer; it is a constraint violation to apply it to a nonpointer object or to a function pointer.

Late in the development process for the original ANSI C Standard, the keyword noalias was added to the draft; three months later, it was withdrawn. The definitions of noalias and restrict overlap, but restrict is a more conservative approach that, for those vendors that have already implemented it, provides a good deal of bang for their buck.

Variable-Length Arrays

The proposal for variable-length arrays allows the size of an array dimension to be specified at run time, provided that array has automatic storage duration or is a formal parameter; see Example 1. A variable-length array cannot be a member of a structure or union.

If an array is declared using a size of * (as in int i[*];), then it is a variable-length array type of unspecified size, which can only be used in declarations with function-prototype scope. If the size is a nonconstant expression, that expression must have some integer type, and shall evaluate to a value greater than 0 at run time. The expression may contain side effects. The size of a variable-length array does not change until the execution of the block containing its declaration has ended. Because the size of a variable-length array cannot normally be determined until run time, sizeof usually must determine its size at run time.

Designated Initializers

This designated-initializers proposal extends initializer syntax to allow two new forms. It allows specific elements of an array or specific members of a structure to be initialized without regard to their relative position within the array or structure. For instance, Example 2(a) initializes elements 0, 1, 17, and 18 explicitly, with elements 2-16 and 19 taking on the value 0; Example 2(b) allows new enumeration constants to be added at the front or middle of the list, or the list order to be rearranged, without requiring the array's initializer list to be changed. As Example 2(c) illustrates, structure members can be initialized in a like manner.

Compound Literals

The proposed compound literals allow an unnamed object to be constructed at run time using syntax like that of a cast, providing a capability similar to a C++ constructor; see Example 3.

In case 1, you construct an array at run time from the expression list shown. The const qualifier prohibits write access to that array.

In case 2, you construct two unnamed Point structures, which you then pass by address to drawline.

In the case of array and structure compound literals, this proposal can take advantage of designated initializers.

Floating-Point Extensions

The floating-point extensions proposal provides machinery to achieve more predictable floating-point arithmetic in general, as well as a binding for IEEE-based implementations. It achieves this via the addition of numerous operator-like macros, several pragmas, some predefined macros, and an extensive library of functions declared in several headers. While the library portion proposes support for overloaded functions, this mechanism is not intended for general use by programmers.

Complex Arithmetic

The proposal for complex arithmetic specifies a set of extensions to support float, double, and long double complex types and arithmetic operations on expressions having those types. For C++ compatibility, the type names are float_complex, double_complex, and long_double_complex rather than float complex, double complex, and long double complex. A family of imaginary types is also proposed, with names float_imaginary, double_imaginary, and long_double_imaginary. The imaginary constant i is available via the macro I, defined in complex.h. The standard math library is extended to support complex arguments.

Extended Integer Support

The extended-integer-support proposal defines a header inttypes.h, containing a family of macros and type synonyms that allow the programmer to find out the available set of integer precisions and to define objects of a minimum precision. Table 1 provides a sampling of the type synonyms.

A family of macros similar to those in limits.h defines the minima and maxima for these types. If a type is not supported, its corresponding macros will be undefined, allowing code to be conditionally compiled.

Another family of macros allows portable calls to printf and scanf, even though different implementations may define different conversion specifiers.

C++-like Class Support

The initial discussion has focused primarily on encapsulation and the addition of the keywords class, private, and public, with some discussion of single inheritance and virtual functions. At the time of writing, the latest revision of this proposal has not yet been finalized. However, previous versions excluded constructors and destructors, which eliminated a number of technical problems, as well as some benefits.

Miscellaneous Proposals

The following issues have been debated via our e-mail reflector and/or are on the agenda for the Copenhagen meeting:

Conclusion

It is too early to predict just what will be in C9X. However, the seven proposals arising from the ANSI TR have been under development for three to five years, and all have been implemented by one or more vendors. Therefore, it seems reasonable that at least some of these will make the final cut, but not necessarily in their current form. As to the adoption of something substantial from C++, I think that the considerable sentiment for such proposals will generate the most controversy and animated debate. No doubt there will also be numerous wording improvements and minor additions, such as the inclusion of //-style comments. In any event, it's a safe bet that the resulting language will still look like the C we currently know and love.

Example 1: Variable-length arrays.

extern int n;
void f(int m, int x[m][n])
{
    char c[m];
    int (*p)[n];
}
Example 2: (a) Initializing elements; (b) adding new enumeration constants; (c) initializing structure members.
(a)
int i[20] = {5, 10, [17] = 50, 60};

(b)
enum color { red, green, blue };
unsigned int total[] = {
    [red]   = 100,
    [blue]  = 200,
    [green] = 50
};

(c)
struct date {
    int year;
    int month;
    int day;
};
struct date birthday = {
    [month] = 1,
    [day]   = 2,
    [year]  = 1950
};
Example 3: Compound literals.
struct Point {
    int x;
    int y;
};
void drawline(struct Point *, struct Point *);
void f(int x2, int y2)
{
/*1*/   const int *p = (int []){x2, 5, y2, 4};
/*2*/   drawline(&(struct Point){0,0}, &(struct Point){x2,y2});
}
Table 1: Extended integer support.
Macro           Description

int8_t         8-bit signed integer.
int16_t        16-bit signed integer.
int32_t        32-bit signed integer.
int64_t        64-bit signed integer.
intptr_t       Signed integer type capable of holding a void.
uintfast_t     Most-efficient unsigned integer type.
uint_least8_t  Smallest unsigned integer having at least 8 bits.


Copyright © 1995, Dr. Dobb's Journal