Hi,
I've recently been *fascinated* about breaking C programs into
functions and what characteristics make up a good function. K&R 2nd
Ed. have a somewhat limited number of programs also briefly written in
pseudocode and Steve Summit's site has a few notes about the role of
functions. While I'm mostly hooked onto function design, I've also got
an interest in the reasoning behind global variables in C programs.
Can anyone point me to any other resources that have information about
the above interests?
TIA
Albert
No links, but I'll offer my not-so-humble opinions.
Good function design attempts to satisfy the following guidelines:
1. A function should perform a single, well-defined task. This task
may be broken down into smaller subtasks (each with its own
function). For example, sorting an array is a single, well-defined
task; sorting an array and opening a network connection are two
unrelated tasks that don't belong in the same function.
2. A function should be reusable, both within the same program and
among different programs. For example, look at the C standard
library. You can reuse functions like printf() or malloc() to your
heart's content; they are not bound to a specific program.
3. A function should isolate its implementation details from the
larger program. This helps make the function easier to reuse as well
as protecting against introducing bugs in the larger program by
editing the function. In other words, if when your main program calls
a function to sort an array, it doesn't care *how* the array gets
sorted, just that it's sorted in the correct order. You should be
able to change the sorting logic without affecting the larger program
at all.
4. Expanding on 3, a function should communicate with its caller
through its parameter list and return value *only*; it should not rely
on global variables to communicate with the caller. This exposes
implementation details to the caller, as well as making the function
hard to reuse.
Global variables should be used very sparingly. There are some
situations where they're the right solution, but more often than not a
better solution is available. They're typically used to persist some
state information between function calls, where that information is
not being stored in the calling program.
For a very contrived example, imagine a module that implements a stack
using an array for the stack contents and an integer for the stack
pointer. The stack manipulation functions (push and pop) need to
maintain the values of the stack and stack pointer between calls, but
that information is not being saved by the main program (since that's
an implementation detail, and we want to hide implementation details
from the main program). So you can define two file-scope (global)
variables that are visible to the push and pop functions, like so:
/* stack.c */
#define STACK_SIZE ...
/**
* The static keyword limits the visibility of the variables
* to this specific source file
*/
static int stack_ptr;
static int stack_data[STACK_SIZE];
void init(void)
{
stack_ptr = STACK_SIZE;
}
int push(int data)
{
int ret = 1;
if (stack_ptr > 0)
stack_data[--stack_ptr] = data;
else
ret = 0; /* stack overflow */
return ret;
}
int pop(int *data)
{
int ret = 1;
if (stack_ptr < STACK_SIZE)
*data = stack_data[stack_ptr++];
else
ret = 0; /* stack underflow */
return ret;
}
/* stack.h */
extern void init(void);
extern int push(int data);
extern int pop(int *data);
Now, there are easily a hundred better ways to implement a stack that
don't rely on global variables and still isolate implementation
details from the calling program (and allow you to create more than
one stack per program): don't take the above code as an example of a
*good* stack design. This is simply an example off the top of my head
of how global variables can be used to maintain state information.