4.1.1 The ANSI Standard
The ANSI C Standard has to reconcile two opposing goals: freedom for compiler vendors to target new devices and improve code generation, with the known functional operation of source code for programmers. If both goals can be met, source code can be made portable.
The standard is implemented as a set of rules which detail not only the syntax that a conforming C program must follow, but also the semantic rules by which that program will be interpreted. Thus, for a compiler to conform to the standard, it must ensure that a conforming C program functions as described by the standard.
The standard describes implementation
, the set of tools,
and the runtime environment on which the code will run. If any of these change, e.g., you
build for, and run on, a different target device, or if you update the version of the
compiler you use to build, then you are using a different implementation.
The standard uses the term behavior
to mean the external
appearance or action of the program. It has nothing to do with how a program is
encoded.
Since the standard is trying to achieve goals that could be construed as
conflicting, some specifications appear somewhat vague. For example, the standard states
that an int
type must be able to hold at least a 16-bit value, but it does
not go as far as saying what the size of an int
actually is; and the
action of right-shifting a signed integer can produce different results on different
implementations; yet, these different results are still ANSI C compliant.
The standard organizes source code whose behavior is not fully defined into groups that include the following behaviors:
Implementation-defined behavior | This is unspecified behavior in which each implementation documents how the choice is made. |
Unspecified behavior | The standard provides two or more possibilities and imposes no further requirements on which possibility is chosen in any particular instance. |
Undefined behavior | This is behavior for which the standard imposes no requirements. |
Code that strictly conforms to the standard does not produce output that
is dependent on any unspecified, undefined, or implementation-defined behavior. The size of
an int
, which was used as an example earlier, falls into the category of
behavior that is defined by implementation. That is to say, the size of an
int
is defined by which compiler is being used, how that compiler is
being used, and the device that is being targeted.
All the MPLAB XC compilers conform to the ANSI X3.159-1989 Standard for programming languages (with the exception of the MPLAB XC8 compiler’s inability to allow recursion, as mentioned in the footnote). This is commonly called the C89 Standard. Some features from the later standard, C99, are also supported.
For freestanding implementations (or for what we typically call embedded applications), the standard allows non-standard extensions to the language, but obviously does not enforce how they are specified or how they work. When working so closely to the device hardware, a programmer needs a means of specifying device setup and interrupts, as well as utilizing the often complex world of small-device memory architectures. This cannot be offered by the standard in a consistent way.
While the ANSI C Standard provides a mutual understanding for programmers and compiler vendors, programmers need to consider the implementation-defined behavior of their tools and the probability that they may need to use extensions to the C language that are non-standard. Both of these circumstances can have an impact on code portability.
For example, the mid-range PIC® microcontrollers do not have a data stack. Because a compiler targeting this device cannot implement recursion, it (strictly speaking) cannot conform to the ANSI C Standard. This example illustrates a situation in which the standard is too strict for mid-range devices and tools.