Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
<!-- Crown Copyright (c) 1998 -->
<HTML>
<HEAD>
<TITLE>C Checker Reference Manual: Type Checking</TITLE>
</HEAD>
<BODY TEXT="#000000" BGCOLOR="#FFFFFF" LINK="#0000FF" VLINK="#400080" ALINK="#FF0000">
<A NAME=S15>
<H1>C Checker Reference Manual</H1>
<H3>January 1998</H3>
<A HREF="tdfc7.html"><IMG SRC="../images/next.gif" ALT="next section"></A>
<A HREF="tdfc5.html"><IMG SRC="../images/prev.gif" ALT="previous section"></A>
<A HREF="tdfc1.html"><IMG SRC="../images/top.gif" ALT="current document"></A>
<A HREF="../index.html"><IMG SRC="../images/home.gif" ALT="TenDRA home page">
</A>
<IMG SRC="../images/no_index.gif" ALT="document index"><P>
<HR>
<DL>
<DT><A HREF="#S16"><B>3.1 </B> - Introduction</A><DD>
<DT><A HREF="#S17"><B>3.2 </B> - Type conversions</A><DD>
<DL>
<DT><A HREF="#S18"><B>3.2.1 </B> - Integer to integer conversions
</A><DD>
<DT><A HREF="#S19"><B>3.2.2 </B> - Pointer to integer and integer
to pointer conversions</A><DD>
<DT><A HREF="#S20"><B>3.2.3 </B> - Pointer to pointer conversions
</A><DD>
<DT><A HREF="#S21"><B>3.2.4 </B> - Example: 64-bit portability issues</A><DD>
</DL>
<DT><A HREF="#S22"><B>3.3 </B> - Function type checking</A><DD>
<DL>
<DT><A HREF="#S23"><B>3.3.1 </B> - Type checking non-prototyped functions</A>
<DD>
<DT><A HREF="#S24"><B>3.3.2 </B> - Checking printf strings</A><DD>
<DT><A HREF="#S25"><B>3.3.3 </B> - Function return checking</A><DD>
</DL>
<DT><A HREF="#S26"><B>3.4 </B> - Overriding type checking</A><DD>
<DL>
<DT><A HREF="#S27"><B>3.4.1 </B> - Implicit Function Declarations</A><DD>
<DT><A HREF="#S28"><B>3.4.2 </B> - Function Parameters</A><DD>
<DT><A HREF="#S29"><B>3.4.3 </B> - Incompatible promoted function
arguments</A><DD>
<DT><A HREF="#S30"><B>3.4.4 </B> - Incompatible type qualifiers</A><DD>
</DL>
</DL>
<HR>
<H1>3 Type Checking</H1>
<A NAME=S16>
<HR><H2>3.1 Introduction</H2>
Type checking is relevant to two main areas of C. It ensures that
all declarations referring to the same object are consistent (clearly
a pre-requisite for a well-defined program). It is also the key to
determining when an undefined or unexpected value has been produced
due to the type conversions which arise from certain operations in
C. Conversions may be explicit (conversion is specified by a cast)
or implicit. Generally explicit conversions may be regarded more leniently
since the programmer was obviously aware of the conversion, whereas
the implications of an implicit conversion may not have been considered.<P>
<A NAME=S17>
<HR><H2>3.2 <A NAME=2>Type conversions</H2>
The only types which may be interconverted legally are integral types,
floating point types and pointer types. Even if these rules are observed,
the results of some conversions can be surprising and may vary on
different machines. The checker can detect three categories of conversion:
integer to integer conversions, pointer to integer and integer to
pointer conversions, and pointer to pointer conversions.<P>
In the default mode, the checker allows all integer to integer conversions,
explicit integer to pointer and pointer to integer conversions and
the explicit pointer to pointer conversions defined by the ISO C standard
(all conversions between pointers to function types and other pointers
are undefined according to the ISO C standard).<P>
Checks to detect these conversions are controlled by the pragma:<P>
<PRE>
#pragma TenDRA conversion analysis <EM>status</EM>
</PRE>
Unless explicitly stated to the contrary, throughout the rest of the
document where <EM>status</EM> appears in a pragma statement it represents
one of <CODE>on</CODE> (enable the check and produce errors),
<CODE>warning</CODE> (enable the check but produce only warnings),
or <CODE>off</CODE> (disable the check). Here <EM>status</EM> may
be <CODE>on</CODE> to give an error if a conversion is detected, <CODE>warning
</CODE> to produce a warning if a conversion is detected, or <CODE>off</CODE>
to switch the checks off. The checks may also be controlled using
the command line option<CODE>
-X:</CODE><EM>test</EM><CODE>=</CODE><EM>state</EM><CODE> </CODE>where
<EM>test</EM> is one of <CODE>convert_all</CODE>, <CODE>convert_int</CODE>,
<CODE>convert_int_explicit</CODE>, <CODE>convert_int_implicit</CODE>,
<CODE>convert_int_ptr</CODE> and <CODE>convert_ptr</CODE> and <EM>state</EM>
is <CODE>check</CODE>,<CODE>warn</CODE> or <CODE>dont</CODE>.<P>
Due to the serious nature of implicit pointer to integer, implicit
pointer to pointer conversions and undefined explicit pointer to pointer
conversions, such conversions are flagged as errors by default. These
conversion checks are not controlled by the global conversion analysis
pragma above, but must be controlled by the relevant individual pragmas
given in sections <A HREF="#8">3.2.2</A> and <A HREF="#11">3.2.3</A>.<P>
<A NAME=S18>
<H3>3.2.1 <A NAME=5>Integer to integer conversions</H3>
All integer to integer conversions are allowed in C, however some
can result in a loss of accuracy and so may be usefully detected.
For example, conversions from int to long never result in a loss of
accuracy, but conversions from long to int may. The detection of these
shortening conversions is controlled by:<P>
<PRE>
#pragma TenDRA conversion analysis ( int-int ) <EM>status</EM>
</PRE>
Checks on explicit conversions and implicit conversions may be controlled
independently using:<P>
<PRE>
#pragma TenDRA conversion analysis ( int-int explicit ) <EM>status</EM>
</PRE>
and<P>
<PRE>
#pragma TenDRA conversion analysis ( int-int implicit ) <EM>status</EM>
</PRE>
Objects of enumerated type are specified by the ISO C standard to
be compatible with an implementation-defined integer type. However
assigning a value of a different integral type other then an appropriate
enumeration constant to an object of enumeration type is not really
in keeping with the spirit of enumerations. The check to detect the
implicit integer to enum type conversions which arise from such assignments
is controlled using:<P>
<PRE>
#pragma TenDRA conversion analysis ( int-enum implicit ) <EM>status</EM>
</PRE>
Note that only implicit conversions are flagged; if the conversion
is made explicit, by using a cast, no errors are raised. <P>
As usual <EM>status</EM> must be replaced by <CODE>on</CODE>, <CODE>warning
</CODE> or <CODE>off</CODE> in all the pragmas listed above.<P>
The interaction of the integer conversion checks with the integer
promotion and arithmetic rules is an extremely complex issue which
is further discussed in Chapter 4.<P>
<A NAME=S19>
<H3>3.2.2 <A NAME=8>Pointer to integer and integer to pointer conversions</H3>
Integer to pointer and pointer to integer conversions are generally
unportable and should always be specified by means of an explicit
cast. The exception is that the integer zero and null pointers are
deemed to be inter-convertible. As in the integer to integer conversion
case, explicit and implicit pointer to integer and integer to pointer
conversions may be controlled separately using:<P>
<PRE>
#pragma TenDRA conversion analysis ( int-pointer explicit ) <EM>status</EM>
</PRE>
and<P>
<PRE>
#pragma TenDRA conversion analysis ( int-pointer implicit ) <EM>status</EM>
</PRE>
or both checks may be controlled together by:<P>
<PRE>
#pragma TenDRA conversion analysis ( int-pointer ) <EM>status</EM>
</PRE>
where <EM>status</EM> may be <CODE>on</CODE>, <CODE>warning</CODE>
or <CODE>off</CODE> as before and <CODE>pointer-int</CODE> may be
substituted for <CODE>int-pointer</CODE>.<P>
<P>
<A NAME=S20>
<H3>3.2.3 <A NAME=11>Pointer to pointer conversions.
</H3>
According to the ISO C standard, section 6.3.4, the only legal pointer
to pointer conversions are explicit conversions between:<P>
<OL>
<LI>a pointer to an object or incomplete type and a pointer to a different
object or incomplete type. The resulting pointer may not be valid
if it is improperly aligned for the type pointed to;<P>
<LI>a pointer to a function of one type and a pointer to a function
of another type. If a converted pointer, used to call a function,
has a type that is incompatible with the type of the called function,
the behaviour is undefined.<P>
</OL>
Except for conversions to and from the generic pointer which are discussed
below, all other conversions, including implicit pointer to pointer
conversions, are extremely unportable. <P>
All pointer to pointer conversion may be flagged as errors using:<P>
<PRE>
#pragma TenDRA conversion analysis ( pointer-pointer ) <EM>status</EM>
</PRE>
Explicit and implicit pointer to pointer conversions may be controlled
separately using:<P>
<PRE>
#pragma TenDRA conversion analysis ( pointer-pointer explicit ) <EM>status</EM>
</PRE>
and<P>
<PRE>
#pragma TenDRA conversion analysis ( pointer-pointer implicit ) <EM>status</EM>
</PRE>
where, as before, <CODE>status</CODE> may be <CODE>on</CODE>, <CODE>warning
</CODE> or <CODE>off</CODE>.<P>
Conversion between a pointer to a function type and a pointer to a
non-function type is undefined by the ISO C standard and should generally
be avoided. The checker can however be configured to treat function
pointers as object pointers for conversion using:<P>
<PRE>
#pragma TenDRA function pointer as pointer <EM>permit</EM>
</PRE>
Unless explicitly stated to the contrary, throughout the rest of the
document where <EM>permit</EM> appears in a pragma statement it represents
one of <CODE>allow</CODE> (allow the construct and do not produce
errors), <CODE>warning</CODE> (allow the construct but produce warnings
when it is detected), or <CODE>disallow</CODE> (produce errors if
the construct is detected) Here there are three options for <EM>permit</EM>:
<CODE>allow
</CODE> (do not produce errors or warnings for function pointer <->
pointer conversions); <CODE>warning</CODE> (produce a warning when
function pointer <-> pointer conversions are detected); or <CODE>disallow
</CODE> (produce an error for function pointer <-> pointer conversions).
<P>
The generic pointer, void *, is a special case. All conversions of
pointers to object or incomplete types to or from a generic pointer
are allowed. Some older dialects of C used char * as a generic pointer.
This dialect feature may be allowed, allowed with a warning, or disallowed
using the pragma:<P>
<PRE>
#pragma TenDRA compatible type : char * == void * <EM>permit</EM>
</PRE>
where <EM>permit </EM>is <CODE>allow</CODE>, <CODE>warning</CODE>
or <CODE>disallow</CODE> as before.<P>
<A NAME=S21>
<H3>3.2.4 <A NAME=15>Example: 64-bit portability issues</H3>
64-bit machines form the "next frontier" of program portability.
Most of the problems involved in 64-bit portability are type conversion
problems. The assumptions that were safe on a 32-bit machine are not
necessarily true on a 64-bit machine - int may not be the same size
as long, pointers may not be the same size as int, and so on. This
example illustrates the way in which the checker's conversion analysis
tests can detect potential 64-bit portability problems.<P>
Consider the following code:<P>
<PRE>
#include <stdio.h>
void print ( string, offset, scale )
char *string;
unsigned int offset;
int scale;
{
string += ( scale * offset );
( void ) puts ( string );
return;
}
int main ()
{
char *s = "hello there";
print ( s + 4, 2U, -2 );
return ( 0 );
}
</PRE>
This appears to be fairly simple - the offset of 2U scaled by -2 cancels
out the offset in s + 4, so the program just prints "hello there".
Indeed, this is what happens on most machines. When ported to a particular
64-bit machine, however, it core dumps. The fairly subtle reason is
that the composite offset, scale * offset, is actually calculated
as an unsigned int by the ISO C arithmetic conversion rules. So the
answer is not -4. Strictly speaking it is undefined, but on virtually
all machines it will be UINT_MAX - 3. The fact that adding this offset
to string is equivalent to adding -4 is only true on machines on which
pointers have the same size as unsigned int. If a pointer contains
64 bits and an unsigned int contains 32 bits, the result is 232 bytes
out.<P>
So the error occurs because of the failure to spot that the offset
being added to string is unsigned. All mixed integer type arithmetic
involves some argument conversion. In the case above, scale is converted
to an unsigned int and that is multiplied by offset to give an unsigned
int result. If the implicit int->int conversion checks (<A HREF="#5">3.2.1
</A>) are enabled, this conversion is detected and the problem may
be avoided.<P>
<A NAME=S22>
<HR><H2>3.3 <A NAME=17>Function type checking</H2>
The importance of function type checking in C lies in the conversions
which can result from type mismatches between the arguments in a function
call and the parameter types assumed by its definition or between
the specified type of the function return and the values returned
within the function definition. Until the introduction of function
prototypes into ISO standard C, there was little scope for detecting
the correct typing of functions. Traditional C allows for absolutely
no type checking of function arguments, so that totally bizarre functions,
such as:<P>
<PRE>
int f ( n ) int n ; {
return ( f ( "hello", "there" ) ) ;
}
</PRE>
are allowed, although their effect is undefined. However, the move
to fully prototyped programs has been relatively slow. This is partially
due to an understandable reluctance to change existing, working programs,
but the desire to maintain compatibility with existing C compilers,
some of which still do not support prototypes, is also a powerful
factor. Prototypes are allowed in the checker's default mode but tchk
can be configured to allow, allow with a warning or disallow prototypes,
using:<P>
<PRE>
#pragma TenDRA prototype <EM>permit</EM>
</PRE>
where <EM>permit </EM>is <CODE>allow</CODE>, <CODE>disallow</CODE>
or <CODE>warning</CODE>. <P>
Even if prototypes are not supported the checker has a facility, described
below, for detecting incorrectly typed functions.<P>
<A NAME=S23>
<H3>3.3.1 <A NAME=20>Type checking non-prototyped functions</H3>
The checker offers a method for applying prototype-like checks to
traditionally defined functions, by introducing the concept of "
weak" prototypes. A weak prototype contains function parameter
type information, but has none of the automatic argument conversions
associated with a normal prototype. Instead weak prototypes imply
the usual argument promotion passing rules for non-prototyped functions.
The type information required for a weak prototype can be obtained
in three ways:<P>
<OL>
<LI>A weak prototype may be declared using the syntax:<P>
<PRE>
int f <EM>WEAK</EM> ( char, char * ) ;
</PRE>
where<EM> WEAK</EM> represents any keyword which has been introduced
using:<P>
<PRE>
#pragma TenDRA keyword <EM>WEAK</EM> for weak
</PRE>
An alternative definition of the keyword must be provided for other
compilers. For example, the following definition would make system
compilers interpret weak prototypes as normal (strong) prototypes:<P>
<PRE>
#ifdef __TenDRA__
#pragma TenDRA keyword WEAK for weak
#else
#define WEAK
#endif
</PRE>
The difference between conventional prototypes and weak prototypes
can be illustrated by considering the normal prototype for f:
<PRE>
int f (char,char *);
</PRE>
When the prototype is present, the first argument to f would be passed
as a char. Using the weak prototype, however, results in the first
argument being passed as the integral promotion of char, that is to
say, as an int.<P>
There is one limitation on the declaration of weak prototypes - declarations
of the form:
<PRE>
int f <EM>WEAK</EM>() ;
</PRE>
are not allowed. If a function has no arguments, this should be stated
explicitly as:<P>
<PRE>
int f <EM>WEAK</EM>( void ) ;
</PRE>
whereas if the argument list is not specified, weak prototypes should
be avoided and a traditional declaration used instead:<P>
<PRE>
extern int f ();
</PRE>
The checker may be configured to allow, allow with a warning or disallow
weak prototype declarations using:
<PRE>
#pragma TenDRA prototype ( weak ) <EM>permit</EM>
</PRE>
where <CODE>permit</CODE> is replaced by <CODE>allow</CODE>, <CODE>warning
</CODE> or <CODE>disallow</CODE> as appropriate. Weak prototypes are
not permitted in the default mode.<P>
<LI>Information can be deduced from a function definition. For example,
the function definition:<P>
<PRE>
int f(c,s) char c; char *s;{...}
</PRE>
is said to have weak prototype:<P>
<PRE>
int f <EM>WEAK</EM> (char,char *);
</PRE>
The checker automatically constructs a weak prototype for each traditional
function definition it encounters and if the weak prototype analysis
mode is enabled (see below) all subsequent calls of the function are
checked against this weak prototype. <P>
For example, in the bizarre function in <A HREF="#17">3.3</A>, the
weak prototype:<P>
<PRE>
int f <EM>WEAK</EM> ( int );
</PRE>
is constructed for f. The subsequent call to f:<P>
<PRE>
f ( "hello", "there" );
</PRE>
is then rejected by comparison with this weak prototype - not only
is f called with the wrong number of arguments, but the first argument
has a type incompatible with (the integral promotion of) int.<P>
<LI>Information may be deduced from the calls of a function. For example,
in:<P>
<PRE>
extern void f ();
void g ()
{
f ( 3 );
f ( "hello" );
}
</PRE>
we can infer from the first call of f that f takes one integral argument.
We cannot deduce the type of this argument, only that it is an integral
type whose promotion is int (since this is how the argument is passed).
We can therefore infer a partial weak prototype for f:<P>
<PRE>
void f <EM>WEAK</EM> ( t );
</PRE>
for some integral type t which promotes to int. Similarly, from the
second call of f we can infer the weak prototype:<P>
<PRE>
void f <EM>WEAK</EM> ( char * );
</PRE>
(the argument passing rules are much simpler in this case). Clearly
the two inferred prototypes are incompatible, so an error is raised.<P>
Note that prototype inferred from function calls alone cannot ensure
that the uses of the function within a source file are correct, merely
that they are consistent. The presence of an explicit function declaration
or definition is required for a definitive "right" prototype.<P>
Null pointers cause particular problems with weak prototypes inferred
from function calls. For example, in:<P>
<PRE>
#include <stdio.h>
extern void f ();
void g () {
f ( "hello" );
f( NULL );
}<P>
</PRE>
the argument in the first call of f is char* whereas in the second
it is int (because NULL is defined to be 0). Whereas NULL can be converted
to char*, it is not necessarily passed to procedures in the same way
(for example, it may be that pointers have 64 bits and ints have 32
bits). It is almost always necessary to cast NULL to the appropriate
pointer type in weak procedure calls.<P>
</OL>
Functions for which explicitly declared weak prototypes are provided
are always type-checked by the checker. Weak prototypes deduced from
function declarations or calls are used for type checking if the weak
prototype analysis mode is enabled using:<P>
<PRE>
#pragma TenDRA weak prototype analysis <EM>status</EM>
</PRE>
where <CODE>status</CODE> is one of <CODE>on</CODE>, <CODE>warning</CODE>
and <CODE>off</CODE> as usual. Weak prototype analysis is not performed
in the default mode.<P>
There is also an equivalent command line option of the form <CODE>-X:weak_proto=
</CODE><CODE>state</CODE>, where <CODE>state</CODE> can be <CODE>check</CODE>,
<CODE>warn</CODE> or <CODE>dont</CODE>. <P>
This section ends with two examples which demonstrate some of the
less obvious consequences of weak prototype analysis.<P>
<P>
<B>Example 1: An obscure type mismatch</B><P>
As stated above, the promotion and conversion rules for weak prototypes
are precisely those for traditionally declared and defined functions.
Consider the program:<P>
<PRE>
void f ( n )long n;{
printf ( "%ld\n", n );
}
void g (){
f ( 3 );
}
</PRE>
The literal constant 3 is an int and hence is passed as such to f.
f is however expecting a long, which can lead to problems on some
machines. Introducing a strong prototype declaration of f for those
compilers which understand them:<P>
<PRE>
#ifdef __STDC__
void f ( long );
#endif
</PRE>
will produce correct code - the arguments to a function declared with
a prototype are converted to the appropriate types, so that the literal
is actually passed as 3L. This solves the problem for compilers which
understand prototypes, but does not actually detect the underlying
error. Weak prototypes, because they use the traditional argument
passing rules, do detect the error. The constructed weak prototype:<P>
<PRE>
void f <EM>WEAK</EM> ( long );
</PRE>
conveys the type information that f is expecting a long, but accepts
the function arguments as passed rather than converting them. Hence,
the error of passing an int argument to a function expecting a long
is detected.<P>
Many programs, seeking to have prototype checks while preserving compilability
with non-prototype compilers, adopt a compromise approach of traditional
definitions plus prototype declarations for those compilers which
understand them, as in the example above. While this ensures correct
argument passing in the prototype case, as the example shows it may
obscure errors in the non-prototype case.<P>
<P>
<B>Example 2: Weak prototype checks in defined programs</B><P>
In most cases a program which fails to compile with the weak prototype
analysis enabled is undefined. ISO standard C does however contain
an anomalous rule on equivalence of representation. For example, in:<P>
<PRE>
extern void f ();
void g () {
f ( 3 );
f ( 4U );
}
</PRE>
the TenDRA checker detects an error - in one instance f is being passed
an int, whereas in the other it is being passed an unsigned int. However,
the ISO C standard states that, for values which fit into both types,
the representation of a number as an int is equal to that as an unsigned
int, and that values with the same representation are interchangeable
in procedure arguments. Thus the program is defined. The justification
for raising an error or warning for this program is that the prototype
analysis is based on types, not some weaker notion of "equivalence
of representation". The program may be defined, but it is not
type correct.<P>
Another case in which a program is defined, but not correct, is where
an unnecessary extra argument is passed to a function. For example,
in:<P>
<PRE>
void f ( a ) int a; {
printf ( "%d\n", a );
}
void g () {
f ( 3, 4 );
}
</PRE>
the call of f is defined, but is almost certainly a mistake.<P>
<A NAME=S24>
<H3>3.3.2 <A NAME=23>Checking printf strings
</H3>
Normally functions which take a variable number of arguments offer
only limited scope for type checking. For example, given the prototype:<P>
<PRE>
int execl ( const char *, const char *, ... );
</PRE>
the first two arguments may be checked, but we have no hold on any
subsequent arguments (in fact in this example they should all be const
char *, but C does not allow this information to be expressed). Two
classes of functions of this form, namely the printf and scanf families,
are so common that they warrant special treatment. If one of these
functions is called with a constant format string, then it is possible
to use this string to deduce the types of the extra arguments that
it is expect ing. For example, in:<P>
<PRE>
printf ( "%ld", 4 );
</PRE>
the format string indicates that printf is expecting a single additional
argument of type long. We can therefore deduce a <EM>quasi-prototype</EM>
which this particular call to printf should conform to, namely:<P>
<PRE>
int printf ( const char *,long );
</PRE>
In fact this is a mixture of a strong prototype and a weak prototype.
The first argument comes from the actual prototype of printf, and
hence is strong. All subsequent arguments correspond to the ellipsis
part of the printf prototype, and are passed by the normal promotion
rules. Hence the long component of the inferred prototype is weak
(see 3.3.1). This means that the error in the call to printf - the
integer literal is passed as an int when a long is expected - is detected.<P>
In order for this check to take place, the function declaration needs
to tell the checker that the function is like printf. This is done
by introducing a special type, <EM>PSTRING</EM> say, to stand for
a printf string, using:<P>
<PRE>
#pragma TenDRA type <EM>PSTRING</EM> for ... printf
</PRE>
For most purposes this is equivalent to:<P>
<PRE>
typedef const char *<EM>PSTRING</EM>;
</PRE>
except that when a function declaration:<P>
<PRE>
int f ( <EM>PSTRING</EM>, ... );
</PRE>
is encountered the checker knows to deduce the types of the arguments
corresponding to the ... from the PSTRING argument (the precise rules
it applies are those set out in the XPG4 definition of fprintf). If
this mechanism is used to apply printf style checks to user defined
functions, an alternative definition of PSTRING for conventional compilers
must be provided. For example:<P>
<PRE>
#ifdef __TenDRA__
#pragma TenDRA type PSTRING for ... printf
#else
typedef const char *PSTRING;
#endif
</PRE>
There are similar rules with scanf in place of printf.<P>
The TenDRA descriptions of the standard APIs use this mechanism to
describe those functions, namely printf, fprintf and sprintf, and
scanf, fscanf and sscanf which are of these forms. This means that
the checks are switched on for these functions by default. However,
these descriptions are under the control of a macro, __NO_PRINTF_CHECKS,
which, if defined before stdio.h is included, effectively switches
the checks off. This macro is defined in the start-up files for certain
checking modes, so that the checks are disabled in these modes (see
chapter 2). The checks can be enabled in these cases by #undef'ing
the macro before including stdio.h. There are equivalent command-line
options to tchk of the form <CODE>-X:printf=</CODE><CODE>state</CODE>,
where <CODE>state</CODE> can be <CODE>check</CODE> or <CODE>dont</CODE>,
which respectively undefine and define this macro.<P>
<A NAME=S25>
<H3>3.3.3 <A NAME=25>Function return checking
</H3>
Function returns normally present no difficulties. The return value
is converted, as if by assignment, to the function return type, so
that the problem is essentially one of type conversion (see 3.2).
There is however one anomalous case. A plain return statement, without
a return value, is allowed in functions returning a non-void type,
the value returned being undefined. For example, in:<P>
<PRE>
int f ( int c )
{
if ( c ) return ( 1 );
return;
}
</PRE>
the value returned when c is zero is undefined. The test for detecting
such void returns is controlled by:<P>
<PRE>
#pragma TenDRA incompatible void return <EM>permit</EM>
</PRE>
where <EM>permit</EM> may be <CODE>allow</CODE>, <CODE>warning</CODE>
or <CODE>disallow</CODE> as usual. <P>
There are also equivalent command line options to tchk of the form
<CODE>-X:void_ret=</CODE><CODE>state</CODE>, where <CODE>state</CODE>
can be <CODE>check</CODE>, <CODE>warn</CODE> or <CODE>dont</CODE>.
Incompatible void returns are allowed in the default mode and of course,
plain return statements in functions returning void are always legal.<P>
This check also detects functions which do not contain a return statement,
but fall out of the bottom of the function as in:<P>
<PRE>
int f ( int c )
{
if ( c ) return ( 1 );
}
</PRE>
Occasionally it may be the case that such a function is legal, because
the end of the function is not reached. Unreachable code is discussed
in section <A HREF="tdfc8.html#2">5.2</A>.<P>
<A NAME=S26>
<HR><H2>3.4 Overriding type checking</H2>
There are several commonly used features of C, some of which are even
allowed by the ISO C standard, which can circumvent or hinder the
type-checking of a program. The checker may be configured either to
enforce the absence of these features or to support them with or without
a warning, as described below.<P>
<A NAME=S27>
<H3>3.4.1 <A NAME=30>Implicit Function Declarations
</H3>
The ISO C standard states that any undeclared function is implicitly
assumed to return int. For example, in ISO C:<P>
<PRE>
int f ( int c ) {
return ( g( c )+1 );
}
</PRE>
the undeclared function g is inferred to have a declaration:<P>
<PRE>
extern int g ();
</PRE>
This can potentially lead to program errors. The definition of f would
be valid if g actually returned double, but incorrect code would be
produced. Again, an explicit declaration might give us more information
about the function argument types, allowing more checks to be applied.<P>
Therefore the best chance of detecting bugs in a program and ensuring
its portability comes from having each function declared before it
is used. This means detecting implicit declarations and replacing
them by explicit declarations. By default implicit function declarations
are allowed, however the pragma:<P>
<PRE>
#pragma TenDRA implicit function declaration <EM>status</EM>
</PRE>
may be used to determine how tchk handles implicit function declarations.
<CODE>Status</CODE> is replaced by <CODE>on</CODE> to allow implicit
declarations, <CODE>warning</CODE> to allow implicit declarations
but to produce a warning when they occur, or <CODE>off</CODE> to prevent
implicit declarations and raise an error where they would normally
be used.<P>
(There are also equivalent command-line options to tcc of the form
<CODE>-X:implicit_func=</CODE><CODE>state</CODE>, where <CODE>state</CODE>
can be <CODE>check</CODE>, <CODE>warn</CODE> or <CODE>dont</CODE>.)<P>
This test assumes an added significance in API checking. If a programmer
wishes to check that a certain program uses nothing outside the POSIX
API, then implicitly declared functions are a potential danger area.
A function from outside POSIX could be used without being detected
because it has been implicitly declared. Therefore, the detection
of implicitly declared functions is vital to rigorous API checking.<P>
<A NAME=S28>
<H3>3.4.2 <A NAME=32>Function Parameters</H3>
Many systems pass function arguments of differing types in the same
way and programs are sometimes written to take advantage of this feature.
The checker has a number of options to resolve type mismatches which
may arise in this way and would otherwise be flagged as errors:<P>
<OL>
<LI><B>Type-type compatibility</B><P>
When comparing function prototypes for compatibility, the function
parameter types must be compared. If the parameter types would otherwise
be incompatible, they are treated as compatible if they have previously
been introduced with a type-type param ter compatibility pragma i.e.<P>
<PRE>
#pragma TenDRA argument <EM>type-name</EM> as <EM>type-name</EM>
</PRE>
where <EM>type-name</EM> is the name of any type. This pragma is transitive
and the second type in the pragma is taken to be the final type of
the parameter. <P>
<LI><B>Type-ellipsis compatibility</B><P>
Two function prototypes with different numbers of arguments are compatible
if: <P>
<UL>
<LI>both prototypes have an ellipsis; <P>
<LI>each parameter type common to both prototypes is compatible;<P>
<LI>each extra parameter type in the prototype with more parameters,
is either specified in a type-ellipsis compatibility pragma or is
type-type compatible (see above) to a type that is specified in a
type-ellipsis compatibility.<P>
</UL>
Type-ellipsis compatibility is introduced using the pragma: <P>
<PRE>
#pragma TenDRA argument <EM>type-name</EM> as ...
</PRE>
where again <CODE>type-name</CODE> is the name of any type.<P>
<LI><B>Ellipsis compatibility</B><P>
If, when comparing two function prototypes for compatibility, one
has an ellipsis and the other does not, but otherwise the two types
would be compatible, then if an `extra' ellipsis is allowed, the types
are treated as compatible. The pragma controlling ellipsis compatibility
is:<P>
<PRE>
#pragma TenDRA extra ... <EM>permit</EM>
</PRE>
where <CODE>permit</CODE> may be <CODE>allow</CODE>, <CODE>disallow</CODE>
or <CODE>warning</CODE> as usual. <P>
</OL>
<P>
<P>
<A NAME=S29>
<H3>3.4.3 <A NAME=37>Incompatible promoted function arguments</H3>
Mixing the use of prototypes with old-fashioned function definitions
can result in incorrect code. For example, in the program below the
function argument promotion rules are applied to the definition of
f, making it incompatible with the earlier prototype (a is converted
to the integer promotion of char, i.e. int).<P>
<PRE>
int f(char);
int f(a)char a;{
...
}
</PRE>
An incompatible type error is raised in the default checking mode.
The check for incompatible types which arise from mixtures of prototyped
and non-prototyped function declarations and definitions is controlled
using: <P>
<CODE>#pragma TenDRA incompatible promoted function argument </CODE><EM>permit
</EM><P>
<CODE>Permit</CODE> may be replaced by <CODE>allow</CODE>, <CODE>warning</CODE>
or <CODE>disallow</CODE> as normal. The parameter type in the resulting
function type is the promoted parameter type.<P>
<A NAME=S30>
<H3>3.4.4 <A NAME=39>Incompatible type qualifiers</H3>
The declarations <P>
<PRE>
const int a;
int a;
</PRE>
are not compatible according to the ISO C standard because the qualifier,
const, is present in one declaration but not in the other. Similar
rules hold for volatile qualified types. By default, tchk produces
an error when declarations of the same object contain different type
qualifiers. The check is controlled using: <P>
<PRE>
#pragma TenDRA incompatible type qualifier <EM>permit</EM>
</PRE>
where the options for <EM>permit</EM> are <CODE>allow</CODE>, <CODE>disallow
</CODE> or <CODE>warning</CODE>.<P>
<P>
<!-- FM pgf ignored -->
<HR>
<P><I>Part of the <A HREF="../index.html">TenDRA Web</A>.<BR>Crown
Copyright © 1998.</I></P>
</BODY>
</HTML>