Miscellaneous Topics [Part 1]


Bit Field in Structure

Exercise :

(1) How many bits are necessary to record "Male", or "Female" ?
(2) Suppose a man lives no longer than 120 years, how many bits do we need to record a man's age ?
(3) How many bits are needed for 0 to 3000 years ?
(4) For 12 months ?
(5) For 31 days ?
(6) For 7 days in a week (mon, tue, .... ) ?

Ans :

(1) 1.
(2) 7 (because 27 = 128)
(3) 12 (212 = 4096)
(4) 4 (24 = 16)
(5) 5 (25 = 32)
(6) 3 (23 = 8)

Exercise : Bit fields are used in the structure below

typedef struct 
   {char name[40];
    unsigned sex : 1;
    unsigned age : 7;
    unsigned year : 12;
    unsigned month : 4;
    unsigned day : 5;
    char tel[20];
   } Mrec;
What is its size in bytes ? (i.e. sizeof(Mrec) = ? )
(Note the use of "colon" in specifying bit-field.)

Ans : Total number of bits is = 1 + 7 + 12 + 4 + 5 = 29, but the computer will make it 32 bits = 4 bytes, hence sizeof(Mrec) is 64.

Note:

  1. Bit field will usually be from an "unsigned", i.e. unsigned integer.
  2. For n bits, the maximum possible cases are 2n.
  3. Bit field will save disk space.

Octal and Hexadecimal Numbers

If we prefix a number by "0" (zero, not letter "O"), that means it is an octal number.

If we prefix it by "0x", or "0X", that means hexadecimal.

Exercise : What are the decimal equivalent for the following numbers :-

  1. 0173
  2. 0235
  3. 0296
  4. 0xfa
  5. 0Xfa
  6. 0x123b

Ans :

  1. = 1*64 + 7*8 + 3 = 123
  2. = 2*64 + 3*8 + 5 = 157
  3. Wrong. 9 is illegal (0,1,2,3,4,5,6,7 in octal system).
  4. = 15*16 + 10 = 250
  5. = 250 (0x or 0X, same)
  6. = 1*163 + 2*162 + 3*16 + 11 = 4667.

Common Error

Exercise : What will the following code produce ?

int i;
i = 0;
if (i = 1)
   {printf("i has the value 1\n");
   }

Ans : It should be

if (i == 1) { .... }
and not
if (i = 1) { .... }

In C, there is no assignment statement, but "assignment-expression". Hence the expression above is 1, which is "true" (any non-zero value is regarded as "true". 0 means "false"). The assignment part, i.e. i = 1, is regarded as a side operation only.

Hence the above code will put 1 into "i", then printf(...) will be executed.

This type of error is very common, and we should be careful.


Assign a value based on condition

In C, there is a "conditional expression"

condition ? expression1 : expression2
(Note : it is advisable to put brackets, e.g.
(condition) ? expression1 : expression2
though syntax does not require it. It is good programming practice to do so.)

Exercise : Rewrite the following codes using "condition expression" :-

iflag = 0;
if (linecount > 20)
   {iflag = 1;
   }

Ans :

iflag = (linecount > 20) ? 1 : 0 ;


Comma

We have met comma before, in "for" statement, e.g.

for (sum=0., i=0; i<100; i++) { ..... }

The following is part of C's syntax (or "grammar")

statement-list =    statement
                  | statement-list  statement

statement =    compound-statement
            | selection-statement
            | iteration-statement
            | labeled-statement
            | jump-statement
            | expression-statement

compound-statement = { declaration-list(optional)  statement-list(optional) }

selection-statement =    if (expression) statement
                       | if (expression) statement  else  statement
                       | switch (expression) statement

iteration-statement =   
      while (expression) statement
    | do  statement while (expression) ;
    | for (expression(optional) ; expression(optional); expression(optional)) statement
             
labeled-statement =    identifier : statement
                     | case constant-expression : statement
                     | default : statement

jump-statement =     goto identifier ;
                   | continue;
                   | break;
                   | return expression(optional);

expression-statement = expression(optional) ;

expression =     assignment-expression
               | expression , assignment-expression

Exercise : (Notice that "|" above means "or")

  1. What is a "compound statement" ? Can we make declarations within "compound statement" ?
  2. Among the statements in "iteration-statement", which statement(s) ends with a semi-colon ?
  3. Among the statements in "selection-statement", which statement(s) ends with a semi-colon ?
  4. Among the statements in "jump-statement", which statement(s) ends with a semi-colon ?
  5. Does "expression-statement" end with a semi-colon or not ? And what are expressions? (Detailed syntax for expression may be found in Appendix A of Kernighan and Ritchie's "The C Programming Language", but just guess what expressions are. Notice that expression is NOT the same as expression-statement !)
  6. Is the following a valid "expression" ?

    expr1, expr2, expr3, ... , assignment-expression

  7. Is the following a valid "expression" ?

    expr1, if (condition) statement, expr2, ... , assignment-expression

  8. The "expression" in "return expression(optional);" is optional. When can we omit the "expression" ?

  9. In this book, I always use
    if (condition) {statement}
    
    if (condition)
        {statement}
    else
        {statement}
    
    Can we omit the curly brackets, if it is a single statement and not a compound statement ?

Ans :

  1. "Compound statement" is one enclosed within curly brackets. And we can make declarations within it.
  2. " do ... while (condition); " is the only one that ends with semi-colon.
  3. None. No "selection-statement" ends with semi-colon.
  4. All. All "jump-statement" ends with semi-colon.
  5. "expression-statement = expressionopt ;" must end with semi-colon. An expression with a semi-colon is an expression-statement. Examples of expression:
    123
    24.5
    a + b
    c * d + e * f - 10.7
    func1(a , c+b, 2.3, 4, &p)
    scanf("%d%lf", &iwhich, &density)
    ar[4]
    ptr->name
    record.wages
    ptr++
    a<<3
    a > 10.5 && a < 100
    b == 6 || b == 7
    iflag & jflag
    iflag | jflag
    ....
    ....
  6. Yes. We may separate expressions with comma. The whole is regarded as an expression. And its value is the last expression, (not the first, nor intermediate ones).
  7. No. Because "if (condition) statement" is a "statement", and "statement" cannot be in an expression.
  8. For functions that returns no value, e.g. "void func(...) {.....}", we may simply use "return;".
  9. If there is one single statement, and not a compound statement, we may omit curly brackets. But doing so is very dangerous, especially in nested if statements - the "else" part may get confused, and this kind of bug is very difficult to discover. Moreover, in PERL, which is similar to C in syntax, curly brackets MUST be included, even for a single statement. Hence if we have formed the habit of using curly brackets, when we program in PERL, we could avoid errors.

"for" statement

The syntax is

for (expr1(opt) ; expr2(opt); expr3(opt)) statement
Notice that all three expressions, "expr1, expr2, expr3", are optional.

expr1 consists of initialization statements (abuse of the term "statement" ! correct one should be initialization expression !)

expr2 usually is a condition, while

expr3 consists of loop-update statements.

We should form the habit of putting ALL initialization statements in "expr1", and not put some of them outside "for".

We should also form the habit of putting all loop-update statements in "expr3", and not put them within tbe body.

Exercise : If all "expr1, expr2, expr3" are omitted, e.g.

for (;;)
   {...
    ...
   }
what does it mean?

Ans : It is an infinite loop, same as, e.g.

L20:
     { ...
       ...
       
       goto L20;
     }


continue, break

Exercise : There are 3 kinds of iteration statements, what are they?

Ans : They are

  1. for ( .. ; .. ; .. ) { ... }

  2. while (condition) { ... }

  3. do { ... } while (condition);

(Notice that curly brackets are not mandatory, but it is good programming practice always to include them.)

Exercise : break is a keyword in C, and may be used for all three iteration statements. It is used to jump out of the iteration. Are there other ways to jump out of a loop ?

Ans : Yes. "goto label" will work always. And "goto" must be used if we are to jump out of nested loops.
break jumps out of one loop only, the loop where it is used.

(Note : Many people would advise against the use of "goto" statement. My personal opinion is for "goto". I studied Maths in university, and I love simplicity. In Maths proofs, it is very important that one can see through all the logical steps down to the basic axioms. I like the C language because it enables me to see through onto the microprocessor hardware instructions. I like "goto" and "if" for the same reason. I tried to use the various constructs "while, do .. while, do ... until, ... " and found my logical reasoning de-railed. I reverted back to the simple "if, goto", and I enjoy peace of mind ever since then.

Incidentally, Ian Sinclair (a British) who manufactured calculators and computers in the past, and who learned electronics and computer all by himself. He is able to see through the instructions in microprocessor down to the logical gates - "J-K master-slave flip-flops". It would be nice if you can see through them as he does. He has written quite a number of books, and in them, he explained how a microprocessor is implemented through logical gates. It would even be better if you can see through the "logic gates" down to the quantum world, of fundamental Physics!)

Exercise : continue is another C keyword. It can also be used for all three iteration statements. What is the purpose of " continue; " ?

Ans : It is used to skip over the rest of the statements in the body, and start the next iteration, e.g.

     for ( .. ; .. ; .. )
         {   ......
             ......

            if (condition)
                {continue;
                }
                              /*  Statements here are not executed if
             ......               condition is true */
             ......
          }
In PERL (another computer language), the author uses "next" for "continue", and "last" for "break".

The reason why the author of C used "continue;" is that, in the old days, many people in the science/engineering community used FORTRAN, and "continue" is a keyword in FORTRAN, and is the last statement in a loop, e.g.

       do 100 i = 1, 20 
          .....
          .....

 100   continue
(for-loop in C is do-loop in FORTRAN)

And when we want to skip over the rest of the codes, we would use, e.g.
       do 100 i = 1, 20 
          .....
          .....
       if (condition) goto 100
          .....
          .....
          .....
 100   continue
Hence, the use of "continue" in C could be understood by many people.

Exercise : "break, continue" may be used in other statements too, not necessarily iteration-statements (e.g. to jump out of a compound statement) - True or False ?

Ans : False. They can only be used with iteration statements.


The importance of supplying "function prototypes"

Exercise : How many arguments does the function "fa(..)" has, and what data types are they, from the following statement,

....
fa(1, 8, 2, &b);
....

Ans : fa(..) needs 4 arguments. The first 3 are probably numbers (whether they are integer, character, long, short, float, double, signed or unsigned, we have no way to know), the 4th one is probably a pointer.

Exercise : Suppose the program contains

#include <stdio.h>

extern void fa(double, double, double, double *);
....
....
int main()
{ double b;
....
....

fa(1, 8, 2, &b);

....
....

return 0;
}

What is the purpose of putting the statement
extern void fa(double, double, double, double *);
at the very beginning ? What will the compiler do with the statement
fa(1, 8, 2, &b);
Will the compiler does the same if the declaration is
extern void fa(int, unsigned, float, double *);

Ans : "extern" is a C keyword. It informs the compiler that "fa(..)" is a subroutine defined elsewhere, and the linkage-editor should look for it in some libraries (or object files) outside this file.

The compiler knows now that fa(..) needs 4 arguments, the first three are of type double, and the last one, a pointer for double. When it encounters the statement

fa(1, 8, 2, &b);
It will convert "1, 8, 2" into double and pushes them into stack before calling the subroutine.

But if the declaration is

extern void fa(int, unsigned, float, double *);
It will convert "1" into an integer, "8" into an unsigned integer, "2" into a floating point number, and push them into the stack before calling the subroutine.

Such a declaration is called "supplying a function prototype to the compiler".

In the absence of a "function prototype declaration", compiler will take certain default actions. It will convert "char, short" into "int", and "float" into "double".

The rule is : we must supply a "function prototype" before calling a function. Or else the compiler will take default actions, (though compiler will usually warn us, if we use "gcc -Wall ... " (Warn all options)), and the result will be unpredictable.

Exercise : Study the example below, why have we omitted "extern" ?

#include <stdio.h>

void fa(double, double, double, double *);
....
....
void fa(double x, double y, double z, double *p)
{
....
....
}
....
....
int main()
{ double b;
....
....

fa(1, 8, 2, &b);

....
....

return 0;
}

Ans : Because the function "fa(..)" is defined in the same file, we omit "extern".

Exercise : What is the difference between

void fa(double, double, double, double *);
and
void fa(double x, double y, double z, double *p) { ... }

Ans : The first is a "function prototype declaration", and the second, a "function definition".

Exercise : Suppose we arrange the program in such a way that functions are defined first before being called, e.g. if fa(..) calls fb(..) which in turns calls fc(..), and we write the program in the following way :

#include <stdio.h>

....
....
void fc( ... )
{
....
....
}

void fb( ... )
{
....
....
}

void fa( ... )
{
....
....
}
....
....
int main()
{
....
....
}

Do we still have to supply "function prototypes" ?

Ans : In this case, no.
But unless the program uses a small number of subroutines, and we can remember their dependencies, we can rarely arrange the program this way, and "function prototypes declaration" is necessary.

Exercise : We have to include many header files in a C program, e.g. <stdio.h>, <stdlib.h>, <string.h>, <math.h>, .... What do you think the header files contain ?

Ans : Mostly they contain "function prototype declarations", together with some constants, e.g. "#define EOF -1", "#define NULL 0", ... There are a lot of "#ifdef .... " declaratives too, to cater for different operating systems (e.g. PC, or Mac, or Digital, or HP, ... various operating systems they use.)


Multi-dimensional Arrays

In Maths and Engineering, we often have to deal with matrices, and they are 2 dimensional arrays. Arrays of more than 2 dimensions are not uncommon in Maths/Engineering.

Declaration of Multi-dimensional arrays

For multi-dimensional array, they are declared as, e.g.

double a[3][5], b[4][2][3];
Then a[3][5] is a 2 dimensional array, and b[4][2][3] a 3 dimensional array.

  a[3][5]       * * * * *        (You may visualize it as a 3 by 5 matrix)
                * * * * *  
                * * * * *

  b[4][2][3]    4 layers each of     * * *       (You may visualize it as 4 layers
                                     * * *        each layer a 2 by 3 matrix.)

Initialization of multi-dimensional arrays

For 1 dimensional arrays, they are initialized in the following way, e.g.

double a[3]= { 10.3, 20.7, 77.0 };
For 2 dimensional arrays, e.g.
double b[3][2]= { {1.5, 2.6}, {-10, 27.3}, {6, 8.3} };
For 3 dimensional arrays, e.g.
double c[2][2][3] = {{{1,2,3}, {4,5,6}}, {{7,8,9}, {10,11,12}}};
and so on.

Arrays as pointers

The compiler accesses an element in a 1 dimensional array via pointers (address registers). e.g.

       double a[]={1, 2, 3, 4, 5};
       double b, c;
       b = a[2];
       c = *(a + 2);
then both b and c have values = a[2] = 3.

Exercise : What is the difference of the following two statements : -

char a[] = "Hong Kong";
char *ptr = "Hong Kong";
And are the following assignment statements correct ?
a = "Kowloon";
ptr = "Kowloon";

Ans :

Hence, functionally, compiler will treat
   char a[3];             as        char *a;


   char a[4][5];          as        char **a;
                               OR   char *a[4];    (1 dimensional array of pointers)


   double b[3];           as        double *b;


   double b[4][5];        as        double **b;
                               OR   double *b[4];


   double b[4][5][6];     as        double ***b;
                               OR   double **b[4]; 
                               OR   double *b[4][5];    (2 dimensional array of pointers)


   double b[4][5][6][7]   as        double ****b;
                               OR   double ***b[4];
                               OR   double **b[4][5];
                               OR   double *b[4][5][6];     (3 dimensional array of pointers)
But they have subtle differences as shown in the diagram above.

How to pass multi-dimensional arrays as arguments of functions

Suppose we wish to write a subroutine that will add two matrices A and B and put the sum in another matrix C. The number of rows and the number of columns may be varied.

      void m_add(a, ndima, b, ndimb, c, ndimc, nrow, ncol)
      int ndima, ndimb, ndimc, nrow, ncol;
      double a[][ndima], b[][ndimb], c[][ndimc];
      {   int irow, jcol;
          for (irow = 0; irow < nrow; irow++)
             for (jcol = 0; jcol < ncol; jcol++)
                 { c[irow][jcol] = a[irow][jcol] + b[irow][jcol];
                 }
          return;
      }
And to declare "function prototype, we would use either
void m_add(double [][], int, double [][], int, double [][], int, int, int);

void m_add(double *[], int, double *[], int, double *[], int, int, int);

void m_add(double **, int, double **, int, double **, int, int, int);
The last two will generate warning messages, but will compile and work alright.

Exercise : Write a subroutine that will multiply matrix A and B and put the product in another matrix C. A is m by n matrix, and B is n by p matrix.

Ans :

      void m_mult(a, ndima, b, ndimb, c, ndimc, nm, nn, np)
      int ndima, ndimb, ndimc, nm, nn, np;
      double a[][ndima], b[][ndimb], c[][ndimc];
      {   int irow, jcol, k;
          double sum;

          for (irow = 0; irow < nm; irow++)
          for (jcol = 0; jcol < np; jcol++)
              {for (sum=0., k=0; k < nn; k++)
                  {sum += a[irow][k] * b[k][jcol];
                  }
               c[irow][jcol] = sum;
              }
       
          return;
      }


Operator precedence

Exercise : Study the grammar below (NOT real C syntax ! Example only),
     expression =   assignment-expression
                  | conditional-expression
                  | unary-expression
                  |   ....
         ....
         ....

     additive-expression =     multiplicative-expression
                            |  additive-expression  +  multiplicative-expression
                            |  additive-expression  -  multiplicative-expression

     multiplicative-expression =       unary-expression
                                   |  multiplicative-expression  *  unary-expression
                                   |  multiplicative-expression  /  unary-expression 

     unary-expression =     primary-expression
                         |  unary-expression[expression]
                         |  unary-expression(expression,expression, ... )
                         |  unary-expression.identifier
                         |  unary-expression->identifier

     primary-expression =     identifer
                           |  constant
                           |  string
                           |  (expression)
then write down some expressions, and try to parse them, as the computer does, and determine "precedence rule" yourself. Next study the C grammar in Appendix A of "The C Programming Language", and find out precedence rules.

Ans : If you try to parse some expressions as the computer does, according to the example-grammar above, you will find

      primary-expressions which consist of identifer, constant, or "...", or (...)
        have the highest precedence.

      Next are, e.g.   a[3]
                       fa(3, 4, p+q, 20)
                       record.name
                       ptr->name

      Next are multiplication and division

      Finally comes addition and subtraction

For real C syntax, if you study Appendix A of the book, you will discover the precedence rule as follows (highest precedence first):


      Primary-expression, e.g. 12.3, wage, "Hong Kong"   (identifier, string)

      Next are, e.g.   a[3]                (array element)
                       fa(3, 4, p+q, 20)   (function)
                       record.name         (structure element)
                       ptr->name           (structure element via pointer)

      Next     !       (logical not)
               ~       (bitwise not)
               ++      (increment)
               --      (decrement)
               +       (unary plus sign)
               -       (unary minus sign)
               *       (indirect addressing, or load indirect)
               &       (load effective address)
               
      Next     *       (multiplication)
               /       (division)
               %       (modulo, or remainder)

      Next     +       (addition)
               -       (subtraction)

      Next     <<      (shift left)
               >>      (shift right)

      Next     <       (less than)
               <=      (less than or equal)
               >       (greatter than)
               >=      (greatter than or equal)

      Next     ==      (equal)
               !=      (not equal)

      Next     &       (bitwise and)

      Next     ^       (bitwise exclusive or)

      Next     |       (bitwise or)

      Next     &&      (logical and)

      Next     ||      (logical or)

      Next     .. ? .. : ..  (conditional expression)

      Next     =       (various assignment operators)
               +=
               -=
               *=
               /=
               %=
               &=
               ^=
               |=
               <<=
               >>=

      Finally   ,       (comma operator that separate expressions)


If you cannot remember these, use bracekts, this is the safest way.


[Previous] [Home] [Next]