Large Program Development
Prof. Brian D. Davison
Computer Science & Engineering, Lehigh University
Announcements
- Reminder: ACM is offering a session on LaTeX today at 5pm -- this is a
topic that I expect we will examine later this semester.
- Today's schedule
- Return Short Quiz #4
- Any review questions for Exam #1
- Start Large Program Development
Large Program Development
- When you are facing the development of a large program, you need to organize how you will proceed. You might
- Focus on top-down development, in which you write a simple main() which calls other (initially abstract) functions, and then implement them which call yet more functions. During this process, you might implement the functions as stubs, so that the whole thing can be compiled and some simple testing performed.
- Focus on bottom-up development, in which you identify some key low-level needs (e.g., reading/writing data) and build functions to provide such services.
Ideally, at the same time, you would also write testing code to exercise your functions and verify their correctness.
- Use a divide-and-conquer strategy, especially when coding in groups. Might just share object files with each other (along with documentation on how to use your code).
- Typically you might use a combination of the above.
Multiple Source Files
- In all of the above approaches, you'll want or need to use multiple source files
- Already know how to compile them...
- Easy enough when you just want to call functions in other files
- Need more if you want to have shared state across multiple files
Sharing State
- A global variable isn't really global -- it's only accessible by those functions in the same source file!
- In order for another source file to be able to access a global declared elsewhere, it must declare that variable to be external:
extern int i;
which essentially says this is a variable with the given type but is actually declared elsewhere (to be resolved at link time).
So any other file in a project can access this variable if they know
the name, and declare it with extern.
Preventing access is possible
- If the keyword static is added to the global declaration
static int i;
then no other file can access this variable, even when using extern.
- The same approach can be used for functions (to prevent them from being called by functions in other source files).
Let's demonstrate this...
The Preprocessor
- When dealing with multiple files, we saw the need for one or more header
files to define function signatures as well as structs, global variables,
typedefs, etc.
- The header file is loaded using a preprocessor directive #include
which loads the specified file
- Local header files have quotes around the name, and are relative to the current directory
- System header files have <> around the file name, and are found in a system-defined location (e.g., /usr/include/)
- The C preprocessor supports many other directives.
Defining constants and macros
- #define lets you create constants and macros
#define PI 3.141592654
#define TIMES *
double area(double r) {
return PI TIMES r TIMES r;
}
- They can be more complex with arguments
#define area(x) (PI * x * x)
but this form won't work for area(v + 1) as the compiler will see
it as (PI * v + 1 * v + 1). A better macro would be
#define area(x) (PI * (x) * (x))
so that expected evaluation order is preserved.
Macros are faster than functions (no stack manipulations) but may use more space (since each function call is expanded in place).
Conditional Compilation
- The C preprocessor can also be used to "turn on" or "turn off" sections of code depending on whether something has been #define'd
#ifdef IS_WINDOWS
/* do something backwards */
....
# else
/* do regular code */
....
#endif
#ifndef allows for the reverse test. Commonly used to prevent header files from being loaded more than once.
/* local.h -- local function definitions, variables, types, etc. */
#ifndef LOCAL_LOADED
#define LOCAL_LOADED
#include <stdio.h>
#define PI the_whole_thing
int eat(int food);
#endif
Common use of conditionals
- Conditional compilation is often used in debugging, and for platform specific code
...
#ifdef DEBUGGING
printf("Code reached point 14; x=%d, y=%f\n", x, y);
#endif
Symbols can also be defined at compile time, not just in #define statements
gcc -D DEBUGGING mycode.c
Other conditions
- The preprocessor can also check the value of a constant expression
#if DEBUG_LEVEL == 1 /* show minimal debugging */
...
#elif DEBUG_LEVEL == 2 /* show more */
...
#elif DEBUG_LEVEL == 9 /* show max */
...
#else /* unsupported debugging level */
...
#endif