PART 2

 

 

Language Rules / Syntax

 

 

 

        Compiler options and error checking

        Constants and variables

        Data types and operations

        Expressions and assignment

        Control structures and looping


Error  Checking

 

Unix

lint -abchx myprog.c

lint -nuvz myprog2.c

 

VMS

cc /standard=portable myprog

cc /nowarnings myprog2

 

Turbo  C

Alt-O

C

E

P

 

Turbo C++,  Borland C++  (prior to version 4.0)

Alt-O

C

E

M

Alt-P

     (fill all X's)

K

Alt-A

     (fill all X's)

M

     (fill all X's)

K

K

Alt-C

     (fill all X's)

K

Alt-F

     (fill all X's)

M

     (fill all X's)

K

K

K


PROGRAM  ERROR  CHECKING

 

 

        In the Unix environment, more extensive error checking is available through the lint program checker.  In other environments, compiler switches or options are used to control the extent of error checking.

    lint is invoked most simply by:

         lint filename.c

          lint checks for:

                   - syntax errors (just like the C compiler)

                   - unreachable code

                   - unused values

                   - type mismatches (especially between functions)

                   - loops not entered at the top

                   - automatic variables declared but not used

                   - logical expressions that evaluate to a constant

                   - consistent function usage (number of arguments, use of return values)

    lint can be made more sensitive or less sensitive to "errors" through the use of command options.  These options may be specified as a string of characters after the command.  Command options that enhance the error checking, making lint more sensitive to errors are:

          a       assignment of long values to int variables

          b       unreachable break statements

          c       casts of questionable portability

          h       heuristically intuit bugs, waste, bad style

          x       report unused external declarations


Turbo  C  Error  Options

 

 

Portability warnings

 

A: Non-portable pointer conversion

B: Non-portable pointer assignment

C: Non-portable pointer comparison

D: Constant out of range in comparison

E: Constant is long

F: Conversion may lose significant digits

G: Mixing pointers to signed and unsigned char

 

 

ANSI violations

 

A: 'ident' not part of structure

B: Zero length structure

C: Void functions may not return a value

D: Both return and return of a value used

E: Suspicious pointer conversion

F: Undefined structure 'ident'

G: Redefinition of 'ident' is not identical

 

 

Common errors

 

A: Function should return a value

B: Unreachable code

C: Code has no effect

D: Possible use of 'ident' before definition

E: 'ident' is assigned a value which is never used

F: Parameter 'ident' is never used

G: Possibly incorrect assignment

 

 

Less common errors

 

A: Superfluous & with function or array

B: 'ident' declared but never used

C: Ambiguous operators need parentheses

D: Structure passed by value

E: No declaration for function 'ident'

F: Call to function with no prototype


ERROR  CHECKING  (Continued)

 

 

    lint command options that suppress some error checking, making lint less sensitive to errors are:

          n       do not check compatibility against the standard  library

          u       do not check variables/functions used and not defined, or defined and not used

          v       do not check for unused arguments in functions

          z       do not check for undefined structures

        In VMS, command qualifiers are used to control the level of error checking.  There are three categories of warnings:

                   informational                   warning                 portability

          By default, the first two are enabled and the third is disabled.  The /NOWARNINGS qualifier disables informational and warning messages.  The /WARNINGS=NOINFORMATIONALS and /WARNINGS=NOWARNINGS qualifiers disable just one of the two categories.  The /STANDARD=PORTABLE qualifier enables portability warnings.

        Turbo C has 27 independent error options that can be individually enabled or disabled.  These, in turn, are divided into four categories, as shown on the facing page.  In the integrated environment, these are switched from menus nested beneath the options main menu item.

        Turbo C++ has 53 independent error options, and Borland C++ has 52 independent options, divided into the following categories: Portability, ANSI, C++, and Frequent


Error  Checking  in  Turbo  C++ 4.5  and
Borland  C++  4.0  and  4.5  for  Windows

 

 

 

1. Select options from main menu

 

2. Select project from options menu

 

3. Double click on messages (if preceded by a +)

3.a. This allows viewing the 8 message categories

 

4. Click on each message category in turn

4.a. This allows viewing of 3 to 13 check boxes

 

5. Click on each unchecked item to check it

 

6. Click on OK after completion of 8 message categories

 

OR

 

1. Select options from main menu

 

2. Select project from options menu

 

3. Click on messages

 

4. Click on ALL diamond to enable all messages (does not change selected messages in each category - messages not checked in each category will be disabled when the SELECTED diamond is clicked)


PROGRAM  ERROR  CHECKING

 

 

        Turbo C++ 4.5 and later, and Borland C++ 4.0 and later use compiler switches or options to control the extent of error checking.

        The sequence on the opposing page will make the Turbo C++/Borland C++ compiler as error sensitive as possible.

        Not all programming errors, of course, can be found during compile time.  Be sure to watch for compiler, linking, and execution errors.

        Thus, you should do separate compilation, linking, and running (Alt-F9, followed by F9, or Alt-F9 followed by Ctrl-F9).

        To enable checking for stack overflow:
1. Select options from main menu
2. Select project from options menu
3. Click on compiler until compiler options expanded
4. Click on debugging until debugging options expanded
4. Click on TEST STACK OVERFLOW box until box is checked.

        Be sure to watch for messages at the end of execution.  If you have "left something behind" that is discovered at the end of execution, this is a very serious error and must be fixed.

        Be particularly wary of any program that gives a “null pointer assignment” error message at the end of execution or warns of possible “memory leakage.”


Turbo C++ 4.5/Borland C++ 4.0/4.5 Error Options

 

The Turbo C++ 4.5 and Borland C and C++ compilers have 51 to 56 independent error checking options that can be enabled or disabled.  Borland C++ for Windows has 56 options divided into 8 categories.

 

Portability

Non-portable pointer conversion

Non-portable pointer comparison

Constant out of range in comparison

Constant is long

Conversion may lose significant digits

Mixing pointers to different ‘char’ types

 

ANSI violations

Void functions may not return a value

Both return and return with a value used

Suspicious pointer conversion

Undefined structure structure

Redefinition of macro is not identical

Hexadecimal value contains more than three digits

Bit fields must be signed or unsigned int

Identifier is declared as both external and static

Declare type prior to use in prototype

Division by zero

Initializing identifier with identifier

Initialization is only partially bracketed

Non-ANSI keyword used: word

 

Obsolete C++

Base initialization without a class name is now obsolete

Style of function definition is now obsolete

Overloaded prefix operator used as a postfix operator

 

Inefficient C++ Coding

Functions containing identifier are not expanded inline

Temporary used to initialize identifier

Temporary used for parameter in call to identifier


Borland C++ 4.0 for Windows Error Options (Continued)

 

 

Potential C++ errors

Constant member identifier is not initialized

Assigning type to enumeration

Function1 hides virtual function function2

Non-const function function called for const object

Base class base1 is also a base class of base2

Array size for ‘delete’ ignored

Use qualified name to access nested type type

Handler for xxx is hidden by previous handler for yyy

Conversion to type will fail for members of virtual base base

Maximum precision used for member pointer type type

Use ‘> >‘ for nested templates instead of ‘>>‘

Non-volatile function function called for volatile object

 

Potential Errors

Possibly incorrect assignment

Possible use of identifier before definition

No declaration for function function

Call to function with no prototype

Function should return a value

Ambiguous operators need parentheses

Condition is always true/false

 

Inefficient Coding

Identifier is assigned a value that is never used

Parameter identifier is never used

Identifier is declared but never used

Structure passed by value

Unreachable code

Code has no effect

 

General

Unknown assembler instruction

Ill-formed pragma

Array variable variable is near

Superfluous & with function

Identifier is obsolete

Cannot create precompiled header: header


maxline.c

 

/* MAXLINE.C: Find and print the longest line from input.

   Functions in separate files, parameters passed */

 

/* stdio.h should be in all files containing I/O */

#include <stdio.h>

 

#define MAXLINE 1000  /* maximum input line size */

 

/* all functions invoked should be declared external

     and their type should be specified */

extern int getline(char [], int);

extern void copy(char [], char[]);

 

main()      /* Computes and prints the longest line */

{

     int  len,          /* current line length */

          max = 0;       /* maximum length seen so far */

     char line[MAXLINE], /* current input line */

          save[MAXLINE]; /* longest line, saved */

 

     while ( (len = getline(line, MAXLINE)) > 0 )

          if (len > max)

          {

              max = len;

              copy(save, line);

          }

 

     if (max > 0)  /* there was a line */

     {

          printf("Line below longest, %d chars.\n",max-1);

          printf("%s", save);

     }

     return 0;

}

 


SEPARATE SOURCE FILES

 

 

        Sometimes source code is so long that it must be divided into files.

        Sometimes source code exists in project source code libraries for common use.

        Variables and functions used across files must be declared in such a way that needed information is available when each file goes through the compiler.  The extern statement declares variables and functions to be found in other files.  All functions should also be explicitly typed, rather than defaulting to type int.

        Each file can be compiled separately, or several files can be compiled together.

        Finally, the parts are linked together to form the final executable version of the program.

        For example, main, getline, and copy are divided into three files, with function main() in file maxline.c, function getline(s) in file getline.c, and function copy(s1,s2) in file copy.c.

        Note that the function main() in file maxline.c requires two extern statements to declare the functions called by the function main(), but not defined in the file maxline.c.

        To check all three source files as a whole for errors, use the lint command:

         lint maxline.c getline.c copy.c

        To compile all three source files as a whole, link and load the object files, and generate an executable file a.out, use the command:

         cc maxline.c getline.c copy.c


getline.c

 

/* GETLINE.C: Separately compiled routine to fetch a line

   - stdio.h is included because the routine does I/O */

#include <stdio.h>

 

/* the file needs no define or external statements */

 

/* Get input line into array s.  The function getline

returns the length of the input line, including a newline

character, if present.  The array s is terminated with an

ascii null */

 

int getline(char s[], int lim)

{

     int c, i = 0;

 

     while( i<lim-1 && (c=getchar())!=EOF  &&  c!='\n')

          s[i++] = c;

 

     if (c == '\n')

          s[i++] = c;

 

     s[i] = '\0';

     return(i);

}

 

copy.c

 

/* COPY.C: separately compiled function to copy a string

*/

/* needs no includes, defines, or externals */

 

/* copy 'from' into 'to'; assume 'to' is big enough */

void copy(char to[], char from[])

{

     int i = 0;

 

     while( (to[i] = from[i]) != '\0' )

          ++i;

}

 



Compiling  Separate  Files

 

 

Unix

 

cc -c getline.c

cc -c copy.c

cc maxline.c getline.o copy.o

a.out

 

 

 

VMS

 

cc getline

cc copy

cc maxline

link maxline, getline, copy

run maxline

 

 

 

Turbo  C,  Turbo  C++

 

tcc -c getline

tcc -c copy

tcc maxline getline.obj copy.obj

maxline

 

 

 

Borland  C++

 

bcc -c getline

bcc -c copy

bcc maxline getline.obj copy.obj

maxline


SEPARATE COMPILATION

 

        Many times there are common functions that do not change.  It is inefficient to recompile a function each time it is used in a program.

        The idea is to not only divide the source code into separate files, but also to compile these files separately, storing the compiled version of the program rather than the source code.

        To check each file for accuracy in a Unix environment with lint, use the commands:

         lint -u getline.c

         lint -u copy.c

         lint maxline.c

          The -u option on the lint command tells the program checker to not complain about functions and variables used and not defined or defined and not used.  We know that each individual file does not have all the pieces.

        The -c option on the cc command in Unix and Turbo C tells the compiler to suppress the loading phase of the compilation (thus not looking for a function main(), nor expecting all parts of the program to be present.)  It also forces the creation of the object files getline.o and copy.o, respectively in Unix.

        Separate compilation saves compile time if there are many programs that need the functions stored in getline.c and copy.c, or if they are lengthy functions not subject to frequent change.

        In VMS, the three compile lines may be replaced by the single line:

         cc maxline, getline, copy

          In this case, three separate object files are created.

        In addition, source files in VMS may be treated as if they were contained in a single file by using the single command line:

         cc maxline + getline + copy

          In the above case, only the object file maxline.obj would be generated.  (The first name is used.)


maxline2.c

 

/* MAXLINE2.C: Find and print the longest line from input

   Uses global variables, no parameters passed */

#include <stdio.h>

 

#define MAXLINE 1000    /* maximum input line size */

 

int max = 0;             /* maximum length seen so far */

char line[MAXLINE];      /* current input line */

char save[MAXLINE];      /* longest line saved here */

 

int getline(void);

void copy(void);

 

/* Computes and prints longest line; specialized version*/

main()

{

     int  len;             /* current line length */

 

     max = 0;

 

     while ( (len = getline()) > 0 )

          if (len > max)

          {

              max = len;

              copy();

          }

     if (max > 0)       /* there was a line */

     {

          printf("Line below longest, %d chars.\n",max-1);

          printf("%s", save);

     }

     return 0;

}

 


maxline2.c  EXPLANATION

 

 

        The three functions comprising this program file accomplish the same goal as in maxline1.c, but are organized in a very different way.

        In maxline1.c, all data was local to each function.  Whenever one function needed information from another it had to be passed back and forth via parameters and return values.  This is not always practical.

        In maxline2.c, the integer max, and the character arrays line and save are defined to be external (or global) variables.   This means they are available to all functions that want them without using parameters or return values.  An external definition is made by merely placing the definition outside of any function.

        Since the philosophy of data sharing between functions has been altered, the declarations of the functions, as well as the functions themselves, must be altered.  No data needs to be passed to getline, so we must declare that getline takes no parameters.  This is done with the statement

         int getline(void);

          which also declares that getline will (still) return an integer.

        Likewise, the function copy does not need any parameters, and, since it doesn't need to return any values either, it is declared as

         void copy(void);

        For a function to use an external variable defined in the same file, it need do nothing but use the variable name in the correct way.  For a function to use an external variabled defined in another file, it must declare its use at the beginning.  If another function that wished to use the variable max were written with its source code in a different file, it would need to have the following statement in the beginning:

         extern int max;


maxline2.c  (continued)

 

/* Get input line into array line.  The function getline

returns the length of the input line, including a newline

character, if present.  The array line is terminated with

an ascii null; specialized version */

int getline(void)

{

     int c, i = 0;

 

     while (i < MAXLINE-1 && (c=getchar()) != EOF

                             && c != '\n')

          line[i++] = c;

 

     if (c == '\n')

          line[i++] = c;

 

     line[i] = '\0';

     return i;

}

 

/* copy: copy 'line' into 'save';

   assume 'save' is big enough; specialized version */

void copy(void)

{

     int i = 0;

 

     while ((save[i] = line[i]) != '\0')

          ++i;

}

 


maxline2.c  EXPLANATION  (Continued)

 

 

        Note that this program becomes "easier" to write without parameters.  It will probably also achieve minor performance gains in execution speed.

        The functions, however, are much less general.  They will only work with properly named external variables.  The external names also get involved in the linking process.  So while execution may go a bit faster, compiling and linking will take longer.

        In general, external variables should be avoided in favor of passing parameters at almost all costs.  The most notable exception to this rule is for keeping track of machine status information across a variety of routines.  In particular, error status is the most common status information that might require the use of global variables.


declar.c

 

/* DECLAR.C: The sqrt and log are math functions (in the C

   library) that return the square root and natural

   logarithm, respectively, of their arguments.  Both

   functions take doubles as their arguments and return

   doubles. */

/* Under Unix, the fact that the math functions in the

   math library are being used must be conveyed to the

   linker.  If prog.c is a C program that uses these math

   functions, then the program is compiled as:

          % cc prog.c -o prog -lm

   (-lm tells the linker to use the math library) */

#include <stdio.h>

 

/* The following declarations for sqrt and log are

   necessary - remove them or get them wrong and see what

   happens.  The point of this program is that all

   functions that return a value other than an integer

   MUST be declared before they are used. */

 

extern double sqrt(), log();  /* no argument conversion */

/* or #include <math.h>  - ANSI, always gets it "right" */

/* or extern double sqrt(double), log(double);  - ANSI  */

/*  ANSI prototype converts arguments */

 

main()

{

     double c;

 

     for ( c = 1.0; c <= 10.0; ++c )

          printf("%3.0f   %1.5f  %1.5f\n",

                   c, sqrt(c), log(c) );

 

     for ( c = 1.0; c <= 10.0; ++c)

          printf("%3.0f   %1.5f  %1.5f\n",

                   c, log(c), sqrt(c) );

 

     return 0;

}

 


DECLAR.C EXPLANATION

 

 

        This program invokes functions not included in any of the programmer's source code files.  The functions are mathematical functions included in the C math library.

        To use these functions two things are needed.  First, the proper declarations must be given in the source file.  Second, the proper object code for the functions used must be obtained.

        In this example, the function declarations are given explicitly.  They could also have been given by using the statement:

         #include <math.h>

          which is merely a file containing declarations of the various functions in the math library.

        To obtain the object code:

                   Unix needs the -lm option given to the C compiler (or the loader) when the program is compiled (loaded).

                   VMS VAXC needs the logical name lnk$library set to sys$library:vaxcrtl (which it also needs for all I/O functions)



Lab  1  for  Part  2

 

 

1.  Try compiling and executing declar.c five different times:

          a.  with the preferred #include <math.h> statement

          b.  with the complete declaration:

         extern double sqrt(double), log(double);

    statement

          c.  with a pre-ANSI declaration:

         extern double sqrt(), log();

          d.  with no declaration statement

          e.  with a totally incorrect declaration of sqrt and log, such as

         extern long sqrt(char), log(unsigned);

          Do this with the most sensitive error checking the compiler will allow.

          Where are problems detected in each case?

 

2.  Change the program declar.c so that it attempts to take the square root and natural logarithm of integers.  Do this by replacing the definition of the variable c and the controlling for loops as follows:

         int c;

         for (c = 1; c <= 10; ++c)

          Now repeat your attempt to use various declarations for the functions to see what happens.


Reserved  Names  in  C

 

 

asm

double

if

static

auto

else

int

struct

break

entry

long

switch

case

enum

noalias

typedef

char

extern

register

union

const

float

return

unsigned

continue

for

short

void

default

fortran

signed

volatile

do

goto

sizeof

while

 

 

Identifiers to avoid if C++ is planned

 

catch

handle

overload

template

class

inline

private

this

delete

new

protected

virtual

friend

operator

public

 

 

 

Borland C++, Turbo C++, Turbo C extra keywords

 

cdecl

huge

near

pascal

far

interrupt

 

 

...and about 26 names beginning with an underscore


VARIABLE NAMES

 

 

        Variable names must start with a letter and continue with letters, digits, or an underscore.  Upper and lower case characters are distinct.

        By convention, variable names are entirely in lower case, except symbolic names which are entirely in upper case.

        The first 31 characters of the variable name are significant.  More may be used, but if variations occur after the first 31 identical characters, the results will depend on the compiler.

        Older compilers may have only allowed as few as 8 significant characters.

        External names (function names and external variable names) should contain only 6 significant characters.

        C reserves the keywords listed on the facing page.  They may not be used as variable names.

        External names should not duplicate the names of desired external variable names, library functions, or names that are special to the linker.  Avoid names like delete, end, getchar, printf, system, or any function names found in the documentation.


size1.c

 

/* SIZE1.C: Find out how much storage (in bytes)

   is allocated for variables of each type */

#include <stdio.h>

 

main ()

{

     int a, b, c, d, e, f;

 

     a = sizeof (short);

     printf ("short integers occupy %d bytes\n",a);

 

     b = sizeof (int);

     printf ("      integers occupy %d bytes\n",b);

 

     c = sizeof (long);

     printf (" long integers occupy %d bytes\n",c);

 

     d = sizeof (char);

     printf ("    characters occupy %d bytes\n",d);

 

     e = sizeof (float);

     printf ("floating point occupy %d bytes\n",e);

 

     f = sizeof (double);

     printf ("double precis. occupy %d bytes\n",f);

 

     return 0;

}

 


FUNDAMENTAL  DATA  TYPES

 

        C supports the following data types:

char

elements of the machine's character set

short

short integers

int

standard size integers

long

long integers

float

single precision floating point

double

double precision floating point

long double

extended precision floating point

void

an empty set of values

enum

allows arbitrary sets of identifiers to be used as values

 

        In addition, types char, short, int, and long may be declared unsigned or signed,.  Unsigned permits no negative values and twice as many positive values as signed numbers.  By default, short, standard, and long integers are signed.  Characters may default to either.

        Each constant and variable of each type occupies a certain number of bits of memory as follows:

 

Machine:

 

AT&T

DEC

DEC

Honey-

IBM

IBM

Inter-

 

 

 

 

well

 

 

data

TYPE

3B

PDP-11

VAX

6000

370

PC

8/32

 

 

 

 

 

 

 

 

char

 8

 8

 8

 9

 8

 8

 8

short

16

16

16

36

16

16

16

int

32

16

32

36

32

16/32

32

long

32

32

32

36

32

32

32

float

32

32

32

36

32

32

32

double

64

64

64

72

64

32/64

64



Lab  2  for  Part  2

 

 

1.  Re-write the program written in lab 2 for part 1 by writing functions to compute the square and the cube of a double precision number.  Place the function for computing the square in the same file as main, and place the function for computing the cube in a different file by itself.

 

2. HOMEWORK:

          a.  Inventory the machines you have access to, determining which machines have C compilers already installed on them.

          b.  Install whatever C compilers you have access to on whatever machines they will run on.

          c.  Check out these compilers by running, in sequence, hello.c and cpy2.c.

          d.  Run the program size.c on all machines and C compilers combinations that you have access to.  Keep these results for future reference.


Character  Constants

 

 

'A' to 'Z'

Capital letters

'a' to 'z'

Lower case letters

'0' to '9'

Digits

'!', '#', etc.

Special printable characters

'\a'

Alert (bell) character

'\b'

Backspace

'\f'

Form feed (page eject, top of form)

'\n'

Newline (linefeed)

'\r'

Carriage return

'\t'

Tab

'\v'

Vertical tab

'\\'

Backslash

'\?'

Question mark

'\''

Single quote

'\"'

Double quote

'\0'

Null (by convention, a string terminator)

'\014'

The character whose octal code is 014 (in ASCII, a form feed)


NUMERIC  CONSTANTS

 

 

        Integer constants:

123

decimal integers

0173

leading zero indicates octal integers (=123 decimal)

0X7B

leading 0X indicates hexadecimal integers (=123 decimal)

        Long integer constants:

123L

trailing L indicates long integer

0173L

long integer expressed in octal

0X7BL

long integer expressed in hexadecimal

        Unsigned integer constants:

123U

trailing U indicates unsigned integer

0173U

unsigned integer expressed in octal

0X7BU

unsigned integer expressed in hexadecimal

        Unsigned long integer constants

123UL

trailing UL indicates both unsigned and long

0173UL

unsigned, long, in octal

0X7BUL

unsigned, long, in hexidecimal

        Double precision constants:

123.

decimal point or e or E must appear.

.123E3

significant digits before or after decimal point must appear

12300e-2

number may be in standard or scientific notation

        Floating point (single precision) constants:

123.0F

trailing F indicates float

1230E-1F

type float in scientific notation

        Extended double precision constants:

123.L

trailing L with decimal point or e or E


strlen.c

 

/* STRLEN.C: Return length of s:  K&R, page 39 */

#include <stdio.h>

 

int strlen (char s[]);

 

/* test strlen */

main()

{

     printf("%d\n", strlen("Hi there"));

     return 0;

}

 

int strlen(char s[])

{

     int i;

 

     i = 0;

     while (s[i] != '\0')

          ++i;

     return i;

}

 


STRING  AND  LOGICAL  CONSTANTS

 

 

        There is no string data type in C.  String constants are arrays of characters terminated by a null.

"I am a string"

A string

""

The null string

" "

A string with one blank

"x"

Note that "x" is not the same as 'x'

        Since all strings are terminated by nulls, the above function is able to determine string length by marching through memory, starting at the beginning of the string, until a terminating null ('\0') is found.

        There is no logical data type in C.  However, 0 is considered false whenever a truth value is expected, and all non-zero values are considered true.  Whenever a truth value is converted to an integer, true converts to a 1 and false converts to a 0.  Thus, programs using logical values usually include the definitions:

#define FALSE

0

#define TRUE

1

    These definitions are sometimes included in stdio.h.


decdef.c

 

/* DECDEF.C: Demonstrate differences between definitions

   (DEF) and declarations (dec) */

#include <stdio.h>      /* dec */

 

int addone (int);       /* dec */

 

main()             /* DEF of function main */

{

     int x;             /* DEF */

     int y = 1;         /* DEF */

 

     x = addone(y);

     printf ("%d\n", x);

     return 0;

}

 

int addone (int i) /* DEF of function addone */

{

     ++i;

     return i;

}

 


DECLARATIONS AND DEFINITIONS

 

        DEFINITION - specifies type, allocates storage, (optionally) initializes value, may occur only once ever.

                   All variables and functions must be defined explicitly.

        DECLARATION - allows type/usage/multi-dimensional array parameters to be known where needed by compiler to generate correct code - occurs in every file or function that must know of declaration.

                   All undeclared functions become int functions.

        The program above contains a function addone to add one to its input parameter, and a function main to test it.

                   Definitions are marked with /* DEF */

                   Declarations are marked with /* dec */


Storage  Class  Summary

 

 

Storage Class:

Auto

Static

External

Register

May be defined at the top of a block

Yes

Yes

No

Yes

May be defined at the top of a file

No

No

Yes

No

Extends throughout block and subblocks

Yes

Yes

Yes

Yes

Extends outside block if declared

No

No

Yes

No

Name exported to the linker

No

No

Yes

No

Initialized before program execution

No

Yes

Yes

No

Initialized at start of block execution

Yes

No

No

N/A

Default initialization

garbage

zero

zero

garbage

Retains value between block executions

No

Yes

Yes

No


STORAGE  CLASSES  FOR  VARIABLES

 

 

        All C variables have a storage class in addition to a data type.

        A variable can be of storage class auto(matic), static, extern(al), or register.

        For variables, storage class determines the scope, lifetime, and initialization of the variable.

        Each variable has a default storage class, which can be overridden by explicitly stating the storage class in front of the data type.

        Variables defined outside of any blocks default to extern storage class

        Variables defined inside of any block default to auto storage class



SCOPE, LIFETIME, INITIALIZATION

 

SCOPE:

          Scope is that part of a program in which the variable is known.

          Auto, static, and register storage class specifiers are permitted only in definitions at the heads of blocks.  Their scope extends throughout the block and all subblocks that do not have another variable defined with the same name.

          External specifiers may appear in definitions at the heads of blocks or at the top level in a file.  Their scope extends throughout the block in which they were defined, and into all blocks that declare it.  Since the variable name is exported to the linker, this includes functions in other files.

LIFETIME:

          Lifetime is that portion of an execution during which a variable has a value.

          Automatic and register variables have a lifetime only for the duration of the block.  When execution passes outside the block that contains them, their values are lost.

          Static and external variables have a lifetime for the duration of the program execution.  Whenever a block is re-entered, the static variables defined within that block contain the same value they had when the block was last exited.  External variables, known to many blocks, carry their values from block to block.

INITIALIZATION:

          Initialization is the process of assigning a value to the variable at the time of its creation.

          If the variable is static or external then the initialization is done only once, before the program starts executing.  Automatic variables are initialized each time the function they are in is called.  Register variables cannot be initialized.


storage.c

 

/* STORAGE.C: Illustrates static and automatic storage

   class for variables. */

#include <stdio.h>

 

void storage (void);

 

main()

{

     int j;

 

     for(j = 0; j < 5; ++j)       /* repeat 5 times */

          storage();

     return 0;

}

 

void storage(void)

{

     static int a;     /* check default initialization */

     static int b = 10; /* check static initialization */

     auto   int c = 20; /* check auto initialization */

            int d = 30; /* check default initialization */

                       /* auto is the default */

 

     printf("static a = %d, b = %d     ", a++, b++);

     printf("auto   c = %d, d = %d\n",    c++, d++);

}

 


INITIALIZATION  OF  VARIABLES

 

 

        Static and external variables are always initialized.  By default, they are initialized to zero.  If variables are to be initialized to non-zero values, they are initialized in the same C statement that defines them.

        Automatic variables for which there is no explicit initializer have undefined (garbage) values.  Each time the function or block they are in is called they begin with garbage values.  If non-garbage values are desired each time the block is entered, the variable must be initialized in the statement that defines them.

        Examples:

         int x =0, y, z=-4;

         char w = '8';

        Static and external variables are initialized once per program just prior to the beginning of execution.

        Automatic variables are initialized once each time the block in which they are defined is entered during execution.


array1.c

 

/* ARRAY1.C: program to demonstrate initialization of

   arrays */

#include <stdio.h>

 

main()

{

     static int y[5] = {6, 8, 10, 12, -14};

     static char greet[] = "hello";

 

     printf ("y(0) = %d,    y(4) = %d\n", y[0], y[4]);

     printf ("greet(1) = %c\n", greet[1]);

     return 0;

}

 


INITIALIZATION  OF  ARRAYS

 

        Only static or external arrays may be initialized (portably).

        The syntax is:

    static type name[] = {iv, iv, iv, ... , iv};

          or

    extern type name[] = {iv, iv, iv, ... , iv};

          where iv represents an initial value.

        For example, the following two initializations are equivalent:

         static int y[5] = {6, 8, 10, 12, -14};

          or

         static int y[] = {6, 8, 10, 12, -14};

        However, the following two initialization are not the same:

    static char n[] = {'H', 'E', 'L', 'L', 'O'};

          (dimension is 5, if terminating null is desired it must be explicitly added)

    static char n[] = {"HELLO"};/*braces optional*/

          (dimension is 6, terminating null is included in all string constants)


static.c

 

/* STATIC.C: Program to demonstrate static function */

#include <stdio.h>

 

static double f (double);

 

main()

{

     double z;

 

     for (z = 0; z < 10; z++)

          printf ("%f\n", f(z));

     return 0;

}

 

static double f (double x)

{

     /* evaluate x^3 + 2x^2 - 3x + 3 */

     return x * (x * (x + 2.0) - 3.0) + 3.0;

}

 


FUNCTION  STORAGE  CLASS

 

 

        A function can have either a static or external storage class.

        If a function has storage class external then it can be referenced in files other than the one in which it was defined.  If a function has storage class static then it can be referenced only in the file in which it was defined.

        If a function is defined without specifying a storage class, it is external.  A function can be declared static by placing the keyword static in the definition of the function.

        In the example above, the function f was written for the purpose of easing the evaluation of a frequently used mathematical function within the file.  However, it may be undesirable for this function to be available to any other function besides main.  The static storage class ensures this "security."


math.c

 

/* MATH.C: Program to illustrate associative problems with

   addition */

#include <stdio.h>

 

main()

{

     double a, b, c, x, y, z;

 

     x = 1.2e+30;

     y = -1.2e+30;

     z = 2.3;

 

     a = x + y;         // should be zero

     b = x + z;         // 1.2e+30 to 28 sig. digits

     c = y + z;         // -1.2e+30 to 28 sig. digits

 

     printf("%f  %f  %f\n", a+z, b+y, c+x);

     printf("%f  %f  %f\n", x+y+z, x+z+y, y+z+x);

     return 0;

}

 


ARITHMETIC OPERATIONS

 

 

+       Adds two numbers, but A + B means add A to B or add B to A.  So A + B + C may add A and B first, or B and C first.  Use of parentheses won't help!

-       Subtracts two numbers.

*       Multiplies two numbers.  As in addition, A * B means multiply A by B or multiply B by A.

/       Divides two numbers.  If both are of type int, it performs an integer division, truncating the result.

%       Computes the remainder from integer division.  This operator doesn't apply to float or double types.

unary -        Changes the sign of the operand following it.


getline.c

 

/* GETLINE.C: Separately compiled routine to fetch a line

   - stdio.h is included because the routine does I/O */

#include <stdio.h>

 

/* the file needs no define or external statements */

 

/* Get input line into array s.  The function getline

returns the length of the input line, including a newline

character, if present.  The array s is terminated with an

ascii null */

 

int getline(char s[], int lim)

{

     int c, i = 0;

 

     while( i<lim-1 && (c=getchar())!=EOF  &&  c!='\n')

          s[i++] = c;

 

     if (c == '\n')

          s[i++] = c;

 

     s[i] = '\0';

     return(i);

}

 


RELATIONAL AND LOGICAL OPERATORS

 

>       Greater than

>=     Greater than or equal to

<       Less than

<=     Less than or equal to

==     Equal to

!=     Not equal to

&&     Logical and (guaranteed to stop left-to-right as soon as first false in a set of conjoined expressions is found)

||     Logical or (guaranteed to stop left-to-right as soon as first true in a set of disjoined expressions is found)

        In the  example above, the statement

    while ( i < lim-1  &&  (c=getchar()) != EOF

                                   &&  c != '\n')

             s[i++] = c;

          will check for array space first, and then, only if there is space, read a character.


Type Conversion Examples

 

 

        Conversions are done across assignment.  Note that:

         int i;

         char c;

         i = c;

         c = i;

          is guaranteed not to change valid characters.

 

        Values are widened, rounded, or truncated as necessary.

 

         int i;

         double x;

         i = x;

          truncates the value stored in x (2.9 becomes 2, -3.6 becomes -3).

 

         float a;

         double x;

         a = x;

          rounds the value of x to the nearest value that can be stored in a.

 

         long w;

         int i;

         i = w;

          drops off the high-order bits in the representation of w.


TYPE CONVERSION

 

 

        Expressions that don't make sense are disallowed.

                   e.g.  floating point subscripts

        Expressions that might lose information draw warnings.

        Conversions that widen one operand to make both operands of the same type are done automatically.

        Characters are converted to integers, using a machine-dependent conversion routine.  (Usually, they just allow the bit pattern representing the character to be interpreted as a binary number.)  Standard characters are guaranteed to produce positive integers, but arbitrary patterns stored in character variables may not yield positive integers.

        The following rules apply to type conversion in simple cases (no unsigned operands).

          If either operand is long double, convert the other to long double.

          Otherwise, if either operand is double, convert the other to double.

          Otherwise, if either operand is float, convert the other to float.

          Otherwise, if either operand is long, convert the other to long.

          Otherwise, (must have char, short, and/or int), convert to int.

        Function arguments are always considered an expression.  Thus function calls do not pass values of type character or short integer.


TYPE CASTING

 

 

        To create any type conversion necessary prior to performing an operation, making an assignment, or invoking a function, an expression may be cast into another type.  This is done by placing the desired type name in parentheses in front of the variable or expression.For example:

         int n;

         sqrt ((double)n);

          invokes the square root math function with a valid type, a double precision value that is the double precision equivalent of the integer stored in n.

         float x[10], y, z;

         y = x[(int)z];

          is a legal construction, since the floating point variable z is first cast into an integer type (through truncation) before being used as a subscript.

        Use of casts is much better than relying on defaults.  It documents what is being done and shows that the type conversions being done were intended.


INCREMENT AND DECREMENT OPERATORS

 

 

        The increment operator ++ adds one to its operand.  The decrement operator -- subtracts one from its operand.

        There are two ways to use the operator, prefix (before the operand) and postfix (after the operand).  The prefix operator increments the operand before using it.  The postfix operator increments the operand after using it.

        Example:

         int x, n;

         n = 5;

         x = n++;

          sets x to 5, but

         int x, n;

         n = 5;

         x = ++n;

          sets x to 6.  In both cases, n becomes 6.

        Increment and decrement apply only to variables, not expressions.


getbits.c

 

/* GETBITS.C: Function to extract bits */

/*   Extract n bits

     from x

     starting at bit position p

     where least significant bit is numbered bit 0 */

 

unsigned getbits(unsigned x, unsigned p, unsigned n)

{

     return((x >> (p+1-n)) & ~(~0 << n));

}

 

 

 

 

getbits1.c

 

/* GETBITS1.C: Function to extract bits */

/*   Extract n bits

     from x

     starting at bit position p

     where least significant bit is numbered bit 0 */

 

unsigned getbits1(x, p, n)

unsigned x, p, n;

{

     return((x >> (p+1-n)) & ~(~0 << n));

}

 


BIT  MANIPULATION

 

        C is capable of doing the following bit manipulation

&       bitwise AND (logical product)

|       bitwise inclusive OR (selective set)

^       bitwise exclusive OR (XOR) (selective complement)

<<     left shift (zero fill on the right - 2's complement multiplication by powers of 2)

>>     right shift: for unsigned, zero fill on the left (both a logical shift and an unsigned divide by powers of 2.  For signed, it may do a sign extension or zero fill on the left.

~       one's complement (independent of word length)

        The  function getbits(x, p, n) returns a right-adjusted n-bit field of x that begins in position p.  (p is the position of the leftmost bit of the extracted field.)  The least significant bit, by convention, is bit 0.

        The function getbits1(x, p, n) is identical to getbits, but uses the pre-ANSI type definition for parameters.  This function does not have a "function prototype."


assign.c

 

/* ASSIGN.C: Program to illustrate the assignment

   operator */

#include <stdio.h>

 

main()

{

     double x = 2.0, y = 3.0, z = 8.0;

 

     x += 5.0;

     y *= 12.0;

     z /= 2.0;

 

     printf("x = %f, y = %f, z = %f\n", x, y, z);

     return 0;

}

 

 

 

RESULTS OF ASSIGN.C

 

 

x =

y =

z =


ASSIGNMENT OPERATORS

 

 

        Expressions such as

         i = i + 2;

          in which the left hand side is repeated on the right can be written in the compressed form

         i += 2;

          using the assignment operator += .

        The following binary operators have a corresponding assignment operator op=, where op is one of the following:

    +   -   *   /   %   <<  >>  &   ^   |

        If e1 and e2 are expressions and op is one of the above, then

                   e1 op= e2

          is equivalent to

                   e1 = e1 op e2

        For example,

         x /= 2

is equivalent to

         x = x/2


condit1.c

 

/* CONDIT1.C: Example of the conditional operator. */

#include <stdio.h>

 

main()

{

     int x = 10, y;

 

     y = x > 2 ? 3 : 5;

 

     printf("x = %d, y = %d\n", x, y);

     return 0;

}

 

 

 

condit2.c

 

/* CONDIT2.C: Program introduces the preprocessor

   directive to define macros */

#include <stdio.h>

 

#define max(a,b)   ( (a) < (b) ) ? b : a

 

main()

{

     int x, y, z;

 

     x = 1;

     y = 5;

     z = 0;

 

     printf("x = %d, y = %d, z = %d\n", x, y, z);

 

     z = max(x,y);

 

     printf("x = %d, y = %d, z = %d\n", x, y, z);

     return 0;

}

 


CONDITIONAL EXPRESSION OPERATOR

 

 

        The conditional expression operator causes the evaluation of one out of two alternative expressions.  The value of the conditional expression operator is the result of the expression evaluated.

        References:  Kochan Pg 88, KR Pg 51.

        The general format of the conditional expression operator is as follows:

                   condition ? expression1 : expression2

        If the result of the evaluation of the condition is TRUE ( non-zero ), then expression1 is evaluated and the result of the evaluation becomes the result of the operation.  If the condition evaluates FALSE ( zero ), then expression2 is evaluated and its value becomes the result of the operation.

        For example:

         int x, y, z;

                             ...

        z = x > 2 ? 3 : 5;

          After the conditional expression is evaluated, z will have a value of 3 or 5, depending on whether x is greater than 2 or less than or equal to 2.

        NOTE: The conditional expression operator is written with two operators ( ? and : ) and three operands.


condit3.c

 

/* CONDIT3.C: Beware of possible side effects when using

   macros */

#include <stdio.h>

 

#define max(a,b)   ( (a) < (b) ) ? b : a

 

main()

{

     int x, y, z;

 

     x = 1;

     y = 5;

     z = 0;

 

     printf("x = %d, y = %d, z = %d\n", x, y, z);

 

     z = max(++x,++y);

 

     printf("x = %d, y = %d, z = %d\n", x, y, z);

     return 0;

}

 

 

 

RESULTS OF CONDIT1.C

x =

y =

 

RESULTS OF CONDIT2.C

x =

y =

z =

x =

y =

z =

 

RESULTS OF CONDIT3.C

x =

y =

z =

x =

y =

z =

%


SIDE EFFECTS IN PARAMETERS

 

 

        The ++ operator increments a variable as a "side effect."  Many function calls also produce results as side effects.  (For example, when calling a function with the name of an array, any modification to the contents of the array is a side effect.)

        Side effects should be avoided in the specification of parameters in a parameter list.  This is because you never know for sure whether a function call is a real function call or whether it will be expanded as a macro.

        In the example program condit3.c, the function max is actually implemented as a macro.  The macro needs to reference the larger of the two parameters twice:  once to make the comparison, and a second time to use its value as the result of the expression.  Any side effect in the parameter list will occur twice for the larger value.

        Moral:  Keep Parameter Lists Simple


PRECEDENCE CHART OF C OPERATORS

OPERATOR

NAME

GROUPING

()

function call

left-to-right

[]

array element

 

.

structure, union member

 

->

structure, union member with pointer

 

!

logical not

right-to-left

~

one's complement

 

-

minus

 

++

increment

 

--

decrement

 

&

address

 

*

indirection

 

(type)

type cast

 

sizeof

size in bytes

 

*

multiplication

left-to-right

/

division

 

%

remainder

 

+

addition

left-to-right

-

subtraction

 

<<

shift left

left-to-right

>>

shift right

 

<

less than

left-to-right

<=

less than or equal

 

>

greater than

 

>=

greater than or equal

 

==

equal

left-to-right

!=

not equal

 

&

bitwise and

left-to-right

^

bitwise exclusive or

left-to-right

|

bitwise or

left-to-right

&&

logical and

left-to-right

||

logical or

left-to-right

?:

conditional

right-to-left

=

assignment operator

right-to-left

+=

assignment replace add

 

-=

assignment replace subtract

 

*=

assignment replace multiply

 

/=

assignment replace divide

 

%=

assignment replace remainder

 

<<=

assignment replace shift left

 

>>=

assignment replace shift right

 

&=

assignment replace and

 

^=

assignment replace exclusive or

 

|=

assignment replace or

 

,

comma

left-to-right


STATEMENTS AND BLOCKS

 

 

        An expression  such as x = 0 or i++ or printf(...) becomes a statement  when it is followed by a semicolon.

         x = 0;

         i++;

         printf(...);

          are now statements.

        Statements can be very long (use a \ at the end of the line to have the C compiler ignore the carriage return if you don't want the white space), or statements can be of zero length (the null statement);;.

        Braces are used to group statements together into a block.  Blocks are syntactically equivalent to a single statement and may be placed anywhere a statement is required or allowed.

        There are two popular formats for using braces to group statements after the while, for, do, if, else statements.  Kernighan and Ritchie use the following format:

         if (i == j) {

             k++;

             printf("Yipes");

         }

          Many C programmers, however, follow the standard

          adopted in these notes:

         if (i == j)

         {

             k++;

             printf("Yipes");

         }

          The second method more readily allows braces to be matched up.  It also provides more white space in the source code in an attempt to further enhance readability.


IF, ELSE, AND ELSE-IF STATEMENTS

 

 

        The simplest form of decision control is of the form

         if (expression)

             statement

          The expression is evaluated; if it is "true" or non- zero, the statement is executed.

        Since the if simply tests the numeric value of an expression,

         if (expression)

          is equivalent to writing

         if (expression != 0)

          Which one is clearer to understand depends on the context.  If the expression already is logical, the first form is preferred.  If the expression is numeric, the second form is probably clearer.

        The more general form of the if statement is:

         if (expression)

             statement-1

         else

             statement-2

          In this form if the expression has a "true" or non-zero value, then statement-1 is executed.  Otherwise, if the expression has a "false" or zero value, then statement-2 is executed.


IF, ELSE, and ELSE IF (continued)

 

        While not a special case, a series of tests is commonly performed in the following fashion:

         if(expression)

             statement-1

         else if(expression)

             statement-2

         else if(expression)

             statement-3

         else

             statement-4

          Only one of the statements is executed.  The first true condition causes execution of the following statement.  If none of the expressions is true, the statement after the final else is executed.

        If statements may be nested, but when there are several active if statements, the first else applies to the closest if.  The unix command cb (C beautifier) will take a source program as input and produce an correctly indented program in K&R style that correctly shows the way the C compiler will interpret the nesting.  The VAX/VMS C compiler program listings provide a nesting indicator in front of each line that shows how many indentations should appear in the source code.


switch1.c

 

/* SWITCH1.C: Illustrates the switch statement */

#include <stdio.h>

 

int test (int);

 

main()

{

     printf("%d\n\n", test(3) );

     printf("%d\n\n", test(4) );

     printf("%d\n\n", test(2) );

     printf("%d\n\n", test(17) );

     return 0;

}

 

int test(int c)

{

     int x;

 

     switch(c)

     {

          case 3:

              printf("in case 3\n");

              x = 10;

              break;

          case 4:

              printf("in case 4\n");

          case 2:

              printf("in case 2\n");

              x = 20;

              break;

          default:

              printf("in default case\n");

              x = 30;

              break;

     }

     return(x);

}

 


SWITCH STATEMENTS

 

 

        The switch statement is a special multi-way decision making structure.  In effect, it is a series of if ... else if ... else if ... else statements that test the same variable against a set of constants.

        The general construction is of the form

         switch (expression)

         {

             case constant-1:

                 statement-1;

                 statement-1a;

             case constant-2:

             case constant-3:

                 statement-23;

                 break;

             default:

                 statement-n;

         }

        The expression must evaluate to an integer at execution.

        The constants, constant-1, constant-2, etc., must be constant expressions known at compile time.


switch2.c

 

/* SWITCH2.C: Count the digits, white space and other

   characters in terminal input using the switch statement

   From KR, page 55 */

#include <stdio.h>

 

main()

{

     int c, i, nwhite = 0, nother = 0, ndigit[10];

     for (i = 0; i < 10; i++)

          ndigit[i] = 0;

     while ( (c=getchar()) != EOF )

          switch (c)

          {

              case '0':

              case '1':

              case '2':

              case '3':

              case '4':

              case '5':

              case '6':

              case '7':

              case '8':

              case '9':

                   ndigit[c-'0']++;

                   break;

              case ' ' :

              case '\n':

              case '\t':

                   nwhite++;

                   break;

              default:

                   nother++;

                   break;

          }

     printf("digits =");

     for (i = 0; i < 10; i++)

          printf(" %d", ndigit[i]);

     printf("\nwhite space = %d, other = %d\n",

                        nwhite,       nother);

     return 0;

}

 


calc.c

 

/* CALC.C: A Simple Calculator */

#include <stdio.h>

#include <stdlib.h>

 

#define PROMPT ':'

 

main()

{

     float a, b;

     char opr;

     float result;

 

     while(putchar(PROMPT),

           scanf("%f%c%f",&a,&opr,&b) !=EOF)

     {

          switch (opr)

          {

              case '+': result = a + b; break;

              case '-': result = a - b; break;

              case '*': result = a * b; break;

              case '/': result = a / b; break;

              default:

                   printf("ERROR ** illegal operator\n");

                   exit(1);

          }

 

          printf("result is %g\n", result);

     }

     return(0);

}

 


Lab 3 for Part 2

 

1.  Write a C program that initializes a character array with a long (more than 136 characters) message.  Have the program print the message on the screen.

 

2.  (Optional, Mathematical) (K&R  Exercise 2-3)  Write a function htoi(s), which converts a string of hexadecimal digits (including an optional 0x or 0X) into its equivalent integer value.  The allowable digits are 0 through 9, a through f, and A through F.


JUMPS

 

 

        A principal problem in writing good source code is in maintaining discipline over the flow of control within the program.  Undisciplined use of GO TO statements in most languages and the lack of alternative control structures render most languages unsuitable for large software development projects.  C takes a middle ground, providing adequate control structures, and controlled jumps, but still possessing a GO TO statement.

        The break statement causes termination of the smallest enclosing while, do, for, or switch statement.   Control passes outside the control structure to the following statement.

        The continue statement causes control to pass to the loop continuation portion of the smallest enclosing while, do, or for statement.  Control remains within the control structure as long as the terminating condition has not been met.

        The return statement causes control to pass back to the calling function.

        The goto statement causes control to pass to a labelled statement.  Generally, its use should be relegated to catastrophic error recovery while embedded in many control structures.

        The exit function causes control to return to _main (essentially, back to the operating system).  exit(0) is considered a successful termination.


array.c

 

/* ARRAY.C: Demo of defining a two dimensional array and

   passing it to a function. */

#include <stdio.h>

 

int trace(int [][4]);

 

main()

{

     static int x[4][4] = { {1,  2,  3,  4},

                           {5,  6,  7,  8},

                           {9, 10, 11, 12},

                           {13, 14, 15, 16} };

 

     printf("sum of diagonal elements = %d\n", trace(x));

     return 0;

}

 

int trace(int z[][4])

     /* return sum of diagonal elements */

{

     return( z[0][0] + z[1][1] + z[2][2] + z[3][3] );

}

 


MULTI-DIMENSIONAL  ARRAYS

 

 

        The C language allows arrays of any dimension to be defined.  The definition

         int x[2][3];

          defines x to be a 2 x 3 array of integers.  (Actually, it is an array of 2 arrays of size 3.)  As in the case of one-dimensional arrays, the indices start from 0.  Therefore, the six integer elements of the array x defined above are:

    x[0][0]  x[0][1]  x[0][2]

    x[1][0]  x[1][1]  x[1][2]

        The elements of x are stored in the order listed above with the last index changing most rapidly.

        If f is a function returning an integer that takes x as an argument ( f(x) ) then f must be declared as:

                   int f(int z[2][3]);   or

                   int f(int z[][3]);    or

                   int f(int (*z)[3]);

        Two-dimensional arrays are initialized in a similar way to one-dimensional arrays.

         static double b[2][3] = { {1.0, 2.3, 3.6},

                               {4.2, 5.4, -6.3} };

          or

         static double b[2][3] =

                 {1.0, 2.3, 3.6, 4.2, 5.4, -6.3};

          Both ways give the same result, although the former is preferred from a readability and defensive programming point of view.

        Program array.c cannot be modified using the techniques it employs to sum the diagonal of a square matrix of arbitrary size.



Lab 4 for Part 2

 

 

1.  Implement a "function" to calculate the minimum of two numbers as a macro.  Use the conditional expression operator.  Test the macro using expressions as well as simple constants and variables.  Now remove the macro and replace it by an appropriate function.  Run the revised implementation through the same tests using the same driving code.

 

2.  Modify the programs written in lab 2 for part 1 and lab 2 for part 2 so that the squaring and cubing of numbers is implemented via a macro.

 

3. (Tricky) (K&R Exercise 4-14, page 91)  Define a macro swap(t, x, y) that interchanges two arguments of type t.


Supplemental Exercises for Part 2

 

 

1.  Write a C program of your choice with main in one file, and with a function that main calls in another file.  The function in the second file that main calls should in turn call a helping function also located in the second file.  Apply an extern declaration on the function main calls, but apply a static declaration on the helping function in the second file.  Compile and test the program.  After the program is running, attempt to also call the helping function from main, observing the error messages.  (If you need a hint, look in the program static2.c.)

 

2.  Write a function expand(s,t) which, in the process of copying string s to string t, converts newlines and tabs into visible escape sequences like \n and \t as it copies the string s to the string t.  Use a switch.  Test the function with an appropriate main driving function.  See K&R, page 60, exercise 3-2.

 

3.  Modify the program calc.c to accept a fifth operator, P, as the power operator.  For example, 3P4 would be 3 to the fourth power, or 81.  Use the pow function in the math library.