C H A P T E R 7 |
Transitioning to ISO C |
This chapter provides information which you can use to help you port applications for K&R style C to conform with 9899:1990 ISO/IEC C standard. The information is presented under the assumption that you are using -xc99=none because you do not want to conform with the newer, 9899:1999 ISO/IEC C standard. The C compiler defaults to -xc99=all which supports the 9899:1999 ISO/IEC C standard.
The ISO C compiler allows both old-style and new-style C code. The compiler provides varying degrees of compliance to the ISO C standard when you use the following -X (note case) options with -xc99=none. -Xa is the default mode. Note that the compiler's default mode is -xc99=all, so its behavior under each of the -X options depends on the setting of -xc99.
ISO C plus K&R C compatibility extensions, with semantic changes required by ISO C. Where K&R C and ISO C specify different semantics for the same construct, the compiler issues warnings about the conflict and uses the ISO C interpretation. This is the default mode.
(c = conformance) Maximally conformant ISO C, without K&R C compatibility extensions. The compiler issues errors and warnings for programs that use ISO C constructs.
(s = K&R C) The compiled language includes all features compatible with ISO K&R C. The computer warns about all language constructs that have differing behavior between ISO C and K&R C.
(t = transition) ISO C plus K&R C compatibility extensions, without semantic changes required by ISO C. Where K&R C and ISO C specify different semantics for the same construct, the compiler issues warnings about the conflict and uses the K&R C interpretation.
The 1990 ISO C standard's most sweeping change to the language is the function prototype borrowed from the C++ language. By specifying for each function the number and types of its parameters, not only does every regular compile get the benefits of argument and parameter checks (similar to those of lint) for each function call, but arguments are automatically converted (just as with an assignment) to the type expected by the function. The 1990 ISO C standard includes rules that govern the mixing of old- and new-style function declarations since there are many, many lines of existing C code that could and should be converted to use prototypes.
When you write an entirely new program, use new-style function declarations (function prototypes) in headers and new-style function declarations and definitions in other C source files. However, if there is a possibility that someone will port the code to a machine with a pre-ISO C compiler, we suggest you use the macro __STDC__ (which is defined only for ISO C compilation systems) in both header and source files. Refer to Section 7.2.3, Mixing Considerations for an example.
An ISO C-conforming compiler must issue a diagnostic whenever two incompatible declarations for the same object or function are in the same scope. If all functions are declared and defined with prototypes, and the appropriate headers are included by the correct source files, all calls should agree with the definition of the functions. This protocol eliminates one of the most common C programming mistakes.
If you have an existing application and want the benefits of function prototypes, there are a number of possibilities for updating, depending on how much of the code you would like to change:
1. Recompile without making any changes.
Even with no coding changes, the compiler warns you about mismatches in parameter type and number when invoked with the -v option.
2. Add function prototypes just to the headers.
All calls to global functions are covered.
3. Add function prototypes to the headers and start each source file with function prototypes for its local (static) functions.
All calls to functions are covered, but doing this requires typing the interface for each local function twice in the source file.
4. Change all function declarations and definitions to use function prototypes.
For most programmers, choices 2 and 3 are probably the best cost/benefit compromise. Unfortunately, these options are precisely the ones that require detailed knowledge of the rules for mixing old and new styles.
For function prototype declarations to work with old-style function definitions, both must specify functionally identical interfaces or have compatible types using ISO C's terminology.
For functions with varying arguments, there can be no mixing of ISO C's ellipsis notation and the old-style varargs() function definition. For functions with a fixed number of parameters, the situation is fairly straightforward: just specify the types of the parameters as they were passed in previous implementations.
In K&R C, each argument was converted just before it was passed to the called function according to the default argument promotions. These promotions specified that all integral types narrower than int were promoted to int size, and any float argument was promoted to double, hence simplifying both the compiler and libraries. Function prototypes are more expressive--the specified parameter type is what is passed to the function.
Thus, if a function prototype is written for an existing (old-style) function definition, there should be no parameters in the function prototype with any of the following types:
There still remain two complications with writing prototypes: typedef names and the promotion rules for narrow unsigned types.
If parameters in old-style functions were declared using typedef names, such as off_t and ino_t, it is important to know whether or not the typedef name designates a type that is affected by the default argument promotions. For these two, off_t is a long, so it is appropriate to use in a function prototype; ino_t used to be an unsigned short, so if it were used in a prototype, the compiler issues a diagnostic because the old-style definition and the prototype specify different and incompatible interfaces.
Just what should be used instead of an unsigned short leads us into the final complication. The one biggest incompatibility between K&R C and the 1990 ISO C compiler is the promotion rule for the widening of unsigned char and unsigned short to an int value. (See Section 7.4, Promotions: Unsigned Versus Value Preserving.) The parameter type that matches such an old-style parameter depends on the compilation mode used when you compile:
The best approach is to change the old-style definition to specify either int or unsigned int and use the matching type in the function prototype. You can always assign its value to a local variable with the narrower type, if necessary, after you enter the function.
Watch out for the use of id's in prototypes that may be affected by preprocessing. Consider the following example:
#define status 23 void my_exit(int status); /* Normally, scope begins */ /* and ends with prototype */ |
Do not mix function prototypes with old-style function declarations that contain narrow types.
Appropriate use of __STDC__ produces a header file that can be used for both the old and new compilers:
header.h: struct s { /* . . . */ }; #ifdef __STDC__ void errmsg(int, ...); struct s *f(const char *); int g(void); #else void errmsg(); struct s *f(); int g(); #endif |
The following function uses prototypes and can still be compiled on an older system:
Here is an updated source file (as with choice 3 above). The local function still uses an old-style definition, but a prototype is included for newer compilers:
source.c: #include "header.h" typedef /* . . . */ MyType; #ifdef __STDC__ static void del(MyType *); /* . . . */ static void del(p) MyType *p; { /* . . . */ } /* . . . */ |
In previous implementations, you could not specify the parameter types that a function expected, but ISO C encourages you to use prototypes to do just that. To support functions such as printf(), the syntax for prototypes includes a special ellipsis (...) terminator. Because an implementation might need to do unusual things to handle a varying number of arguments, ISO C requires that all declarations and the definition of such a function include the ellipsis terminator.
Since there are no names for the "..." part of the parameters, a special set of macros contained in stdarg.h gives the function access to these arguments. Earlier versions of such functions had to use similar macros contained in varargs.h.
Let us assume that the function we wish to write is an error handler called errmsg() that returns void, and whose only fixed parameter is an int that specifies details about the error message. This parameter can be followed by a file name, a line number, or both, and these are followed by format and arguments, similar to those of printf(), that specify the text of the error message.
To allow our example to compile with earlier compilers, we make extensive use of the macro __STDC__ which is defined only for ISO C compilation systems. Thus, the function's declaration in the appropriate header file is:
The file that contains the definition of errmsg() is where the old and new styles can get complex. First, the header to include depends on the compilation system:
stdio.h is included because we call fprintf() and vfprintf() later.
Next comes the definition for the function. The identifiers va_alist and va_dcl are part of the old-style varargs.h interface.
void #ifdef __STDC__ errmsg(int code, ...) #else errmsg(va_alist) va_dcl /* Note: no semicolon! */ #endif { /* more detail below */ } |
Since the old-style variable argument mechanism did not allow us to specify any fixed parameters, we must arrange for them to be accessed before the varying portion. Also, due to the lack of a name for the "..." part of the parameters, the new va_start() macro has a second argument--the name of the parameter that comes just before the "..." terminator.
As an extension, Sun ISO C allows functions to be declared and defined with no fixed parameters, as in:
For such functions, va_start() should be invoked with an empty second argument, as in:
The following is the body of the function:
Both the va_arg() and va_end() macros work the same for the old-style and ISO C versions. Because va_arg() changes the value of ap, the call to vfprintf() cannot be:
The definitions for the macros FILENAME, LINENUMBER, and WARNING are presumably contained in the same header as the declaration of errmsg().
A sample call to errmsg() could be:
The following information appears in the Rationale section that accompanies the 1990 ISO C Standard: "QUIET CHANGE". A program that depends on unsigned preserving arithmetic conversions will behave differently, probably without complaint. This is considered to be the most serious change made by the Committee to a widespread current practice.
This section explores how this change affects our code.
According to K&R, The C Programming Language (First Edition), unsigned specified exactly one type; there were no unsigned chars, unsigned shorts, or unsigned longs, but most C compilers added these very soon thereafter. Some compilers did not implement unsigned long but included the other two. Naturally, implementations chose different rules for type promotions when these new types mixed with others in expressions.
In most C compilers, the simpler rule, "unsigned preserving," is used: when an unsigned type needs to be widened, it is widened to an unsigned type; when an unsigned type mixes with a signed type, the result is an unsigned type.
The other rule, specified by ISO C, is known as "value preserving," in which the result type depends on the relative sizes of the operand types. When an unsigned char or unsigned short is widened, the result type is int if an int is large enough to represent all the values of the smaller type. Otherwise, the result type is unsigned int. The value preserving rule produces the least surprise arithmetic result for most expressions.
Only in the transition or ISO modes (-Xt or -Xs) does the ISO C compiler use the unsigned preserving promotions; in the other two modes, conforming (-Xc) and ISO (-Xa), the value preserving promotion rules are used.
In the following code, assume that an unsigned char is smaller than an int.
The code above causes the compiler to issue the following warning when you use the -xtransition option:
line 6: warning: semantics of "<" change in ISO C; use explicit cast
The result of the addition has type int (value preserving) or unsigned int (unsigned preserving), but the bit pattern does not change between these two. On a two's-complement machine, we have:
This bit representation corresponds to -1 for int and UINT_MAX for unsigned int. Thus, if the result has type int, a signed comparison is used and the less-than test is true; if the result has type unsigned int, an unsigned comparison is used and the less-than test is false.
The addition of a cast serves to specify which of the two behaviors is desired:
Since differing compilers chose different meanings for the same code, this expression can be ambiguous. The addition of a cast is as much to help the reader as it is to eliminate the warning message.
The same situation applies to the promotion of bit-field values. In ISO C, if the number of bits in an int or unsigned int bit-field is less than the number of bits in an int, the promoted type is int; otherwise, the promoted type is unsigned int. In most older C compilers, the promoted type is unsigned int for explicitly unsigned bit-fields, and int otherwise.
Similar use of casts can eliminate situations that are ambiguous.
In the following code, assume that both unsigned short and unsigned char are narrower than int.
In this example, both automatics are either promoted to int or to unsigned int, so the comparison is sometimes unsigned and sometimes signed. However, the C compiler does not warn you because the result is the same for the two choices.
As with expressions, the rules for the types of certain integral constants have changed. In K&R C, an unsuffixed decimal constant had type int only if its value fit in an int; an unsuffixed octal or hexadecimal constant had type int only if its value fit in an unsigned int. Otherwise, an integral constant had type long. At times, the value did not fit in the resulting type. In the 1990 ISO/IEC C standard, the constant type is the first type encountered in the following list that corresponds to the value:
The ISO C compiler warns you, when you use the -xtransition option, about any expression whose behavior might change according to the typing rules of the constants involved. The old integral constant typing rules are used only in the transition mode; the ISO and conforming modes use the new rules.
Note - The rules for typing unsuffixed decimal constants has changed in accordance with the 1999 ISO C standard. See Section 2.1.1, Integral Constants. |
In the following code, assume ints are 16 bits.
Because the hexadecimal constant's type is either int (with a value of -1 on a two's-complement machine) or an unsigned int (with a value of 65535), the comparison is true in -Xs and -Xt modes, and false in -Xa and -Xc modes.
Again, an appropriate cast clarifies the code and suppresses a warning:
The U suffix character is a new feature of ISO C and probably produces an error message with older compilers.
Probably the least specified part of previous versions of C concerned the operations that transformed each source file from a bunch of characters into a sequence of tokens, ready to parse. These operations included recognition of white space (including comments), bundling consecutive characters into tokens, handling preprocessing directive lines, and macro replacement. However, their respective ordering was never guaranteed.
The order of these translation phases is specified by ISO C.
Every trigraph sequence in the source file is replaced. ISO C has exactly nine trigraph sequences that were invented solely as a concession to deficient character sets, and are three-character sequences that name a character not in the ISO 646-1983 character set:
These sequences must be understood by ISO C compilers, but we do not recommend their use. The ISO C compiler warns you, when you use the -xtransition option, whenever it replaces a trigraph while in transition (-Xt) mode, even in comments. For example, consider the following:
The ??/ becomes a backslash. This character and the following newline are removed. The resulting characters are:
The first / from the second line is the end of the comment. The next token is the *.
1. Every backslash/new-line character pair is deleted.
2. The source file is converted into preprocessing tokens and sequences of white space. Each comment is effectively replaced by a space character.
3. Every preprocessing directive is handled and all macro invocations are replaced. Each #included source file is run through the earlier phases before its contents replace the directive line.
4. Every escape sequence (in character constants and string literals) is interpreted.
5. Adjacent string literals are concatenated.
6. Every preprocessing token is converted into a regular token; the compiler properly parses these and generates code.
7. All external object and function references are resolved, resulting in the final program.
Previous C compilers did not follow such a simple sequence of phases, nor were there any guarantees for when these steps were applied. A separate preprocessor recognized tokens and white space at essentially the same time as it replaced macros and handled directive lines. The output was then completely retokenized by the compiler proper, which then parsed the language and generated code.
Because the tokenization process within the preprocessor was a moment-by-moment operation and macro replacement was done as a character-based, not token-based, operation, the tokens and white space could have a great deal of variation during preprocessing.
There are a number of differences that arise from these two approaches. The rest of this section discusses how code behavior may change due to line splicing, macro replacement, stringizing, and token pasting, which occur during macro replacement.
In K&R C, backslash/new-line pairs were allowed only as a means to continue a directive, a string literal, or a character constant to the next line. ISO C extended the notion so that a backslash/new-line pair can continue anything to the next line. The result is a logical source line. Therefore, any code that relied on the separate recognition of tokens on either side of a backslash/new-line pair does not behave as expected.
The macro replacement process has never been described in detail prior to ISO C. This vagueness spawned a great many divergent implementations. Any code that relied on anything fancier than manifest constant replacement and simple function-like macros was probably not truly portable. This manual cannot uncover all the differences between the old C macro replacement implementation and the ISO C version. Nearly all uses of macro replacement with the exception of token pasting and stringizing produce exactly the same series of tokens as before. Furthermore, the ISO C macro replacement algorithm can do things not possible in the old C version. For example,
causes any use of name to be replaced with an indirect reference through name. The old C preprocessor would produce a huge number of parentheses and stars and eventually produce an error about macro recursion.
The major change in the macro replacement approach taken by ISO C is to require macro arguments, other than those that are operands of the macro substitution operators # and ##, to be expanded recursively prior to their substitution in the replacement token list. However, this change seldom produces an actual difference in the resulting tokens.
In K&R C, the following code produced the string literal "x y!":
Thus, the preprocessor searched inside string literals and character constants for characters that looked like macro parameters. ISO C recognized the importance of this feature, but could not condone operations on parts of tokens. In ISO C, all invocations of the above macro produce the string literal "a!". To achieve the old effect in ISO C, we make use of the # macro substitution operator and the concatenation of string literals.
The above code produces the two string literals "x y" and "!" which, after concatenation, produces the identical "x y!".
There is no direct replacement for the analogous operation for character constants. The major use of this feature was similar to the following:
which evaluates to the ASCII control-L character. The best solution we know of is to change all uses of this macro to:
This code is more readable and more useful, as it can also be applied to expressions.
In K&R C, there were at least two ways to combine two tokens. Both invocations in the following produced a single identifier x1 out of the two tokens x and 1.
Again, ISO C could not sanction either approach. In ISO C, both the above invocations would produce the two separate tokens x and 1. The second of the above two methods can be rewritten for ISO C by using the ## macro substitution operator:
# and ## should be used as macro substitution operators only when __STDC__ is defined. Since ## is an actual operator, the invocation can be much freer with respect to white space in both the definition and invocation.
There is no direct approach to effect the first of the two old-style pasting schemes, but since it put the burden of the pasting at the invocation, it was used less frequently than the other form.
The keyword const was one of the C++ features that found its way into ISO C. When an analogous keyword, volatile, was invented by the ISO C Committee, the "type qualifier" category was created.
const and volatile are part of an identifier's type, not its storage class. However, they are often removed from the topmost part of the type when an object's value is fetched in the evaluation of an expression--exactly at the point when an lvalue becomes an rvalue. These terms arise from the prototypical assignment "L=R"; in which the left side must still refer directly to an object (an lvalue) and the right side need only be a value (an rvalue). Thus, only expressions that are lvalues can be qualified by const or volatile or both.
The type qualifiers may modify type names and derived types. Derived types are those parts of C's declarations that can be applied over and over to build more and more complex types: pointers, arrays, functions, structures, and unions. Except for functions, one or both type qualifiers can be used to change the behavior of a derived type.
declares and initializes an object with type const int whose value is not changed by a correct program. The order of the keywords is not significant to C. For example, the declarations:
are identical to the above declaration in its effect.
declares an object with type pointer to const int, which initially points to the previously declared object. The pointer itself does not have a qualified type--it points to a qualified type, and can be changed to point to essentially any int during program execution. pci cannot be used to modify the object to which it points unless a cast is used, as in the following:
If pci actually points to a const object, the behavior of this code is undefined.
says that somewhere in the program there exists a definition of a global object with type const pointer to int. In this case, cpi's value will not be changed by a correct program, but it can be used to modify the object to which it points. Notice that const comes after the * in the above declaration. The following pair of declarations produces the same effect:
These declarations can be combined as in the following declaration in which an object is declared to have type const pointer to const int:
In hindsight, readonly would have been a better choice for a keyword than const. If one reads const in this manner, declarations such as:
are easily understood to mean that the second parameter is only used to read character values, while the first parameter overwrites the characters to which it points. Furthermore, despite the fact that in the above example, the type of cpi is a pointer to a const int, you can still change the value of the object to which it points through some other means, unless it actually points to an object declared with const int type.
The two main uses for const are to declare large compile-time initialized tables of information as unchanging, and to specify that pointer parameters do not modify the objects to which they point.
The first use potentially allows portions of the data for a program to be shared by other concurrent invocations of the same program. It may cause attempts to modify this invariant data to be detected immediately by means of some sort of memory protection fault, since the data resides in a read-only portion of memory.
The second use helps locate potential errors before generating a memory fault during that demo. For example, functions that temporarily place a null character into the middle of a string are detected at compile time, if passed a pointer to a string that cannot be so modified.
So far, the examples have all used const because it's conceptually simpler. But what does volatile really mean? To a compiler writer, it has one meaning: take no code generation shortcuts when accessing such an object. In ISO C, it is a programmer's responsibility to declare every object that has the appropriate special properties with a volatile qualified type.
The usual four examples of volatile objects are:
The first three examples are all instances of an object with a particular behavior: its value can be modified at any point during the execution of the program. Thus, the seemingly infinite loop:
is valid as long as flag has a volatile qualified type. Presumably, some asynchronous event sets flag to zero in the future. Otherwise, because the value of flag is unchanged within the body of the loop, the compilation system is free to change the above loop into a truly infinite loop that completely ignores the value of flag.
The fourth example, involving variables local to functions that call setjmp, is more involved. The fine print about the behavior of setjmp and longjmp notes that there are no guarantees about the values for objects matching the fourth case. For the most desirable behavior, it is necessary for longjmp to examine every stack frame between the function calling setjmp and the function calling longjmp for saved register values. The possibility of asynchronously created stack frames makes this job even harder.
When an automatic object is declared with a volatile qualified type, the compilation system knows that it has to produce code that exactly matches what the programmer wrote. Therefore, the most recent value for such an automatic object is always in memory and not just in a register, and is guaranteed to be up-to-date when longjmp is called.
At first, the internationalization of ISO C affected only library functions. However, the final stage of internationalization--multibyte characters and wide characters--also affected the language proper.
The basic difficulty in an Asian-language computer environment is the huge number of ideograms needed for I/O. To work within the constraints of usual computer architectures, these ideograms are encoded as sequences of bytes. The associated operating systems, application programs, and terminals understand these byte sequences as individual ideograms. Moreover, all of these encodings allow intermixing of regular single-byte characters with the ideogram byte sequences. Just how difficult it is to recognize distinct ideograms depends on the encoding scheme used.
The term "multibyte character" is defined by ISO C to denote a byte sequence that encodes an ideogram, no matter what encoding scheme is employed. All multibyte characters are members of the "extended character set." A regular single-byte character is just a special case of a multibyte character. The only requirement placed on the encoding is that no multibyte character can use a null character as part of its encoding.
ISO C specifies that program comments, string literals, character constants, and header names are all sequences of multibyte characters.
The encoding schemes fall into two camps. The first is one in which each multibyte character is self-identifying, that is, any multibyte character can simply be inserted between any pair of multibyte characters.
The second scheme is one in which the presence of special shift bytes changes the interpretation of subsequent bytes. An example is the method used by some character terminals to get in and out of line-drawing mode. For programs written in multibyte characters with a shift-state-dependent encoding, ISO C requires that each comment, string literal, character constant, and header name must both begin and end in the unshifted state.
Some of the inconvenience of handling multibyte characters would be eliminated if all characters were of a uniform number of bytes or bits. Since there can be thousands or tens of thousands of ideograms in such a character set, a 16-bit or 32-bit sized integral value should be used to hold all members. (The full Chinese alphabet includes more than 65,000 ideograms!) ISO C includes the typedef name wchar_t as the implementation-defined integral type large enough to hold all members of the extended character set.
For each wide character, there is a corresponding multibyte character, and vice versa; the wide character that corresponds to a regular single-byte character is required to have the same value as its single-byte value, including the null character. However, there is no guarantee that the value of the macro EOF can be stored in a wchar_t, just as EOF might not be representable as a char.
The 1990 ISO/IEC C standard provides five library functions that manage multibyte characters and wide characters, the 1999 ISO/IEC C standard provides many more such functions.
To give even more flexibility to the programmer in an Asian-language environment, ISO C provides wide character constants and wide string literals. These have the same form as their non-wide versions, except that they are immediately prefixed by the letter L:
Multibyte characters are valid in both the regular and wide versions. The sequence of bytes necessary to produce the ideogram ¥ is encoding-specific, but if it consists of more than one byte, the value of the character constant '¥' is implementation-defined, just as the value of 'ab' is implementation-defined. Except for escape sequences, a regular string literal contains exactly the bytes specified between the quotes, including the bytes of each specified multibyte character.
When the compilation system encounters a wide character constant or wide string literal, each multibyte character is converted into a wide character, as if by calling the mbtowc() function. Thus, the type of L'¥' is wchar_t; the type of abc¥xyz is array of wchar_t with length eight. Just as with regular string literals, each wide string literal has an extra zero-valued element appended, but in these cases, it is a wchar_t with value zero.
Just as regular string literals can be used as a shorthand method for character array initialization, wide string literals can be used to initialize wchar_t arrays:
wchar_t *wp = L"a¥z"; wchar_t x[] = L"a¥z"; wchar_t y[] = {L'a', L'¥', L'z', 0}; wchar_t z[] = {'a', L'¥', 'z', '\0'}; |
In the above example, the three arrays x, y, and z, and the array pointed to by wp, have the same length. All are initialized with identical values.
Finally, adjacent wide string literals are concatenated, just as with regular string literals. However, with the 1990 ISO/IEC C standard, adjacent regular and wide string literals produce undefined behavior. Also, the 1990 ISO/IEC C standard specifies that a compiler is not required to produce an error if it does not accept such concatenations.
Early in the standardization process, the ISO Standards Committee chose to include library functions, macros, and header files as part of ISO C.
This section presents the various categories of reserved names and some rationale for their reservations. At the end is a set of rules to follow that can steer your programs clear of any reserved names.
Most implementations provide more headers, but a strictly conforming 1990 ISO/IEC C program can only use these.
Other standards disagree slightly regarding the contents of some of these headers. For example, POSIX (IEEE 1003.1) specifies that fdopen is declared in stdio.h. To allow these two standards to coexist, POSIX requires the macro _POSIX_SOURCE to be #defined prior to the inclusion of any header to guarantee that these additional names exist. In its Portability Guide, X/Open has also used this macro scheme for its extensions. X/Open's macro is _XOPEN_SOURCE.
ISO C requires the standard headers to be both self-sufficient and idempotent. No standard header needs any other header to be #included before or after it, and each standard header can be #included more than once without causing problems. The Standard also requires that its headers be #included only in safe contexts, so that the names used in the headers are guaranteed to remain unchanged.
The Standard places further restrictions on implementations regarding their libraries. In the past, most programmers learned not to use names like read and write for their own functions on UNIX Systems. ISO C requires that only names reserved by the Standard be introduced by references within the implementation.
Thus, the Standard reserves a subset of all possible names for implementations to use. This class of names consists of identifiers that begin with an underscore and continue with either another underscore or a capital letter. The class of names contains all names matching the following regular expression:
Strictly speaking, if your program uses such an identifier, its behavior is undefined. Thus, programs using _POSIX_SOURCE (or _XOPEN_SOURCE) have undefined behavior.
However, undefined behavior comes in different degrees. If, in a POSIX-conforming implementation you use _POSIX_SOURCE, you know that your program's undefined behavior consists of certain additional names in certain headers, and your program still conforms to an accepted standard. This deliberate loophole in the ISO C standard allows implementations to conform to seemingly incompatible specifications. On the other hand, an implementation that does not conform to the POSIX standard is free to behave in any manner when encountering a name such as _POSIX_SOURCE.
The Standard also reserves all other names that begin with an underscore for use in header files as regular file scope identifiers and as tags for structures and unions, but not in local scopes. The common practice of having functions named _filbuf and _doprnt to implement hidden parts of the library is allowed.
In addition to all the names explicitly reserved, the 1990 ISO/IEC C standard also reserves (for implementations and future standards) names matching certain patterns:
In the above lists, names that begin with a capital letter are macros and are reserved only when the associated header is included. The rest of the names designate functions and cannot be used to name any global objects or functions.
There are four simple rules you can follow to keep from colliding with any ISO C reserved names:
These rules are just a general guideline to follow, as most implementations will continue to add names to the standard headers by default.
The section Section 7.7, Multibyte Characters and Wide Characters introduced the internationalization of the standard libraries. This section discusses the affected library functions and gives some hints on how programs should be written to take advantage of these features. The section only discusses internationalization with respect to the 1990 ISO/IEC C standard. The 1999 ISO/IEC C standard has no significant extension to support internationalization over those discussed here.
At any time, a C program has a current locale--a collection of information that describes the conventions appropriate to some nationality, culture, and language. Locales have names that are strings. The only two standardized locale names are "C" and "". Each program begins in the "C" locale, which causes all library functions to behave just like they have historically. The "" locale is the implementation's best guess at the correct set of conventions appropriate to the program's invocation. "C" and "" can cause identical behavior. Other locales may be provided by implementations.
For the purposes of practicality and expediency, locales are partitioned into a set of categories. A program can change the complete locale, or just one or more categories. Generally, each category affects a set of functions disjoint from the functions affected by other categories, so temporarily changing one category for a little while can make sense.
The setlocale() function is the interface to the program's locale. In general, any program that uses the invocation country's conventions should place a call such as:
early in the program's execution path. This call causes the program's current locale to change to the appropriate local version, since LC_ALL is the macro that specifies the entire locale instead of one category. The following are the standard categories:
Any of these macros can be passed as the first argument to setlocale() to specify that category.
The setlocale() function returns the name of the current locale for a given category (or LC_ALL) and serves in an inquiry-only capacity when its second argument is a null pointer. Thus, code similar to the following can be used to change the locale or a portion thereof for a limited duration:
Most programs do not need this capability.
Wherever possible and appropriate, existing library functions were extended to include locale-dependent behavior. These functions came in two groups:
All ctype.h predicate functions, except isdigit() and isxdigit(), can return nonzero (true) for additional characters when the LC_CTYPE category of the current locale is other than "C". In a Spanish locale, isalpha('ñ') should be true. Similarly, the character conversion functions, tolower() and toupper(), should appropriately handle any extra alphabetic characters identified by the isalpha() function. The ctype.h functions are almost always macros that are implemented using table lookups indexed by the character argument. Their behavior is changed by resetting the table(s) to the new locale's values, and therefore there is no performance impact.
Those functions that write or interpret printable floating values can change to use a decimal-point character other than period (.) when the LC_NUMERIC category of the current locale is other than "C". There is no provision for converting any numeric values to printable form with thousands separator-type characters. When converting from a printable form to an internal form, implementations are allowed to accept such additional forms, again in other than the "C" locale. Those functions that make use of the decimal-point character are the printf() and scanf() families, atof(), and strtod(). Those functions that are allowed implementation-defined extensions are atof(), atoi(), atol(), strtod(), strtol(), strtoul(), and the scanf() family.
Certain locale-dependent capabilities were added as new standard functions. Besides setlocale(), which allows control over the locale itself, the Standard includes the following new functions:
In addition, there are the multibyte functions mblen(), mbtowc(), mbstowcs(), wctomb(), and wcstombs().
The localeconv() function returns a pointer to a structure containing information useful for formatting numeric and monetary information appropriate to the current locale's LC_NUMERIC and LC_MONETARY categories. This is the only function whose behavior depends on more than one category. For numeric values, the structure describes the decimal-point character, the thousands separator, and where the separator(s) should be located. There are fifteen other structure members that describe how to format a monetary value.
The strcoll() function is analogous to the strcmp() function, except that the two strings are compared according to the LC_COLLATE category of the current locale. The strxfrm() function can also be used to transform a string into another, such that any two such after-translation strings can be passed to strcmp(), and get an ordering analogous to what strcoll() would have returned if passed the two pre-translation strings.
The strftime() function provides formatting similar to that used with sprintf() of the values in a struct tm, along with some date and time representations that depend on the LC_TIME category of the current locale. This function is based on the ascftime() function released as part of UNIX System V Release 3.2.
One of the choices made by Dennis Ritchie in the design of C was to give compilers a license to rearrange expressions involving adjacent operators that are mathematically commutative and associative, even in the presence of parentheses. This is explicitly noted in the appendix in the The C Programming Language by Kernighan and Ritchie. However, ISO C does not grant compilers this same freedom.
This section discusses the differences between these two definitions of C and clarifies the distinctions between an expression's side effects, grouping, and evaluation by considering the expression statement from the following code fragment.
The side effects of an expression are its modifications to memory and its accesses to volatile qualified objects. The side effects in the above expression are the updating of i and p and any side effects contained within the functions f() and g().
An expression's grouping is the way values are combined with other values and operators. The above expression's grouping is primarily the order in which the additions are performed.
An expression's evaluation includes everything necessary to produce its resulting value. To evaluate an expression, all specified side effects must occur anywhere between the previous and next sequence point, and the specified operations are performed with a particular grouping. For the above expression, the updating of i and p must occur after the previous statement and by the ; of this expression statement; the calls to the functions can occur in either order, any time after the previous statement, but before their return values are used. In particular, the operators that cause memory to be updated have no requirement to assign the new value before the value of the operation is used.
The K&R C rearrangement license applies to the above expression because addition is mathematically commutative and associative. To distinguish between regular parentheses and the actual grouping of an expression, the left and right curly braces designate grouping. The three possible groupings for the expression are:
All of these are valid given K&R C rules. Moreover, all of these groupings are valid even if the expression were written instead, for example, in either of these ways:
If this expression is evaluated on an architecture for which either overflows cause an exception, or addition and subtraction are not inverses across an overflow, these three groupings behave differently if one of the additions overflows.
For such expressions on these architectures, the only recourse available in K&R C was to split the expression to force a particular grouping. The following are possible rewrites that respectively enforce the above three groupings:
ISO C does not allow operations to be rearranged that are mathematically commutative and associative, but that are not actually so on the target architecture. Thus, the precedence and associativity of the ISO C grammar completely describes the grouping for all expressions; all expressions must be grouped as they are parsed. The expression under consideration is grouped in this manner:
This code still does not mean that f() must be called before g(), or that p must be incremented before g() is called.
In ISO C, expressions need not be split to guard against unintended overflows.
ISO C is often erroneously described as honoring parentheses or evaluating according to parentheses due to an incomplete understanding or an inaccurate presentation.
Since ISO C expressions simply have the grouping specified by their parsing, parentheses still only serve as a way of controlling how an expression is parsed; the natural precedence and associativity of expressions carry exactly the same weight as parentheses.
The above expression could have been written as:
with no different effect on its grouping or evaluation.
There were several reasons for the K&R C rearrangement rules:
The ISO C Committee eventually became convinced that the rearrangement rules were intended to be an instance of the as if rule when applied to the described target architectures. ISO C's as if rule is a general license that permits an implementation to deviate arbitrarily from the abstract machine description as long as the deviations do not change the behavior of a valid C program.
Thus, all the binary bitwise operators (other than shifting) are allowed to be rearranged on any machine because there is no way to notice such regroupings. On typical two's-complement machines in which overflow wraps around, integer expressions involving multiplication or addition can be rearranged for the same reason.
Therefore, this change in C does not have a significant impact on most C programmers.
The ISO C standard introduced the term "incomplete type" to formalize a fundamental, yet misunderstood, portion of C, implicit from its beginnings. This section describes incomplete types, where they are permitted, and why they are useful.
ISO separates C's types into three distinct sets: function, object, and incomplete. Function types are obvious; object types cover everything else, except when the size of the object is not known. The Standard uses the term "object type" to specify that the designated object must have a known size, but it is important to know that incomplete types other than void also refer to an object.
There are only three variations of incomplete types: void, arrays of unspecified length, and structures and unions with unspecified content. The type void differs from the other two in that it is an incomplete type that cannot be completed, and it serves as a special function return and parameter type.
An array type is completed by specifying the array size in a following declaration in the same scope that denotes the same object. When an array without a size is declared and initialized in the same declaration, the array has an incomplete type only between the end of its declarator and the end of its initializer.
An incomplete structure or union type is completed by specifying the content in a following declaration in the same scope for the same tag.
Certain declarations can use incomplete types, but others require complete object types. Those declarations that require object types are array elements, members of structures or unions, and objects local to a function. All other declarations permit incomplete types. In particular, the following constructs are permitted:
The function return and parameter types are special. Except for void, an incomplete type used in such a manner must be completed by the time the function is defined or called. A return type of void specifies a function that returns no value, and a single parameter type of void specifies a function that accepts no arguments.
Since array and function parameter types are rewritten to be pointer types, a seemingly incomplete array parameter type is not actually incomplete. The typical declaration of main's argv, namely, char *argv[], as an unspecified length array of character pointers, is rewritten to be a pointer to character pointers.
Most expression operators require complete object types. The only three exceptions are the unary & operator, the first operand of the comma operator, and the second and third operands of the ?: operator. Most operators that accept pointer operands also permit pointers to incomplete types, unless pointer arithmetic is required. The list includes the unary * operator. For example, given:
&*p is a valid subexpression that makes use of this.
Why are incomplete types necessary? Ignoring void, there is only one feature provided by incomplete types that C has no other way to handle, and that has to do with forward references to structures and unions. If one has two structures that need pointers to each other, the only way to do so is with incomplete types:
All strongly typed programming languages that have some form of pointer and heterogeneous data types provide some method of handling this case.
Defining typedef names for incomplete structure and union types is frequently useful. If you have a complicated bunch of data structures that contain many pointers to each other, having a list of typedefs to the structures up front, possibly in a central header, can simplify the declarations.
typedef struct item_tag Item; typedef union note_tag Note; typedef struct list_tag List; . . . struct item_tag { . . . }; . . . struct list_tag { struct list_tag { }; |
Moreover, for those structures and unions whose contents should not be available to the rest of the program, a header can declare the tag without the content. Other parts of the program can use pointers to the incomplete structure or union without any problems, unless they attempt to use any of its members.
A frequently used incomplete type is an external array of unspecified length. Generally, it is not necessary to know the extent of an array to make use of its contents.
With K&R C, and even more so with ISO C, it is possible for two declarations that refer to the same entity to be other than identical. The term "compatible type" is used in ISO C to denote those types that are "close enough". This section describes compatible types as well as "composite types"--the result of combining two compatible types.
If a C program were only allowed to declare each object or function once, there would be no need for compatible types. Linkage, which allows two or more declarations to refer to the same entity, function prototypes, and separate compilation all need such a capability. Separate translation units (source files) have different rules for type compatibility from within a single translation unit.
Since each compilation probably looks at different source files, most of the rules for compatible types across separate compiles are structural in nature:
An additional requirement is that the names of members, including the lack of names for unnamed members, match for structures, unions, and enums, but not necessarily their respective tags.
When two declarations in the same scope describe the same object or function, the two declarations must specify compatible types. These two types are then combined into a single composite type that is compatible with the first two. More about composite types later.
The compatible types are defined recursively. At the bottom are type specifier keywords. These are the rules that say that unsigned short is the same as unsigned short int, and that a type without type specifiers is the same as one with int. All other types are compatible only if the types from which they are derived are compatible. For example, two qualified types are compatible if the qualifiers, const and volatile, are identical, and the unqualified base types are compatible.
For two pointer types to be compatible, the types they point to must be compatible and the two pointers must be identically qualified. Recall that the qualifiers for a pointer are specified after the *, so that these two declarations
declare two differently qualified pointers to the same type, int.
For two array types to be compatible, their element types must be compatible. If both array types have a specified size, they must match, that is, an incomplete array type (see Section 7.11, Incomplete Types) is compatible both with another incomplete array type and an array type with a specified size.
To make functions compatible, follow these rules:
signed int behaves the same as int, except possibly for bit-fields, in which a plain int may denote an unsigned-behaving quantity.
Another interesting note is that each enumeration type must be compatible with some integral type. For portable programs, this means that enumeration types are separate types. In general, the ISO C standard views them in that manner.
The construction of a composite type from two compatible types is also recursively defined. The ways compatible types can differ from each other are due either to incomplete arrays or to old-style function types. As such, the simplest description of the composite type is that it is the type compatible with both of the original types, including every available array size and every available parameter list from the original types.
Copyright © 2004, Sun Microsystems, Inc. All rights reserved.