Module 2: Elements of C


Reserved words and identifiers

 

C's 37 reserved words:

auto          enum          restrict          unsigned
break         extern        return            void
case          float         short             volatile
char          for           signed            while
const         goto          sizeof            _Bool
continue      if            static            _Complex
default       inline        struct            _Imaginary
do            int           switch
double        long          typedef
else          register      union
Note:
 

Identifiers in C:

 
Exercise 2.1: What kind of a compiler error do you get if you try to use a reserved word as an identifier? Use a reserved word to declare an integer identifier.


Comments

 

There are two types of comments in ANSI C99:

Consider this example: (source file)
#include <stdio.h>


/* This is a block comment. Everything between the begin-comment symbol
   and the end-comment symbol is ignored by the compiler. It is conventional 
   to use a separate line for the end-comment symbol.
*/


// This is an in-line comment.

int main (/* A strange place for a block comment */)
{
  printf ("Hello World!\n");  // Another inline comment.
}
Note:


Include's

 
The first line, and perhaps the next few, typically have #include's.


Data types

 

About C's data types:

 

Integer types (ANSI C99 additions shown in bold):

Type Typical
storage size
Range Print
specifier
int 2 or 4 bytes -32,768 to 32,767 (2 bytes)
-2,147,483,648 to 2,147,483,647 (4 bytes)
%d
unsigned int 2 or 4 bytes 0 to 65,535 (2 bytes)
0 to 4,294,967,295 (4 bytes)
%u
short 2 bytes -32,768 to 32,767 %d
unsigned short 2 bytes 0 to 65,535 %u
long 4 bytes -2,147,483,648 to 2,147,483,647 %ld
unsigned long 4 bytes 0 to 4,294,967,295 %lu
long long 8 bytes -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 %lld
unsigned long long 8 bytes 0 to 18,446,744,073,709,551,615 %llu
 

Let's look at an example: (source file)

#include <stdio.h>

int main ()
{
  int numDaysInYear = 365;
  long int numStarsInUniverse = 2000000000L;
  unsigned long long int largestIntegerInC = 18446744073709551615LL;

  printf ("numDaysInYear = %d\n", numDaysInYear);
  printf ("numStarsInUniverse = %ld\n", numStarsInUniverse);
  printf ("largestIntegerInC = %llu\n", largestIntegerInC);
}
Note:
  • Where to declare variables:
    • Prior to C99, all variables had to be declared either at the top of a function, or globally (outside any function).
    • Since ANSI C99, variables can also be declared inside blocks, as in: (source file)
      int main ()
      {
        int numDaysInYear = 365;
        printf ("numDaysInYear = %d\n", numDaysInYear);
      
        if (numDaysInYear < 366) {
            long int numStarsInUniverse = 2000000000L;
            printf ("Thank your lucky %ld stars\n", numStarsInUniverse);
        }
        else {
            unsigned long long int numStarsInUniverse = 1844674407370955161LL;
            printf ("Thank your lucky %llu stars\n", numStarsInUniverse);
        }
      }
             
    • Two commonly used declarations in other languages (Java, C++) are NOT permitted in C:
      1. Declaration in a for-loop:
        int main ()
        {
            int i;
        
            for (i=0; i<10; i++) {        // Allowed.
                printf ("i=%d\n", i);
            }
        
            for (int j=0; j<10; j++) {    // Not allowed in C89. j needs to be declared at the top.
                printf ("j=%d\n", j);
            }
        
        }
               
      2. Declare as needed:
        int main ()
        {
            int numDaysInYear = 365;                          // Allowed.
            printf ("numDaysInYear = %d\n", numDaysInYear);
        
            long int numStarsInUniverse = 2000000000L;        // Not allowed.
            printf ("Number of stars = %ld stars\n", numStarsInUniverse);
        }
               
    • To make matters confusing, some compilers will allow both types of declarations (for-loop, as-needed) above.
    • To be safe, you can follow ANSI C89 and always declare every needed variable at the top of the method or as a global.

  • Variables can be initialized and declared in one statement, or declared and initialized separately:
    #include <stdio.h>
    
    // Declaration and assignment.
    int numDaysInYear = 365;
    
    int main ()
    {
      // Declaration.
      long int numStarsInUniverse;
    
      printf ("numDaysInYear = %d\n", numDaysInYear);
    
      // Assignment.
      numStarsInUniverse = 2000000000L;
    
      printf ("numStarsInUniverse = %ld\n", numStarsInUniverse);
    }
        
    For variety, we have declared numDaysInYear as a global variable.

  • ANSI C99 has added the long long type, not available in C89 or earlier.

  • The int reserved word may be left out if modifiers are present:
    int main ()
    {
      int numDaysInYear = 365;
      long numStarsInUniverse = 2000000000L;
      unsigned long long largestIntegerInC = 18446744073709551615LL;
    
      printf ("numDaysInYear = %d\n", numDaysInYear);
      printf ("numStarsInUniverse = %ld\n", numStarsInUniverse);
      printf ("largestIntegerInC = %llu\n", largestIntegerInC);
    }
       

  • The letter L is appended to the end of a long constant.

  • The letter LL is appended to the end of a long long constant.
 

Screen output:

  • The printf library function is used for output to the screen.

  • The arguments to printf are:
        printf (format-string [zero or more variables])
      

  • The format string needs to specify all variables that follow the format string:
    • The idea is, printf will look at additional arguments only based on what's in the format string.
    • In the first printf above, the %d symbol specifies an integer.
    • Similarly, %ld specifies a long and %llu specifies an unsigned long long int.

  • The format string can specify any number of variables, separated by arbitrary text, for example:
    int main ()
    {
      int numDaysInYear = 365;
      long int numStarsInUniverse = 2000000000L;
      unsigned long long int largestIntegerInC = 18446744073709551615LL;
    
      printf ("numDaysInYear = %d  numStarsInUniverse = %ld  largestIntegerInC = %llu\n", 
              numDaysInYear, numStarsInUniverse, largestIntegerInC);
    }
      

  • The format string can also specify the number of significant digits to be printed out:
    int main ()
    {
      int numDaysInYear = 365;
      long int numStarsInUniverse = 2000000000L;
      unsigned long long int largestIntegerInC = 18446744073709551615LL;
    
      // Up to 5 digits:
      printf ("numDaysInYear = %5d\n", numDaysInYear);
    
      // Up to 10 digits:
      printf ("numStarsInUniverse = %10ld\n", numStarsInUniverse);
    
      // Up to 20 digits:
      printf ("largestIntegerInC = %20llu\n", largestIntegerInC);
    }
      
 
Exercise 2.2: Consider the program
#include <stdio.h>

int main ()
{
  int numDaysInYear = 365;
  long int numStarsInUniverse = 2000000000L;
  unsigned long long int largestIntegerInC = 18446744073709551615LL;

  printf ("numDaysInYear = %d  numStarsInUniverse = %ld  largestIntegerInC = %llu\n",   
          numDaysInYear, numStarsInUniverse, largestIntegerInC);
}
What happens when you mistakenly use %d for all the integers above?
 

Floating-point types:

Type Typical
storage size
Range Print
specifier
Approximate
precision
float 4 bytes 1.2 x 10-38 to 3.4 x 1038 %f 6 decimal places
double 8 bytes 2.3 x 10-308 to 1.7 x 10308 %lf 15 decimal places
long double 10 bytes 3.4 x 10-4932 to 1.1 x 104932 %llf 19 decimal places

Consider this example: (source file)

int main ()
{
  // Constant with "F" appended:
  float PI = 3.141F;

  // Constant in exponent format:
  double doublePI = 314.159265E-2;

  // Long double constant:
  long double ldoublePI = 3.14159265358979L;

  // Output in exponent format:
  printf ("float PI = %7.5e\n", PI);

  // Output in decimal format with field width and number of significant digits:
  printf ("double PI = %16.10lf\n", doublePI);

  // Long double's need to be printed as double's
  printf ("long double PI = %15.12lf\n", (double) ldoublePI);
}
Note:
  • The letter F is appended to the end of a "decimal" type constant:
      float PI = 3.141F;
        

  • Floating point constants can also be specified in exponent format:
      double doublePI = 314.159265E-2;
        

  • The letter L is used for a long double constant.

  • The print format string specifies both the total width of the field and the number of digits that follow the decimal point.

  • Use %e in the format string for exponent format.
 
Exercise 2.3: It's a common error to mistype the format string. Find out what happens when you use %d for a floating point number and %f for a double.
 

Character types:

Type Typical
storage size
Range Print
specifier
char 1 byte -128 to 127 %c
unsigned char 1 byte 0 to 255 none
signed char 1 byte -128 to 127 none
 

Consider this example: (source file)

int main ()
{
  char letter = 'a';
  unsigned char letter2 = 'b';
  signed char letter3 = 'c';

  printf ("letter = %c\n", letter);
  printf ("letter2 = %c\n", letter2);
  printf ("letter3 = %c\n", letter3);
}
Note:
  • For practical purposes, we almost always use char.

  • There is no special print-format specifier for signed and unsigned char.

  • char variables are also used to access memory on a byte-by-byte basis, as we will see shortly.


Casting

 

Consider this example: (source file)

int main ()
{
  int i = 5;
  long j = 6;
  double d = 3.141;

  j = i;         // Works fine. Implicit cast from int to long.
  d = i;         // Works fine. Implicit cast from int to double

  i = j;         // May not compile.
  i = d;         // May not compile.

  d = 3.141;
  i = (int) j;   // Compiles. Explicit cast from long to int.
  i = (int) d;   // Compiles. Explicit cast from double to int.

  // Cast's can be used in any expression:
  printf ("The int part of d=%lf is %d\n", d, (int) d);
}
Note:
  • To assign a variable higher in the cast hierarchy to one lower, an explicit cast is required.

  • An explicit cast is the desired type in parentheses placed before the target of the cast:
      i = (int) d;   
        

  • Many compilers don't warn you if you are "down" casting: this is a common source of error.

  • The implicit cast hierarchy is:
    shortintunsigned intlongunsigned longlong longfloatdoublelong double

  • Use an explicit cast in any assignment down the hierarchy.


Operators

 

An example with arithmetic operators (source file)

int main ()
{
  double x = 6, y = 5;
  int i = 8, j = 5;

  // Standard: plus, minus, multiple, divide
  printf ("x+y=%lf\n", x+y);     // Prints 11.0
  printf ("x-y=%lf\n", x-y);     // Prints 1.0
  printf ("x*y=%lf\n", x*y);     // Prints 30.0
  printf ("x/y=%lf\n", x/y);     // Prints 1.2

  // Integer divide and remainder:
  printf ("i/j=%d\n", i/j);      // Prints 1
  printf ("i mod j=%d\n", i%j);  // Prints 3

  // Post and pre-increment:
  printf ("i++ = %d\n", i++);    // Prints 8
  printf ("++j = %d\n", ++j);    // Prints 6

  // Assignment shortcut example.
  i += j;
  printf ("i=%d\n", i);          // Prints 15

}
 

An example with bitwise operators (source file)

int main ()
{
  int a = 9;
  int b = 4;

  printf ("a&b = %d\n", a&b);          // Bitwise AND: Prints 0
  printf ("a|b = %d\n", a|b);          // Bitwise OR: Prints 13 
  printf ("a^b = %d\n", a^b);          // Bitwise EOR: Prints 13

  printf ("~a = %d\n", ~a);            // Complement: Prints -10
  printf ("b << 1 = %d\n", (b << 1));  // Left shift by 1: Prints 8
  printf ("b >> 2 = %d\n", (b >> 2));  // Right shift by 2: Prints 1
}
 

An example with boolean operators (source file)

int main ()
{
  double x = 5, y = 6, z = 6;
  int result;

  printf ("x < y = %d\n", (x < y));     // Prints 1 (true)
  printf ("x <= y = %d\n", (x <= y));   // Prints 1
  printf ("y > z = %d\n", (y > z));     // Prints 0 (false)
  printf ("y >= z = %d\n", (y >= z));   // Prints 1
  printf ("x == y = %d\n", (x == y));   // Prints 0
  printf ("y == z = %d\n", (y == z));   // Prints 1
  printf ("x != y = %d\n", (x != y));   // Prints 1

  result = (x < y);
  printf ("result=%d\n", result);       // Prints 1

  if (result == 1) {      // Equality comparison.
    printf ("x < y\n");
  }
  else {
    printf ("x >= y\n");
  }
  // Prints "x < y"
}
Note:
  • In C, boolean operators result in 1 (indicating "true") or 0 (indicating "false").

  • These results can be assigned to int-compatible variables.
 

Boolean types:

  • C originally did not provide any boolean types.

  • There are now four ways of working with boolean's:
    1. Use the original approach of 1 and 0:
      int main ()
      {
        double x = 5, y = 6;
      
        int result = (x < y);
        if (result == 1) { 
          printf ("x < y\n");
        }
        else {
          printf ("x >= y\n");
        }
      }
          

    2. Define your own boolean types using the pre-processor:
      #define boolean int
      #define true 1
      #define false 0
      
      int main ()
      {
        double x = 5, y = 6;
      
        boolean result = (x < y);
      
        if (result == true) { 
          printf ("x < y\n");
        }
        else {
          printf ("x >= y\n");
        }
      }
          

    3. Use a recent addition to the standard C library:
      #include <stdio.h>
      #include <stdbool.h>
      
      int main ()
      {
        double x = 5, y = 6;
      
        bool result = (x < y);
      
        if (result == true) { 
          printf ("x < y\n");
        }
        else {
          printf ("x >= y\n");
        }
      }
          

    4. Use the new C99 _Bool type:
      #include <stdio.h>
      #include <stdbool.h>
      
      int main ()
      {
        double x = 5, y = 6;
      
        _Bool result = (x < y);
      
        if (result == true) { 
          printf ("x < y\n");
        }
        else {
          printf ("x >= y\n");
        }
      }
          

  • We recommend using the stdbool package, as shown in (3) above.


Constants

 

There are two ways of defining constants in C:

  • The simple and traditional way is to use the preprocessor:
    #define PI 3.14159
    
    int main ()
    {
      double radius = 5.0;
    
      printf ("Area of Circle with radius %lf is %lf\n", radius, PI*radius*radius);
    }
      

  • Another way is to use the const modifier that makes a variable immutable:
    const double PI = 3.14159;
    
    int main ()
    {
      double radius = 5.0;
    
      printf ("Area of Circle with radius %lf is %lf\n", radius, PI*radius*radius);
    }
      
    The latter approach is more general, and can be applied to function parameters as well:
    const double PI = 3.14159;
    
    // "const" declaration makes it illegal for a function to 
    // change the input parameter.
    
    double computeArea (const double radius)
    {
      return PI * radius * radius;
    }
    
    int main ()
    {
      double radius = 5.0;
      printf ("Area of Circle with radius %lf is %lf\n", radius, computeArea(radius) );
    }
      


Control flow

 

An overview of C's control flow statements via examples: (source file)

int main ()
{
  int i = 1;

  // if-statement:
  if (i == 0) {
    printf ("i is zero\n");
  }


  // if-statement with compound expression:
  if ( (i >= -1) && (i <= 1) ) {
    printf ("-1 <= i <= 1\n");
  }


  // if-else combination
  if (i == 1) {
    printf ("one\n");
  }
  else if (i == 2) {
    printf ("two\n");
  }
  else {
    printf ("larger than two\n");
  }


  // Variation of if-else above:
  if (i == 1) {
    printf ("one\n");
  }
  else {
    if (i == 2) {
      printf ("two\n");
    }
    else {
      printf ("larger than two\n");
    }
  }


  // Equivalent switch statement:
  switch (i) {
    case 1: {
      printf ("one\n");
      break;
    }
    case 2: {
      printf ("two\n");
      break;
    }
    default: {
      printf ("larger than two\n");
    }
  }


  // for-loop example:
  printf ("Numbers 0 through 9: \n");
  for (i=0; i < 10; i++) {
    printf (" %d\n", i);
  }

  
  // while-loop equivalent:
  printf ("Numbers 0 through 9: \n");
  i = 0;
  while (i < 10) {
    printf (" %d\n", i);
    i++;
  }


  // do-while equivalent:
  printf ("Numbers 0 through 9: \n");
  i = 0;
  do {
    printf (" %d\n", i);
    i++;
  } while (i < 10);



  // Example of using "break"
  printf ("Numbers 0 through 9: \n");
  i = 0;
  while (1) {
    if (i == 10) {
      break;
    }
    printf (" %d\n", i);
    i++;
  }
  

  printf ("Odd numbers less than 10:\n");
  i = 0;
  while (1) {
    // If we've reached the limit, break out of the loop.
    if (i == 10) {
      break;
    }

    // If it's an even number, skip to next iteration of loop.
    if (i % 2 == 0) {
      i++;
      continue;
    }

    // Print odd number.
    printf (" %d\n", i);
    i++;
  }

}
Note:
  • Between a for-loop and a while-loop, use a while-loop only if the while-loop is easier to write.

  • Similarly, prefer a while-loop over an equivalent do-while.

  • ANSI C99 allows declaration of the for-loop variable in the for-loop:
        for (int i=0; i < 10; i++) {
          // ...
        }
        
    Versions prior to C99 do not. For compatibility with many textbooks, we will declare variables at the top.



© 2003, Rahul Simha (revised 2017)