Programming in C Miscellaneous Topics

advertisement
Programming in C
Miscellaneous Topics
Fixed 2-d Arrays
• Number of rows and columns fixed at
compile time
int gameBoard [ 5 ] [ 3 ];
– 5 rows, 3 columns
– Use 2-d indexing
int x = gameBoard[ row ][ col ];
7/28/09
(0,0)
(0,1)
(0,2)
(1,0)
(1,1)
(1,2)
(2,0)
(2,1)
(2,2)
(3,0)
(3,1)
(3,2)
(4,0)
(4,1)
(4,2)
Dynamic 2-d Arrays (1)
• Number of rows fixed at compile time, but variable
number of columns in each row
• Really an array of int*, where each int* points to
the row.
• Indexing with row / column still ok
– 3 rows, each dynamically allocated with (possibly) different
number of columns (row sizes)
int *gameBoard[ 3 ];
– The rows, possibly different sizes
gameBoard[ 0 ] = (int *)malloc( 4 * sizeof( int ));
gameBoard[ 1 ] = (int *)malloc( 6 * sizeof( int ));
gameBoard[ 2 ] = (int *)malloc( 8 * sizeof( int ));
7/28/09
Dynamic 2-d Arrays (2)
• Number of rows and columns dynamically allocated.
• Indexing with row / column still ok
– A pointer to the (first element of the) array of row pointers
int **gameBoard;
– The array of row pointers
gameBoard = (int **)malloc( 3 * sizeof( int *));
– The rows, possibly different sizes
gameBoard[ 0 ] = (int *)malloc( 4 * sizeof( int ));
gameBoard[ 1 ] = (int *)malloc( 6 * sizeof( int ));
gameBoard[ 2 ] = (int *)malloc( 8 * sizeof( int ));
7/28/09
Compiler directives
Compiler directives begin with # and are interpreted
by the preprocessor
The most common directives are
– #include
• Used for file inclusion (See K&R 4.11.1)
– #define
• Used for constant/macro definitions (see K&R 4.11.2)
– #if, #ifdef, #ifndef, #else, #elif, #endif
• Used for conditional compilation (see K&R 4.11.3)
7/28/09
#include
The #include directive is used to include the contents of another file
into the file being compiled. It is usually used to include header
(.h) files into the source. #include files may be nested so that
one .h file may #include another .h file
A line of the form
#include <filename>
causes replacement of that line by the entire contents of the file
filename. The named file is searched for in implementation
specific places, usually /usr/include on Linux
Similarly, a line of the form
#include “filename”
first searches in the same directory as the source file, and if that
search fails, continues searching as above.
7/28/09
#define
A line of the form
#define identifier token-sequence
Causes the preprocessor to replace all occurrences
of identifier with the token-sequence (except
in quoted strings). Note that this is a text
replacement.
#defines are most commonly used to give names
to “magic constants”
For example
#define MAXSIZE 100
replaces every occurrence of MAXSIZE with 100 as
in int table[MAXSIZE];
7/28/09
#define macros
The #define directive may also be used to create a simple macro.
Care must be taken when writing macros and when calling macros because
the arguments are copied literally.
For example, the simple macro below doesn’t work properly in all instances.
#define SQUARE(x) x * x
Assuming int z = 4, what is the value of y after the assignment
int y = SQUARE( z - 1 );
Even when written correctly as
#define SQUARE(x) (x) * (x)
Calling the macro may result in erroneous side effects.
Assuming again that int z = 4, what is the value of z after the assignment
int y = SQUARE( ++z )
Nonetheless, macros like this can make code easier to read as we’ll see later
7/28/09
Importing #defines
• The most common way to #define symbols is
within your .c file
– #define DEBUG
– #define MAX 100
• It is also possible to “import” #defines on the
compiler command line
– gcc -o myProg -DDEBUG -DMAX=100 myProg.c
• You cannot use the command line to redefine a
symbol already defined in your .c file
7/28/09
Predefined Constants
• The C pre-processor predefines several #define
constants
__FILE__ is the name of the file being compiled
__LINE__ is the current line number being
compiled
• These may be helpful if printed as part of debug
output
printf(“Bad Input: %s, %d\n”, __FILE__, __LINE__);
7/28/09
assert( )
• The C library provides a macro named assert( ) which
can be very helpful for debugging
• The parameter to assert is any boolean expression -assert( expression );
– If the boolean expression is true, nothing happens and
execution continues on the next line
– If the boolean expression is false, a message is printed to
stderr and your program terminates
• To use assert( ), you must #include <assert.h>
• assert( ) may be disabled by
#define NDEBUG prior to #include <assert.h>
• Or by importing NDEBUG via the command line
7/28/09
Using assert( )
• Use assert( ) to verify an array index
int scores[100];
int k = <some complicated calculation >
assert( k >= 0 && k < 1000 );
• Use assert( ) to check for NULL pointers
int *ptr = malloc( 4 * sizeof( int ) );
assert( ptr != NULL);
• Use assert( ) to check functions that
shouldn’t fail
int errorCode = foo ( );
assert( errorCode == 0); /* but NOT assert(foo( ) == 0); */
7/28/09
Conditional Compilation
•
The preprocessor directives #ifdef (read as “if defined”), #ifndef
(read as “if not defined”), #if, #else, #elif (read as “else if”), and
#endif (together with #defines) can be used to control which lines
in a course file are compiled and which are not. Each directive
appears on a separate line.
•
Each #if and subsequent #elif is evaluated as true (1) or false (0)
based on the definition (or lack of definition) of the identifier that
follows. Lines that follow a false (0) evaluation are skipped.
•
Conditional compilation is frequently used to avoid multiple
inclusions of the same header (.h) file, compiling lines used for
debugging, and (in system level .h files) to compile certain lines
based on the type of system on which the program is being
compiled.
7/28/09
Conditional Compilation
for header files
Header (.h) files should only be included once in a source file to avoid
compilation errors. Suppose the file myheader.h is #included into some
source file. The .h files should be “guarded” as follows
#ifndef MYHEADER_H
#define MYHEADER_H
....
...
#endif
/* same as #if !defined(MYHEADER_H) */
How does this work?
Whenever the compiler includes myheader.h, the #ifndef checks to see if
the identifier MYHEADER_H has be #defined. The first time myheader.h is
included, MYHEADER_H will not have been defined so the lines in the
header file (in particular #define MYHEADER_H) will be included into the
source file. Thereafter, when the compiler subsequently tries to include
myheader.h, the #ifndef will evaluate to false (because MYHEADER_H is
now defined) and all lines in myheader.h will be skipped, avoiding
duplicate inclusion.
7/28/09
Conditional Compilation for
Debugging
One simple way of debugging a program is to insert
printf( ) statements are strategic points in the
code. When the program is run, the output of
these printf( ) statements help you debug your
program. When the program is bug free, these
printf( ) statements are removed from your
code.
Rather than removing the printf( ) statements by
editting your code, conditional compilation can
be used to allow or prevent them from being
executed.
7/28/09
debug.c
#ifdef DEBUG_ON
#define DEBUG(x) printf(x)
#else
#define DEBUG(x)
#endif
int main (int argc, char *argv[] )
{
int i, count, sum = 0;
assert( arcg == 2);
DEBUG( "Command Line arg is: %s\n“, argv[1]);
count = atoi( argv[1] );
DEBUG( "Entering for loop\n");
for (i = 0; i < count; i++)
{
sum += i;
}
DEBUG ("exited for loop\n");
printf("sum = %d\n", sum);
return 0;
}
Program organization
• main( ) is generally defined in its own .c file and generally
just calls helper functions
• Program-specific helper functions in another .c file
– If there are very few helpers, they can be in the same file as
main( )
• Reusable functions in their own .c file
– Group related functions in the same file
• Each struct or union
– Defined in a separate .h file
– Related functions in a separate .c file
– File names should be indicative of the struct name
7/28/09
Variable Scope and Lifetime
• The scope of a variable refers to that
part of a program that may refer to
the variable.
• The lifetime of a variable refers to the
time in which a variable occupies a
place in memory
• The scope and lifetime of a variable
are determined by how and where
the variable is defined
7/28/09
Global Variables
• Global (external) variables are defined outside of any
function, near the top of your .c file.
– May be used anywhere in the .c file in which they are
defined.
– Exist for the duration of your program
– May be used by any other .c file in your program that
declares them as “extern” (unless also defined as static)
– Static global variables may only be used in the .c file that
declares them
– “extern” declarations for global variables should be placed
into a header file
7/28/09
Local variables
• Local variables are defined within the
opening and closing braces of a function,
loop, if-statement, etc. Function
parameters are local to the function.
– Are usable only within the braces in which
they are defined
– Exist only during the execution of the block
unless also defined as static
– Static local variables retain their values for the
duration of your program. Usually used in
functions, they retain their values between
calls to the function.
7/28/09
Function Scope
• All functions are external because C does not
allow nesting of function definitions.
– So no “extern” declaration is needed
– All functions may be called from any .c file in your
program unless they are also declared as static.
• Static functions may only be used within the .c file
in which they are defined
7/28/09
variableScope.c - part 1
#include <stdio.h>
// extern definition of randomInt and prototype for getRandomInt
#include “randomInt.h”
/* a global variable that can only be used
by functions in this .c file */
static int inputValue;
/* a function that can only be called by other functions
in this .c file */
static void inputPositiveInt( char *prompt )
{
/* init to invalid value to enter while loop */
inputValue = -1;
while (inputValue <= 0)
{
printf( "%s", prompt);
scanf( "%d", &inputValue);
}
}
7/28/09
variableScope.c - part 2
/* main is the entry point for all programs */
int main( )
{
/* local/automatic variables that can only be used in this
function and that are destroyed when the function ends
*/
int i, maxValue, nrValues;
inputPositiveInt("Input max random value to generate: ");
maxValue = inputValue;
inputPositiveInt("Input number of random ints to generate: ");
nrValues = inputValue;
for (i = 0; i < nrValues; i++)
{
getRandomInt( maxValue );
printf( “%d: %d\n", i + 1, randomInt );
}
return 0;
/* successful completion */
}
7/28/09
randomInt.c
/*
**
**
**
a global variable to be used by code in other .c files.
This variable exists until the program ends
Other .c files must declare this variable as "extern"
holds the random number that was generated */
int randomInt;
/* a function that can be called from any other function
** returns a random integer from 1 to max, inclusive */
void getRandomInt( int max )
{
/* a local variable that can only be used inside this function,
** but persists between calls to this function */
static long lastRandom = 100001;
lastRandom = (lastRandom * 125) % 2796203;
randomInt = (lastRandom % max) + 1;
}
7/28/09
randomInt.h
#ifndef RANDOMINT_H
#define RANDOMINT_H
// global variable in randomint.c
// set by calling getRandomInt( )
extern int randomInt;
// prototypes for function in randomInt.c
void getRandomInt(int max );
#endif
7/28/09
Download