
                  INTRODUCTION TO THE C LANGUAGE

A Brief History

     Towards the end of the sixties,  MIT was working closely with 
General Electric and Bell Labs to improve the performance of time-
sharing  operating systems.  They came up with MULTICS,  a  system 
that turned out to be too big,  too slow and too cumbersome.  Bell 
decided  to opt out,  leaving Ken Thompson without a  computer  to 
design his 'Space Travel' video game.
     He discovered an unused PDP-7 in one of the Bell offices  and 
went  back to work.  He experimented with a new file structure  he 
had  invented in order to improve the performance of his game  and 
he wrote in assembly language a simplified operating system called 
UNIX -- a pun on MULTICS.
     The project caught the attention of Dennis Ritchie who  wrote 
under  UNIX  a word processing program  which  another  department 
started to use. Bell gave them some money, a PDP-11/20 and more or 
less stamped an official status on their project.  UNIX started to 
spread within Bell because it was so well adapted to the needs  of 
people using it.
     Thompson played around with BCPL (Basic Combined  Programming 
Language),  a  simplified version of CPL,  itself a derivation  of 
Algol  60.  He  wrote an interpreter  called  'B'.  Ritchie  added 
structures to 'B' and the result was baptized 'C'.
     During  the  summer of 73,  UNIX was rewritten in C  and  was 
later  ported to other computers besides the Digital  ones...  and 
the rest is history.

PROS AND CONS OF C

     The  concept behind C was simple :  allow the  programmer  to 
express  his ideas clearly without giving a thought to the  system 
architecture (portability).  The object code has to be fast enough 
to make it unnecessary to use assembler in most cases.
     C  has  the  structural  benefits  of  Pascal  (at  the  data 
structures,  decisions  and control levels).  It's  portable  like 
FORTRAN and its efficiency comes close to Assembler.
     One  could  call  C  an  intermediate  language,  a  portable 
assembler  as it were.  Like assembler,  the C compiler takes  for 
granted that the programmer knows what he/she is doing.  An  error 
can  make  the  program crash in a  most  mysterious  way.  In  an 
environment where the operating system is not user-proof (like MS-
DOS) the whole system can come crashing down.
     The dangers of C haven't deterred programmers who in any case 
would have had to resort to Assembler or a language less suited to 
their needs.

HOW THIS COURSE IS STRUCTURED

     The following introduction to C is divided in two parts.  The 
first part will concentrate on concepts common to most languages : 
basic  structures,   operators,   statements,  program  structure, 
indirect addressing (pointers) and input/output.
     The  second part will go more deeply into these concepts  and 
will introduce files,  macros,  advanced pointers,  dynamic memory 
allocation and portability problems.

                        1 - DATA CONCEPTS

     In  C  one must declare all data  (variables  and  constants) 
before using them. This constraint makes for easier documentation, 
easier rewriting and allows more complex structures.
     C is case sensitive. In other words, the expression MAX_LINES 
is not equivalent to max_lines or to MAX_lines.  But many  linkers 
do  not make that distinction and therefore global variables  must 
be declared accordingly.

1.1 - Constants

     In  C  constants  are declared through the use  of  a  simple 
mechanism.  They  must be declared at the top of any  module.  The 
syntax is as follows :

          #define   N_LINES   24
          #define   PI        3.14159
     
     Traditionally  constants are given capitalized  names.  Note 
that the declarations are not followed by a semi-colon (;). Most C 
statements are ended with a semi-colon. The above declarations are 
in  fact macro declarations (more on this below) and the  compiler 
will substitute the N_LINES value everywhere in the text (this  is 
called 'pre-processing').
     A number beginning with 0 is considered an octal constant.  A 
number  beginning with 0x is considered an  hexadecimal  constant. 
For example :

          010       equals the decimal 8.
          0x10      equal the decimal 16.

     A character delimited by single quotes ' ' is replaced by its 
ASCII (or EBCDIC) value. For example (in ASCII) :

          'A'       equals decimal 65.
          'B'       equals decimal 66.

1.2 - Variables

     Variables  come  in  many flavors known  as  'primitive  data 
types' : char, int, short, unsigned, long, float, and double.
     Variables of type char occupy one byte and are mostly used to 
represent an alphanumerical character.
     Variables of type int,  short,  unsigned and long are used to 
represent integers.
     Variables of type float and double are used to represent real 
numbers of simple or double precision.

1.3 - Declaring Variables

     Variables must be declared before they are used. Declarations 
are  divided  in two parts :  a type specification and a  list  of 
names. For example :

          int       i, j, k;
          char      c, line[80], name[20];
          float     quotient;

     Variables  can be initialized with a default value by  simply 
adding an equal sign '=' and the desired constant value after  the 
name. For example :

          int       max = 9, min = 3;
          char      star = '*';
          float     pi = 3.14159;

1.4 - Memory Allocation vs Architecture

     The  memory space taken up by each variable type  depends  on 
the system architecture. Here is a table for different systems :

        68000     8086      PDP-11      IBM 370     CDC      HONEYWELL

        ASCII     ASCII     ASCII       EBCDIC      ex-ASCII   ASCII
char      8         8         8            8           ?         9
int      16        16        32           32          60        36
short    16        16        16           16          60        36
unsigned 16        16        32           32          60        36
long     32        32        32           32          60        36
float    32        32        32           32          60        36
double   64        64        64           64         120        72


                          2 - OPERATORS

     There  are  many  more  operators in  C  than  in  any  other 
language (except APL).  The code they generate is better suited to 
the programmer's needs.

2.1 - A brief commentary on commentaries

     Despite  what  certain programmers  think,  commentaries  are 
never superfluous.
     A commentary begins with /* and ends with */, like in PL-1. A 
commentary can spill on many lines.  Some compilers will allow you 
to nest your commentaries this way :

          /* level 1 ...
                     ... /* level 2 ... */
                                          ... level 1 */

     Nesting commentaries is a double-edged tool. It's very useful 
to logically delete segments of commented code. But one must never 
forget to balance the /* and */. Otherwise chaos ensues.
     A  humorous  commentary  at  the right  place  will  be  more 
appreciated  by one's peers than an  intricate  algorithm.  Enough 
said.

2.2 - Arithmetic Operators

     The fundamental binary operators in C are :  +,  -, *, /, and 
the modulo %. The unary operator - (negation) is supported but not 
the unary operator + (dummy operator).
     These operators have a normal precedence : that is *, / and % 
have a higher precedence than + or -.
     Fractions  are truncated in an integer division.  The  modulo 
operator only applies to integers.  Parentheses ( ) can be used to 
modify the way an expression will be evaluated. For example :

          a = (b + c) * d;
          x = a + (CONST_1 + CONST_2);

     The  parentheses seem redundant in the  second  example,  but 
some compilers especially on small systems would not have  reduced 
the  constants  to  their simplest  expression  in  the  compiling 
process  and the code generated would have  been  slower.  (Always 
read the manual !)

2.3 - Relational Operators
     
     C has the following relational operators :  <,  >, <+, >=, == 
and != (unequality).  One cannot replace <=,  >= and != by =<,  => 
and  =!.  These  last three operators are not recognized  by  most 
compilers.
     Relational operators return a null value if the expression is 
false and a non-null value otherwise. The constants TRUE and FALSE 
are usually declared this way :

          #define   TRUE      -1        /*1111111111111111*/
          #define   FALSE      0        /*0000000000000000*/

     The operators <,  >,  <= and >= have a higher precedence than 
== and !=.

2.4 - Logical Operators

     The logical operators :  && (AND),  || (OR), ! (NOT) are used 
with logical values :

          if (a <b && b < c) ...

     They return a null value if the expression if false.  The  && 
operator has a higher precedence than ||.  The not operator !  has 
the highest precedence.

2.5 Bitwise Operators

     Bitwise operators allow the programmer to manipulate bits.  C 
supports the following operators :  & (and), | (or) ^ (exclusive or 
--  xor),   <<  (shift  left),  >>  (shift  right)  and  ~  (one's 
complement).
     Do  not  confuse  the logical operators && and  ||  with  the 
bitwise operators & and |. For example :

          2 & 1     yields         0
          2 && 1    yields        TRUE (non-null).

     The  shift  operators are binary :  the left operand  is  the 
value to be shifted;  the right operand is the number of shifts to 
be performed. For example :

          1 << 2    yields    4.

2.6 - Increment and Decrement

     C  supports two unary operators to increment or  decrement  a 
variable value : ++ and --. For example :

          ++x; or x++;   is equivalent to    x = x + 1;
          --x; or x--;   is equivalent to    x = x - 1;

     These operators can be used as prefix or suffix.  As  prefix, 
the variable will be incremented or decremented before it is used. 
As suffix, after it is used. For example :

          a = ++x * b;   is equivalent to    x = x + 1; a = x * b;
          a = v[x--]     is equivalent to    a = v[x]; x = x - 1;

2.7 - Assignment Operators

     C has more assignment operators than most languages. There is 
of course the traditional equal sign = which assigns the value  of 
the expression on the right to the variable on the left.  C allows 
multiple assignments such as :

          a = b = c = 100;

     Furthermore the operators +,  -,  *, /, %. <<, >>, &, ! and ^ 
can be combined with = in order to write more compact expressions. 
For example :

          i += 2;        is equivalent to    i = i + 2;
          i <<= j;       is equivalent to    i = i + << j;
          i *= k + 1;    is equivalent to    i = i * (k + 1);

     It must be emphasized that the numerical operator must be  on 
the left of the equal sign (though some old compilers support both 
forms :  i += or i =+). The second form should not be used even if 
the  compiler  allows it because the syntax is ambiguous  and  the 
compiler  could  misinterpret  the  programmer's  intention.   For 
example :

          i =- 5;        could mean     i = i - 5;
                         or             i = (-5);

     Compilers  who support this archaic form only do so in  order 
to remain compatible with older versions.
     These operators,  besides being elegant, can truly inform the 
compiler about the programmer's intentions;  the compiler can then 
generate more efficient code.

2.8 - The Conditional Operator

     In  many cases the assignment of a value to a  variable  will 
depend  on  the value of another variable or the  value  of  other 
variables. For example:

          if (a > b)
               max = a;
          else
               max = b;

     C allows a more compact form of this operation :

          max = (a > b) ? a : b;

     Of course this operation can be (and is often) used inside an 
expression. For example :

          a = b / ((c == 0) ? 1 : c);

     Here is the syntax of this operator :

          ex1 ? ex2 : ex3

     Ex1 in a logical expression.  Ex2 will be evaluated if ex1 is 
TRUE and ex3 will be evaluated if ex1 is FALSE.

2.9 - Type Conversion
   
     When different types are used in an expression,  the compiler 
will  implicitly (automatically) convert one of the operands to  a 
common  type (if this conversion is  logical).  For  example,  the 
compiler  will  convert an integer to a real in the  expression  : 
i+f.
     Illogical expressions (for example,  if one indexes an  array 
with real numbers) will have to be explicitly converted to another 
type.
     The rules of implicit conversions are fairly simple :  in any 
arithmetic  expression  'char' and 'short' will  be  converted  to 
'int',  and 'float' will be converted to 'double'.  If one of  two 
operands  is  a  'double' the other one will  be  converted  to  a 
'double'.  If one of two operands is a 'long',  the other one will 
be converted to a 'long'.  If one of two operands is an 'unsigned' 
the  other  one  will be converted  to  an  'unsigned'.  Else  all 
operands  must  be of type 'int' and the result will  be  of  type 
'int'.
     All  floating  point  operations  are  in  double  precision. 
Conversion  from  'char'  to 'int' is done with  or  without  sign 
extension,  depending  on the compiler.  Conversion from 'int'  to 
'char' is done by truncating the most significant bits. Conversion 
from 'float' to 'int' is done by truncating the decimals.
     An  explicit  conversion is required if  the  compiler  can't 
apply  the preceding rules.  The operand must then be preceded  by 
the  name  of the type in parentheses  :  (type)  expression.  For 
example :

          float f;
          i = v[(int) f];

     In the philosophy of C,  the compiler takes for granted  that 
the programmer knows what he/she is doing. If the compiler detects 
type incompatibities, it will display an error message but will go 
on compiling.


2.10 - The sizeof Operator

     The  sizeof operator returns the number of bytes occupied  by 
the variable of the argument. For example :

          long dummy;              /* a long occupies 4 bytes */
          i := sizeof(dummy)       /* answer is 4.            */


2.11 - Magical Operators

     For your own cultural benefit,  we will briefly mention  here 
operators  which  have  pointers as operands  or  which  are  most 
particular to C. A more detailed discussion will follow later on.
     Here they are :  .  (the structure operator), -> (the pointer 
operator  to  a  structure),  *  (the  pointer),  &  (the  address 
operator), [] (the index operator), () (the function operator) and 
, (the comma operator).
     Some of these operators may seem trivial,  the index operator 
for example.  But one can use the latter without understanding its 
real meaning, or without understanding the C philosophy.


2.12 - Operator Precedence

     Here is a table of precedence and associativity.

     Operator                                          Associativity
     ---------------------------------------------------------------
     ()  []  ->  .                                     Left to Right
     !  ~  ++  --  -  *  &  (type)  sizeof                R to L 
     *  /  %                                              L to R
     +  -                                                 L to R
     <<  >>                                               L to R
     <  <=  >  >=                                         L to R
     ==  !=                                               L to R
     &                                                    L to R
     ^                                                    L to R
     |                                                    L to R
     &&                                                   L to R
     ||                                                   L to R
     ? :                                                  R to L
     =  +=  -=  *=  /=  %=  &=  |=  <<=  >>=              R to L
     ,                                                    L to R
     ---------------------------------------------------------------

     Left to right examples :

       a + b + c           will be evaluated        (a + b) + c
       a == b == c         will be evaluated        (a == b) == c
       a << b >> c         will be evaluated        (a << b) >> c

     Right to left examples :

       *a++                will be evaluated        *(a++)     
       a  +=b += c         will be evaluated        a += (b += c)
       a ?  b ? c : d : e  will be evaluated        a ? (b ? c : d) : e



																																																																																																																							