-

Difference between revisions of "Specific Features"

From HI-TECH C for CP/M Fan WIKI(EN)
Jump to: navigation, search
(Created page with "The HI-TECH C compiler has a number of features, which while largely compatible with other C compilers, contribute to more reliable programming methods. === ANSI C Standard...")
 
 
Line 1: Line 1:
 
The HI-TECH C compiler has a number of features,  which while  largely compatible with other C compilers, contribute to more reliable programming methods.
 
The HI-TECH C compiler has a number of features,  which while  largely compatible with other C compilers, contribute to more reliable programming methods.
  
=== ANSI C Standard Compatibility ===
+
== ANSI C Standard Compatibility ==
  
 
At the time of writing the Draft ANSI Standard for  the C Language was at an advanced stage, though not yet an official standard. Accordingly it is not possible to claim  compliance  with  that standard, however HI-TECH C includes the
 
At the time of writing the Draft ANSI Standard for  the C Language was at an advanced stage, though not yet an official standard. Accordingly it is not possible to claim  compliance  with  that standard, however HI-TECH C includes the
 
majority of the new and altered features in the  draft  ANSI standard.  Thus  it  is in the sense that most people understand it "ANSI compatible".
 
majority of the new and altered features in the  draft  ANSI standard.  Thus  it  is in the sense that most people understand it "ANSI compatible".
  
=== Type Checking ===
+
== Type Checking ==
  
 
Previous C compilers have adopted  a  lax  approach  to type  checking.  This  is  typified  by the Unix C compiler, which allows almost arbritary mixing  of  types  in  expressions. The HI-TECH C compiler performs much more strict type
 
Previous C compilers have adopted  a  lax  approach  to type  checking.  This  is  typified  by the Unix C compiler, which allows almost arbritary mixing  of  types  in  expressions. The HI-TECH C compiler performs much more strict type
 
checking, although in most cases only warning  messages  are issued,  allowing  compilation  to proceed if the user knows that the errors are harmless. This would occur, for example, when  an  integer  value was assigned to a pointer variable. The generated code would almost certainly be what  the  user intended,  however if in fact it represented an error in the source code, the user is prompted to check  and  correct  it where necessary.
 
checking, although in most cases only warning  messages  are issued,  allowing  compilation  to proceed if the user knows that the errors are harmless. This would occur, for example, when  an  integer  value was assigned to a pointer variable. The generated code would almost certainly be what  the  user intended,  however if in fact it represented an error in the source code, the user is prompted to check  and  correct  it where necessary.
  
=== Member Names ===
+
== Member Names ==
  
 
In early C compilers member names in  different  structures were required to be distinct except under certain circumstances. HI-TECH C, like most recent  implementations  of C, allows member names in different structures and unions to
 
In early C compilers member names in  different  structures were required to be distinct except under certain circumstances. HI-TECH C, like most recent  implementations  of C, allows member names in different structures and unions to
Line 20: Line 20:
 
device, consisting of several  registers.  The  declarations and use may look something like the code fragment in fig. 2.
 
device, consisting of several  registers.  The  declarations and use may look something like the code fragment in fig. 2.
  
struct io_dev
+
struct io_dev
{
+
{
short  io_status;    /* status */
+
short  io_status;    /* status */
char    io_rxdata;    /* rx data */
+
char    io_rxdata;    /* rx data */
char    io_txdata;    /* tx data */
+
char    io_txdata;    /* tx data */
};
+
};
 +
 +
#define RXRDY  01          /* rx ready */
 +
#define TXRDY  02          /* tx ready */
 +
 +
/* define the (absolute) device address */
 +
 +
#define DEVICE  ((struct io_dev *)0xFF00)
 +
 +
send_byte(c)
 +
char c;
 +
{
 +
/* wait till transmitter ready */
 +
while(!(DEVICE->io_status & TXRDY))
 +
continue;
 +
/* send the data byte */
 +
DEVICE->io_txdata = c;
 +
}
  
#define RXRDY  01          /* rx ready */
+
;Fig. 2. Use of Typecast on an Absolute Address
#define TXRDY  02          /* tx ready */
 
 
 
/* define the (absolute) device address */
 
 
 
#define DEVICE  ((struct io_dev *)0xFF00)
 
 
 
send_byte(c)
 
char c;
 
{
 
/* wait till transmitter ready */
 
while(!(DEVICE->io_status & TXRDY))
 
continue;
 
/* send the data byte */
 
DEVICE->io_txdata = c;
 
}
 
 
 
Fig. 2. Use of Typecast on an Absolute Address
 
  
  
Line 52: Line 52:
 
Some examples of right and wrong usage of member  names are shown in fig. 3.
 
Some examples of right and wrong usage of member  names are shown in fig. 3.
  
=== Unsigned Types ===
+
== Unsigned Types ==
  
 
HI-TECH C implements unsigned versions of all  integral types;  i.e.  unsigned  char,  short,  int and long.  If an unsigned quantity is shifted right, the shift will  be  performed  as  a  logical  shift,  i.e. bringing zeros into the
 
HI-TECH C implements unsigned versions of all  integral types;  i.e.  unsigned  char,  short,  int and long.  If an unsigned quantity is shifted right, the shift will  be  performed  as  a  logical  shift,  i.e. bringing zeros into the
 
rightmost bits. Similarly right shifts of a signed  quantity will sign extend the rightmost bits.
 
rightmost bits. Similarly right shifts of a signed  quantity will sign extend the rightmost bits.
  
=== Arithmetic Operations ===
+
== Arithmetic Operations ==
  
 
On machines where arithmetic  operations  may  be  performed  more  efficiently  in  lengths  shorter  than  int, operands shorter than int will not be extended to int length unless necessary.
 
On machines where arithmetic  operations  may  be  performed  more  efficiently  in  lengths  shorter  than  int, operands shorter than int will not be extended to int length unless necessary.
Line 63: Line 63:
 
For example, if two characters are added and the result stored  into  another  character,  it  is  only necessary to
 
For example, if two characters are added and the result stored  into  another  character,  it  is  only necessary to
  
 +
struct fred
 +
{
 +
char      a;
 +
int      b;
 +
}    s1, * s2;
 +
 +
struct bill
 +
{
 +
float  c;
 +
long    b;
 +
}      x1, * x2;
 +
 +
main()
 +
{
 +
/* wrong - c is not a member of fred */
 +
s1.c = 2;
 +
 +
/* correct */
 +
s1.a = 2;
 +
 +
/* wrong - s2 is a pointer */
 +
s2.a = 2;
 +
 +
/* correct */
 +
x2->b = 24L;
 +
 +
/* right, but note type conversion
 +
from long to int */
 +
s2->b = x2->b;
 +
}
  
struct fred
+
;Fig. 3. Examples of Member Usage
{
 
char      a;
 
int      b;
 
}    s1, * s2;
 
 
 
struct bill
 
{
 
float  c;
 
long    b;
 
}      x1, * x2;
 
 
 
main()
 
{
 
/* wrong - c is not a member of fred */
 
s1.c = 2;
 
 
 
/* correct */
 
s1.a = 2;
 
 
 
/* wrong - s2 is a pointer */
 
s2.a = 2;
 
 
 
/* correct */
 
x2->b = 24L;
 
 
 
/* right, but note type conversion
 
from long to int */
 
s2->b = x2->b;
 
}
 
 
 
Fig. 3. Examples of Member Usage
 
  
 
perform arithmetic in 8 bits, since any  overflow  into  the top  8 bits will be lost. However, if the sum of two characters is stored into an int, the addition should be  done  in 16 bits to ensure the correct result.
 
perform arithmetic in 8 bits, since any  overflow  into  the top  8 bits will be lost. However, if the sum of two characters is stored into an int, the addition should be  done  in 16 bits to ensure the correct result.
Line 101: Line 100:
 
In accordance with the draft ANSI standard,  operations on  float rather than double quantities will be performed in the shorter precision rather than being converted to  double precision then back again.
 
In accordance with the draft ANSI standard,  operations on  float rather than double quantities will be performed in the shorter precision rather than being converted to  double precision then back again.
  
=== Structure Operations ===
+
== Structure Operations ==
  
 
HI-TECH C implements structure  assignments,  structure arguments  and structure-valued functions in their full generality. The example in fig. 4 is  a  function  returning  a structure. Some legal (and illegal) uses of the function are
 
HI-TECH C implements structure  assignments,  structure arguments  and structure-valued functions in their full generality. The example in fig. 4 is  a  function  returning  a structure. Some legal (and illegal) uses of the function are
 
also shown.
 
also shown.
  
 +
struct bill
 +
{
 +
char    a;
 +
int    b;
 +
}
 +
afunc()
 +
{
 +
struct bill    x;
 +
 +
return x;
 +
}
 +
 +
main()
 +
{
 +
struct bill    a;
 +
 +
a = afunc();            /* ok */
 +
pf("%d", afunc().a);    /* ok */
 +
 +
/* illegal, afunc() cannot be assigned
 +
to, therefore neither can
 +
afunc().a */
 +
afunc().a = 1;
 +
 +
/* illegal, same reason */
 +
afunc().a++;
 +
}
  
struct bill
+
;Fig. 4. Example of a Function Returning a Structure
{
 
char    a;
 
int    b;
 
}
 
afunc()
 
{
 
struct bill    x;
 
  
return x;
+
== Enumerated Types ==
}
 
 
 
main()
 
{
 
struct bill    a;
 
 
 
a = afunc();            /* ok */
 
pf("%d", afunc().a);    /* ok */
 
 
 
/* illegal, afunc() cannot be assigned
 
to, therefore neither can
 
afunc().a */
 
afunc().a = 1;
 
 
 
/* illegal, same reason */
 
afunc().a++;
 
}
 
 
 
Fig. 4. Example of a Function Returning a Structure
 
 
 
=== Enumerated Types ===
 
  
 
HI-TECH C supports enumerated types;  these  provide  a structured way of defining named constants.
 
HI-TECH C supports enumerated types;  these  provide  a structured way of defining named constants.
Line 144: Line 142:
 
index or as the operand of a switch  statement.  Arithmetic may  be  performed  on enumerated types, and enumerated type expressions may be compared, both for equality and with  the relation  operators.  An example of the use of an enumerated type is given in fig. 5.
 
index or as the operand of a switch  statement.  Arithmetic may  be  performed  on enumerated types, and enumerated type expressions may be compared, both for equality and with  the relation  operators.  An example of the use of an enumerated type is given in fig. 5.
  
=== Initialization Syntax ===
+
== Initialization Syntax ==
  
 
Kernighan and Ritchie in "The C  Programming  Language" state  that  pairs of braces may be omitted from an initializer in certain contexts; the draft ANSI  standard  provides that  a  conforming C program must either include all braces
 
Kernighan and Ritchie in "The C  Programming  Language" state  that  pairs of braces may be omitted from an initializer in certain contexts; the draft ANSI  standard  provides that  a  conforming C program must either include all braces
 
in an initializer, or leave them all out. HI-TECH  C  allows any  pairs  of braces to be omitted providing that the front end of the compiler can determine the  size  of  any  arrays being  initialized, and providing that there is no ambiguity as to which braces have been omitted. To avoid ambiguity  if any  pairs of braces are present then any braces which would
 
in an initializer, or leave them all out. HI-TECH  C  allows any  pairs  of braces to be omitted providing that the front end of the compiler can determine the  size  of  any  arrays being  initialized, and providing that there is no ambiguity as to which braces have been omitted. To avoid ambiguity  if any  pairs of braces are present then any braces which would
  
 +
/* a represents 0, b -> 1 */
 +
enum fred { a, b, c = 4 };
 +
 +
main()
 +
{
 +
enum fred      x, y, z;
 +
 +
x = z;
 +
if(x < z)
 +
func();
 +
x = (enum fred)3;
 +
switch(z) {
 +
case a:
 +
case b:
 +
default:
 +
}
 +
}
  
/* a represents 0, b -> 1 */
+
;Fig. 5. Use of an Enumerated Type
enum fred { a, b, c = 4 };
 
 
 
main()
 
{
 
enum fred      x, y, z;
 
 
 
x = z;
 
if(x < z)
 
func();
 
x = (enum fred)3;
 
switch(z) {
 
case a:
 
case b:
 
default:
 
}
 
}
 
 
 
Fig. 5. Use of an Enumerated Type
 
  
 
enclose those braces must also be present. The compiler will complain  ("initialization  syntax")  if  any  ambiguity  is present.
 
enclose those braces must also be present. The compiler will complain  ("initialization  syntax")  if  any  ambiguity  is present.
  
=== Function Prototypes ===
+
== Function Prototypes ==
  
 
A new feature of C included in the proposed ANSI for C, known  as "function prototypes", provides C with an argument checking facility, i.e. it allows the compiler to  check  at compile  time  that  actual arguments supplied to a function
 
A new feature of C included in the proposed ANSI for C, known  as "function prototypes", provides C with an argument checking facility, i.e. it allows the compiler to  check  at compile  time  that  actual arguments supplied to a function
 
invocation  are  consistent  with  the  formal  parameters expected  by the function. The feature allows the programmer to include in a function  declaration  (either  an  external declaration or an actual definition) the types of the parameters to that function.  For  example,  the  code  fragment shown in fig. 6 shows two function prototypes.
 
invocation  are  consistent  with  the  formal  parameters expected  by the function. The feature allows the programmer to include in a function  declaration  (either  an  external declaration or an actual definition) the types of the parameters to that function.  For  example,  the  code  fragment shown in fig. 6 shows two function prototypes.
  
void fred(int, long, char *);
+
void fred(int, long, char *);
 
+
char *
+
char *
bill(int a, short b, ...)
+
bill(int a, short b, ...)
{
+
{
return a;
+
return a;
}
+
}
  
Fig. 6. Function Prototypes
+
;Fig. 6. Function Prototypes
  
  
The first prototype is an external declaration  of  the function  _�f_�r_�e_�d(),  which  accepts  one integer argument, one long argument, and one argument which is a pointer to  char. Any  usage  of  _�f_�r_�e_�d() while the prototype declaration is in scope will cause the actual parameters  to  be  checked  for number  and  type  against  the  prototype, e.g. if only two arguments were supplied or an integral  value  was  supplied for the third argument the compiler would report an error.
+
The first prototype is an external declaration  of  the function  <strong>fred()</strong>,  which  accepts  one integer argument, one long argument, and one argument which is a pointer to  char. Any  usage  of  <strong>fred()</strong> while the prototype declaration is in scope will cause the actual parameters  to  be  checked  for number  and  type  against  the  prototype, e.g. if only two arguments were supplied or an integral  value  was  supplied for the third argument the compiler would report an error.
  
  
In the second example, the function _�b_�i_�l_�l() expects  two or more arguments. The first and second will be converted to int and short respectively, while the remainder (if present) may  be  of any type. The ellipsis symbol (...) indicates to the compiler that zero or more arguments  of  any  type  may follow the other arguments. The ellipsis symbol must be last in the argument list, and may not appear as the  only  argument in a prototype.
+
In the second example, the function <strong>bill()</strong> expects  two or more arguments. The first and second will be converted to int and short respectively, while the remainder (if present) may  be  of any type. The ellipsis symbol (...) indicates to the compiler that zero or more arguments  of  any  type  may follow the other arguments. The ellipsis symbol must be last in the argument list, and may not appear as the  only  argument in a prototype.
  
 
All prototypes for a function must agree exactly,  however  it  is legal for a definition of a function in the old style,  i.e.  with  just  the  parameter  names  inside  the parentheses,  to follow a prototype declaration provided the number and type of the arguments agree. In this case  it  is essential  that  the  function definition is in scope of the prototype declaration.
 
All prototypes for a function must agree exactly,  however  it  is legal for a definition of a function in the old style,  i.e.  with  just  the  parameter  names  inside  the parentheses,  to follow a prototype declaration provided the number and type of the arguments agree. In this case  it  is essential  that  the  function definition is in scope of the prototype declaration.
  
Access to unspecified arguments  (i.e.  arguments  supplied where an ellipsis appeared in a prototype) must be via the macros defined  in  the  header  file  <stdarg.h>.  This defines the macros _�v_�a__�s_�t_�a_�r_�t, _�v_�a__�a_�r_�g and _�v_�a__�e_�n_�d. See _�v_�a__�s_�t_�a_�r_�t in the library function listing for more information.
+
Access to unspecified arguments  (i.e.  arguments  supplied where an ellipsis appeared in a prototype) must be via the macros defined  in  the  header  file  <stdarg.h>.  This defines the macros <strong>va_start, va_arg</strong> and <strong>va_end.</strong> See <strong>va_start</strong> in the library function listing for more information.
  
 
NOTE that is is a grave error to use a  function  which has  an  associated  prototype  unless  that prototype is in scope, i.e. the prototype MUST be declared  (possibly  in  a header  file)  before  the  function is invoked.  Failure to comply with this rule may result in strange behaviour of the program.  HI-TECH  C  will  issue a warning message ("func() declared  implicit  int")  whenever  a  function  is  called without  an  explicit  declaration.  It is good practice to declare all functions and global variables in  one  or  more header  files  which are included wherever the functions are defined or referenced.
 
NOTE that is is a grave error to use a  function  which has  an  associated  prototype  unless  that prototype is in scope, i.e. the prototype MUST be declared  (possibly  in  a header  file)  before  the  function is invoked.  Failure to comply with this rule may result in strange behaviour of the program.  HI-TECH  C  will  issue a warning message ("func() declared  implicit  int")  whenever  a  function  is  called without  an  explicit  declaration.  It is good practice to declare all functions and global variables in  one  or  more header  files  which are included wherever the functions are defined or referenced.
  
=== Void and Pointer to Void ===
+
== Void and Pointer to Void ==
  
The _�v_�o_�i_�d type may be used to indicate to  the  compiler that  a  function  does not return a value. Any usage of the return value from a void function  will  be  flagged  as  an error.
+
The <strong>void</strong> type may be used to indicate to  the  compiler that  a  function  does not return a value. Any usage of the return value from a void function  will  be  flagged  as  an error.
  
The type _�v_�o_�i_�d *, i.e. pointer to void, may be used as a "universal"  pointer type. This is intended to assist in the writing of general purpose storage allocators and the  like, where a pointer is returned which may be assigned to another variable of some other pointer type.  The  compiler  permits without  typecasting  and  without  reporting  an  error the conversion of _�v_�o_�i_�d * to any  other  pointer  type  and  vice versa.  The programmer is advised to use this facility carefully and ensure that any  _�v_�o_�i_�d *  value  is  usable  as  a pointer  to  any  other type, e.g. the alignment of any such pointer should be suitable for storage of any object.
+
The type <strong>void</strong> *, i.e. pointer to void, may be used as a "universal"  pointer type. This is intended to assist in the writing of general purpose storage allocators and the  like, where a pointer is returned which may be assigned to another variable of some other pointer type.  The  compiler  permits without  typecasting  and  without  reporting  an  error the conversion of <strong>void</strong> * to any  other  pointer  type  and  vice versa.  The programmer is advised to use this facility carefully and ensure that any  <strong>void</strong> *  value  is  usable  as  a pointer  to  any  other type, e.g. the alignment of any such pointer should be suitable for storage of any object.
  
=== Type qualifiers ===
+
== Type qualifiers ==
  
The ANSI C standard  introduced  the  concept  of  _�t_�y_�p_�e_�q_�u_�a_�l_�i_�f_�i_�e_�r_�s to C; these are keywords that qualify the type to which they are applied.  The type qualifiers defined by ANSI C are const and volatile.  HI-TECH C also implements several other type qualifiers.  The extra qualifiers include:
+
The ANSI C standard  introduced  the  concept  of  <strong>typequalifiers</strong> to C; these are keywords that qualify the type to which they are applied.  The type qualifiers defined by ANSI C are const and volatile.  HI-TECH C also implements several other type qualifiers.  The extra qualifiers include:
  
far
+
:far
near
+
:near
interrupt
+
:interrupt
fast interrupt
+
:fast interrupt
port
+
:port
  
 
Not all versions of the compilers implement all of the extra qualifiers.  See  the  machine dependent section for further information.
 
Not all versions of the compilers implement all of the extra qualifiers.  See  the  machine dependent section for further information.
Line 219: Line 216:
 
When constructing declarations using  type  qualifiers, it  is very easy to be confused as to the exact semantics of the declaration. A couple of rules-of-thumb will  make  this easier.  Firstly, where a type qualifier appears at the left of a declaration  it  may  appear  with  any  storage  class specifier and the basic type in any order, e.g.
 
When constructing declarations using  type  qualifiers, it  is very easy to be confused as to the exact semantics of the declaration. A couple of rules-of-thumb will  make  this easier.  Firstly, where a type qualifier appears at the left of a declaration  it  may  appear  with  any  storage  class specifier and the basic type in any order, e.g.
  
static void interrupt  func();
+
static void interrupt  func();
  
 
is semantically the same as
 
is semantically the same as
  
interrupt static void  func();
+
interrupt static void  func();
  
  
Where a qualifier appears in this context,  it  applies to  the  basic  type  of  the declaration. Where a qualifier appears to the right of  one  or  more  '*'  (_�s_�t_�a_�r)  pointer modifiers,  then  you should read the declaration from right to left, e.g.
+
Where a qualifier appears in this context,  it  applies to  the  basic  type  of  the declaration. Where a qualifier appears to the right of  one  or  more  '*'  (<strong>star</strong>)  pointer modifiers,  then  you should read the declaration from right to left, e.g.
  
char * far fred;
+
char * far fred;
  
should be read as "fred is a  far  pointer  to  char".  This means  that  fred is qualified by _�f_�a_�r, not the char to which it points. On the other hand,
+
should be read as "fred is a  far  pointer  to  char".  This means  that  fred is qualified by <strong>far</strong>, not the char to which it points. On the other hand,
  
 
char far * bill;
 
char far * bill;
Line 237: Line 234:
  
  
=== 5.12.===
+
== 5.12.==
  
 
There are two methods provided  for  in-line  assembler code  in  C  programs.  The  first  allows several lines of assembler anywhere in a program. This is via  the  #asm  and #endasm  preprocessor  directives.  Any lines between these
 
There are two methods provided  for  in-line  assembler code  in  C  programs.  The  first  allows several lines of assembler anywhere in a program. This is via  the  #asm  and #endasm  preprocessor  directives.  Any lines between these
two directives will be copied straight through to the assembler  file  produced  by the compiler. Alternatively you can use the asm("_�s_�t_�r_�i_�n_�g"); construct anywhere a C  statement  is expected. The _�s_�t_�r_�i_�n_�g will be copied through to the assembler file. Care should be  taken  with  using  in-line  assembler since it may interact with compiler generated code.
+
two directives will be copied straight through to the assembler  file  produced  by the compiler. Alternatively you can use the asm("<strong>string</strong>"); construct anywhere a C  statement  is expected. The <strong>string</strong> will be copied through to the assembler file. Care should be  taken  with  using  in-line  assembler since it may interact with compiler generated code.
  
=== Pragma Directives ===
+
== Pragma Directives ==
  
The draft  ANSI  C  standard  provides  for  a  #pragma preprocessor  directive that allows compiler implementations to control  various  aspects  of  the  compilation  process. Currently  HI-TECH  C  only  supports  one  pragma, the _�p_�a_�c_�k directive. This allows control  over  the  manner  in  which members  are  allocated inside a structure. By default, some of the compilers (especially the 8086 and  68000  compilers) will  align structure members onto even boundaries to optimize machine accesses. It is sometimes  desired  to  override this  to achieve a particular layout inside a structure. The _�p_�a_�c_�k pragma allows specification of a maximum  packing  factor.  For  example,  #pragma pack1(1) will instruct the compiler that no additional padding be inserted between  struc-
+
The draft  ANSI  C  standard  provides  for  a  #pragma preprocessor  directive that allows compiler implementations to control  various  aspects  of  the  compilation  process. Currently  HI-TECH  C  only  supports  one  pragma, the <strong>pack</strong> directive. This allows control  over  the  manner  in  which members  are  allocated inside a structure. By default, some of the compilers (especially the 8086 and  68000  compilers) will  align structure members onto even boundaries to optimize machine accesses. It is sometimes  desired  to  override this  to achieve a particular layout inside a structure. The <strong>pack</strong> pragma allows specification of a maximum  packing  factor.  For  example,  #pragma pack1(1) will instruct the compiler that no additional padding be inserted between  struc
ture  members,  i.e.  that  all members should be aligned on boundaries divisible by 1. Similarly  #pragma  pack(2)  will allow  alignment  on  boundaries  divisible by 2. In no case will use of the _�p_�a_�c_�k pragma force a greater  alignment  than would have been used for that data type anyway.
+
ture  members,  i.e.  that  all members should be aligned on boundaries divisible by 1. Similarly  #pragma  pack(2)  will allow  alignment  on  boundaries  divisible by 2. In no case will use of the <strong>pack</strong> pragma force a greater  alignment  than would have been used for that data type anyway.
  
More that one _�p_�a_�c_�k pragma may be used in a program. Any use  will  remain  in force until changed by another _�p_�a_�c_�k or until the end of the file. Do not use a _�p_�a_�c_�k pragma  before include files such as <_�s_�t_�d_�i_�o._�h> as this will cause incorrect declarations of run-time library data structures.
+
More that one <strong>pack</strong> pragma may be used in a program. Any use  will  remain  in force until changed by another <strong>pack</strong> or until the end of the file. Do not use a <strong>pack</strong> pragma  before include files such as <<strong>stdio.h</strong>> as this will cause incorrect declarations of run-time library data structures.

Latest revision as of 18:14, 30 July 2017

The HI-TECH C compiler has a number of features, which while largely compatible with other C compilers, contribute to more reliable programming methods.

ANSI C Standard Compatibility

At the time of writing the Draft ANSI Standard for the C Language was at an advanced stage, though not yet an official standard. Accordingly it is not possible to claim compliance with that standard, however HI-TECH C includes the majority of the new and altered features in the draft ANSI standard. Thus it is in the sense that most people understand it "ANSI compatible".

Type Checking

Previous C compilers have adopted a lax approach to type checking. This is typified by the Unix C compiler, which allows almost arbritary mixing of types in expressions. The HI-TECH C compiler performs much more strict type checking, although in most cases only warning messages are issued, allowing compilation to proceed if the user knows that the errors are harmless. This would occur, for example, when an integer value was assigned to a pointer variable. The generated code would almost certainly be what the user intended, however if in fact it represented an error in the source code, the user is prompted to check and correct it where necessary.

Member Names

In early C compilers member names in different structures were required to be distinct except under certain circumstances. HI-TECH C, like most recent implementations of C, allows member names in different structures and unions to overlap. A member name is recognized only in the context of an expression whose type is that of the structure in which the member is defined. In practice this means that a member name will be recognized only to the right of a '.' or '->'operator, where the expression to the left of the operator is of type structure or pointer to structure the same as that in which the member name was declared. This not only allows structure names to be re-used without conflict in more than one structure, it permits strict checking of the usage of members; a common error with other C compilers is the use of a member name with a structure pointer of the wrong type, or worse with a variable which is a pointer to a simple type.

There is however an escape from this, where the user desires to use as a structure pointer something which is not declared as such. This is via the use of a typecast. For example, suppose it is desired to access a memory-mapped i/o device, consisting of several registers. The declarations and use may look something like the code fragment in fig. 2.

struct io_dev
{
short   io_status;    /* status */
char    io_rxdata;    /* rx data */
char    io_txdata;    /* tx data */
};

#define RXRDY   01           /* rx ready */
#define TXRDY   02           /* tx ready */

/* define the (absolute) device address */

#define DEVICE  ((struct io_dev *)0xFF00)

send_byte(c)
char c;
{
/* wait till transmitter ready */
while(!(DEVICE->io_status & TXRDY))
continue;
/* send the data byte */
DEVICE->io_txdata = c;
}
Fig. 2. Use of Typecast on an Absolute Address


In this example, the device in question has a 16 bit status port, and two 8 bit data ports. The address of the device (i.e. the address of its status port) is given as (hex)0FF00. This address is typecast to the required struc- ture pointer type to enable use of the structure member names. The code generated by this will use absolute memory references to access the device, as required.

Some examples of right and wrong usage of member names are shown in fig. 3.

Unsigned Types

HI-TECH C implements unsigned versions of all integral types; i.e. unsigned char, short, int and long. If an unsigned quantity is shifted right, the shift will be performed as a logical shift, i.e. bringing zeros into the rightmost bits. Similarly right shifts of a signed quantity will sign extend the rightmost bits.

Arithmetic Operations

On machines where arithmetic operations may be performed more efficiently in lengths shorter than int, operands shorter than int will not be extended to int length unless necessary.

For example, if two characters are added and the result stored into another character, it is only necessary to

struct fred
{
char      a;
int       b;
}     s1, * s2;

struct bill
{
float   c;
long    b;
}       x1, * x2;

main()
{
/* wrong - c is not a member of fred */
s1.c = 2;

/* correct */
s1.a = 2;

/* wrong - s2 is a pointer */
s2.a = 2;

/* correct */
x2->b = 24L;

/* right, but note type conversion
from long to int */
s2->b = x2->b;
}
Fig. 3. Examples of Member Usage

perform arithmetic in 8 bits, since any overflow into the top 8 bits will be lost. However, if the sum of two characters is stored into an int, the addition should be done in 16 bits to ensure the correct result.

In accordance with the draft ANSI standard, operations on float rather than double quantities will be performed in the shorter precision rather than being converted to double precision then back again.

Structure Operations

HI-TECH C implements structure assignments, structure arguments and structure-valued functions in their full generality. The example in fig. 4 is a function returning a structure. Some legal (and illegal) uses of the function are also shown.

struct bill
{
char    a;
int     b;
}
afunc()
{
struct bill     x;

return x;
}

main()
{
struct bill     a;

a = afunc();            /* ok */
pf("%d", afunc().a);    /* ok */

/* illegal, afunc() cannot be assigned
to, therefore neither can
afunc().a */
afunc().a = 1;

/* illegal, same reason */
afunc().a++;
}
Fig. 4. Example of a Function Returning a Structure

Enumerated Types

HI-TECH C supports enumerated types; these provide a structured way of defining named constants.

The uses of enumerated types are more restricted than that allowed by the Unix C compiler, yet more flexible than permitted by LINT. In particular, an expression of enumerated type may be used to dimension arrays, as an array index or as the operand of a switch statement. Arithmetic may be performed on enumerated types, and enumerated type expressions may be compared, both for equality and with the relation operators. An example of the use of an enumerated type is given in fig. 5.

Initialization Syntax

Kernighan and Ritchie in "The C Programming Language" state that pairs of braces may be omitted from an initializer in certain contexts; the draft ANSI standard provides that a conforming C program must either include all braces in an initializer, or leave them all out. HI-TECH C allows any pairs of braces to be omitted providing that the front end of the compiler can determine the size of any arrays being initialized, and providing that there is no ambiguity as to which braces have been omitted. To avoid ambiguity if any pairs of braces are present then any braces which would

/* a represents 0, b -> 1 */
enum fred { a, b, c = 4 };

main()
{
enum fred       x, y, z;

x = z;
if(x < z)
func();
x = (enum fred)3;
switch(z) {
case a:
case b:
default:
}
}
Fig. 5. Use of an Enumerated Type

enclose those braces must also be present. The compiler will complain ("initialization syntax") if any ambiguity is present.

Function Prototypes

A new feature of C included in the proposed ANSI for C, known as "function prototypes", provides C with an argument checking facility, i.e. it allows the compiler to check at compile time that actual arguments supplied to a function invocation are consistent with the formal parameters expected by the function. The feature allows the programmer to include in a function declaration (either an external declaration or an actual definition) the types of the parameters to that function. For example, the code fragment shown in fig. 6 shows two function prototypes.

void fred(int, long, char *);

char *
bill(int a, short b, ...)
{
return a;
}
Fig. 6. Function Prototypes


The first prototype is an external declaration of the function fred(), which accepts one integer argument, one long argument, and one argument which is a pointer to char. Any usage of fred() while the prototype declaration is in scope will cause the actual parameters to be checked for number and type against the prototype, e.g. if only two arguments were supplied or an integral value was supplied for the third argument the compiler would report an error.


In the second example, the function bill() expects two or more arguments. The first and second will be converted to int and short respectively, while the remainder (if present) may be of any type. The ellipsis symbol (...) indicates to the compiler that zero or more arguments of any type may follow the other arguments. The ellipsis symbol must be last in the argument list, and may not appear as the only argument in a prototype.

All prototypes for a function must agree exactly, however it is legal for a definition of a function in the old style, i.e. with just the parameter names inside the parentheses, to follow a prototype declaration provided the number and type of the arguments agree. In this case it is essential that the function definition is in scope of the prototype declaration.

Access to unspecified arguments (i.e. arguments supplied where an ellipsis appeared in a prototype) must be via the macros defined in the header file <stdarg.h>. This defines the macros va_start, va_arg and va_end. See va_start in the library function listing for more information.

NOTE that is is a grave error to use a function which has an associated prototype unless that prototype is in scope, i.e. the prototype MUST be declared (possibly in a header file) before the function is invoked. Failure to comply with this rule may result in strange behaviour of the program. HI-TECH C will issue a warning message ("func() declared implicit int") whenever a function is called without an explicit declaration. It is good practice to declare all functions and global variables in one or more header files which are included wherever the functions are defined or referenced.

Void and Pointer to Void

The void type may be used to indicate to the compiler that a function does not return a value. Any usage of the return value from a void function will be flagged as an error.

The type void *, i.e. pointer to void, may be used as a "universal" pointer type. This is intended to assist in the writing of general purpose storage allocators and the like, where a pointer is returned which may be assigned to another variable of some other pointer type. The compiler permits without typecasting and without reporting an error the conversion of void * to any other pointer type and vice versa. The programmer is advised to use this facility carefully and ensure that any void * value is usable as a pointer to any other type, e.g. the alignment of any such pointer should be suitable for storage of any object.

Type qualifiers

The ANSI C standard introduced the concept of typequalifiers to C; these are keywords that qualify the type to which they are applied. The type qualifiers defined by ANSI C are const and volatile. HI-TECH C also implements several other type qualifiers. The extra qualifiers include:

far
near
interrupt
fast interrupt
port

Not all versions of the compilers implement all of the extra qualifiers. See the machine dependent section for further information.

When constructing declarations using type qualifiers, it is very easy to be confused as to the exact semantics of the declaration. A couple of rules-of-thumb will make this easier. Firstly, where a type qualifier appears at the left of a declaration it may appear with any storage class specifier and the basic type in any order, e.g.

static void interrupt   func();

is semantically the same as

interrupt static void   func();


Where a qualifier appears in this context, it applies to the basic type of the declaration. Where a qualifier appears to the right of one or more '*' (star) pointer modifiers, then you should read the declaration from right to left, e.g.

char * far fred;

should be read as "fred is a far pointer to char". This means that fred is qualified by far, not the char to which it points. On the other hand,

char far * bill;

should be read as "bill is a pointer to a far char", i.e. the char to which bill points is located in the far address space. In the context of the 8086 compiler this will mean that bill is a 32 bit pointer while fred is a 16 bit pointer. You will hear bill referred to as a "far pointer",however the terminology "pointer to far" is preferred.


5.12.

There are two methods provided for in-line assembler code in C programs. The first allows several lines of assembler anywhere in a program. This is via the #asm and #endasm preprocessor directives. Any lines between these two directives will be copied straight through to the assembler file produced by the compiler. Alternatively you can use the asm("string"); construct anywhere a C statement is expected. The string will be copied through to the assembler file. Care should be taken with using in-line assembler since it may interact with compiler generated code.

Pragma Directives

The draft ANSI C standard provides for a #pragma preprocessor directive that allows compiler implementations to control various aspects of the compilation process. Currently HI-TECH C only supports one pragma, the pack directive. This allows control over the manner in which members are allocated inside a structure. By default, some of the compilers (especially the 8086 and 68000 compilers) will align structure members onto even boundaries to optimize machine accesses. It is sometimes desired to override this to achieve a particular layout inside a structure. The pack pragma allows specification of a maximum packing factor. For example, #pragma pack1(1) will instruct the compiler that no additional padding be inserted between struc ture members, i.e. that all members should be aligned on boundaries divisible by 1. Similarly #pragma pack(2) will allow alignment on boundaries divisible by 2. In no case will use of the pack pragma force a greater alignment than would have been used for that data type anyway.

More that one pack pragma may be used in a program. Any use will remain in force until changed by another pack or until the end of the file. Do not use a pack pragma before include files such as <stdio.h> as this will cause incorrect declarations of run-time library data structures.