Computational Physics
Bits and pieces
Comments and questions to John Rowe.
Sharing variables between functions
Relying too
heavily on external variables is fraught with peril since it leads to
programs whose data connections are not at all obvious - variables can
be changed in unexpected and even inadvertant ways and the program is
hard to modify.Kernighan & Ritchie.
GLOBAL variables (to be used only if you
really need them)..
Torvalds
Sharing variables between functions in the same source file
Variables defined
outside of a function are called
external variables and
can be used by any function
in the file, provided only that the function definition follows the
variable definition:
static int somenumber;
void myfun1(int i) {
/* myfun1 can now use the variable somenumber */
}
void myfun2(int i) {
/* so can myfun2 */
}
The word
static here means that
somenumber will be shared between functions inside
this source file but not with functions in other source files. We
could quite legally miss out the word
static,
which would allow functions in other files to also access
somenumber.
External variables should be used sparingly
Why? Well, look at the comment by Kernighan &
Ritchie at the top of the page. Contrast that with this:
x = fun1(arg1, arg2);
Here when we call function fun1 we can be confident that
fun1
isn't going to change the values of its arguments. if instead of
using arguments we had made
arg1 and
arg2 external
so the function call was just
x = fun1(); we
wouldn't know if
arg1 and
arg2 had the same values
when
fun1 returned.
If overused external variables tie different routines up in knots. Use
them for situations when all of the below apply:
- They represent quantities which really are global to the whole
scope of the problem (or a large sub-part).
- Sufficiently many functions need them that passing them as
arguments is inconvenient.
- It is logically impossible for there to be more
than one of that thing, not just "I can't imagine we would ever want more than one".
Good uses:
- The first in a linked list of items that are fundamental
to the whole program (as in firstfood below).
- Program-wide options and flags.
External variables are permanent and initialised to zero
External variables have space allocated for them when the program
starts and are never destroyed. They are unlike automatic variables in
the following ways:
- External variables retain
their values between calls to functions.
- External variables are initialised to zero (or NULL for
pointers) when the program starts - other variables have garbage unless
explicitly initialised in the code.
Static variables within a function
Very occasionally you may need to have a variable within a function
keep its value between calls. The
static keyword does this.
As above such variables are permanent and initialised to zero (or
NULL) before the first call to the function.
#include <stdio.h>
void pointless_function(void) {
static int called;
printf("This function has been called %d times before\n", called);
++called;
}
int main(void) {
int ntimes;
printf("How many calls?\n");
scanf("%d", &ntimes);
while(--ntimes >= 0)
pointless_function();
}
Also note that if
pointless_function were to be called
recursively all instances of the function would have the same variable
called.
For both static and external variables, if a variable is explicitly
initialised:
void myfun(void) {
static int foobar = 17;
the variable is intialised when the program starts to run, when
myfun is called the second and subsequent times
foobar has whatever value it had when
myfun last returned.
Class exercise
What's wrong with this? (
stdio.h is where
NULL
is defined by the way).
#include <stdio.h>
/*
* Accept a number in the range 1-12 and return the name of
* the month
*/
char *month_name(int month) {
char *months[] = { "January", "February", "March", "April",
"May", "June", "July", "August",
"September", "October", "November", "December" };
if (month > 0 && month <= 12)
return months[month - 1];
else
return NULL;
}
Sharing variables between functions in different source files
As mentioned above, if we had missed out the word
static we could have shared
somenumber between functions in different files. The steps are:
- Tell each file that needs to know about it.
Here is a typical include file which we have called mydefs.h
#define MAXLEN 256
typedef struct food {
char name[MAXLEN];
float calories;
struct food *next;
} Food;
void new_food(void);
void calculate_calories(void);
extern Food *firstfood;
The extern keyword is one we haven't met before, it says
"some other file has a variable called firstfood
and we are going to share it". We will include mydefs.h
in the c files.
- Have the variable defined for real in one file only.
#include "mydefs.h"
#include <stdio.h>
Food * firstfood;
int main(void) {
int choice = 1;
while(choice) {
printf("\nChoice?\n\n"
"0. Quit\n"
"1. Add new food\n"
"2. Calculate calories\n");
scanf("%d", &choice);
switch(choice) {
case 0:
break;
case 1:
new_food();
break;
case 2:
calculate_calories();
break;
default:
fprintf(stderr, "Input out of range\n");
}
}
return 0;
}
firstfood is a non-static external variable
(one that can be shared with other files) as opposed to
somenumber above which was a static external
variable (one that cannot be shared with
other files).
The main function also shows something you may not have seen
before: when two or more strings follow each other separated by white
space ("First string" "second string") C just joins
them together to form a single string
("First stringsecond string" - note it didn't put a
space in there).
- Have other files refer to the variable.
#include "mydefs.h"
#include <stdlib.h>>
#include <stdio.h>>
void new_food(void) {
char *name;
Food *new = xmalloc(sizeof *new);
printf("Name of food?\n");
scanf("%255s", new->name);
printf("Calories per gramme?\n");
scanf("%f", &new->calories);
new->next = firstfood;
firstfood = new;
}
void calculate_calories(void) {
Food *tmp;
float calories = 0, grammes;
for(tmp = firstfood; tmp != NULL; tmp = tmp->next) {
printf("How many grammes of %s?\n", tmp->name);
scanf("%f", &grammes);
calories += grammes * tmp->calories;
}
printf("Your food contains %.2f calories\n", calories);
}
Notes
- firstfood was defined properly (ie without an
extern) just once, outside of any function
and without a static modifier. We chose the file with the
main function but it could have been any file.
- Other files could then declare firstfood as
extern. We chose to do it via mydefs.h but we could
have manually typed the statement in the file or even put the
statement in individual functions. (Using an include file is the most
common way of declaring external variables.)
- Eagle-eyed students may have noticed that firstfood
ended up being declared twice in the main file: as an external
variable via mydefs.h and then defined properly (without the
extern) in the actual file. This isn't a problem - C allows
this to make it easier to use include files.
Arrays
can be initialised when the function starts up
int
myfun() {
float myarray[3] = {1.0, 17.3, -9.4};
Here you see an array that has been given some initial values. Every
time
myfun is called the array will start off with those
values.
There are two useful variations on this:
Arrays can have their sizes determined from their
initialisation
The above fragment could just as easily have been written:
int
myfun() {
float myarray[] = {1.0, 17.3, -9.4};
Notice how the compiler has worked out the size of the array from the
number of elements.
Arrays
can have some of their elements initialised to zero
C has a handy feature whereby if an array has its size specified
and the number of initialisers given is less than the number required
the rest are initialised to zero:
#define N 200
main() {
float myarray[N] = { 17.3 }; /* The rest of the array is zero */
For arrays of pointers the unitialised elements are
NULL.
Notice that if we had missed out the initialisation altogether the
array
myarray would have been filled with garbage:
#define N 200
main() {
float myarray[N]; /* myarray has random values */
Bitwise operators
It's unlikely you will have a lot of use for bitwise operators
(operators that operate on the individual bits of a variable)
but if you do, here they are:
Operator | Description |
& | Bitwise and (both bits are one) |
| | Bitwise or (either or both bits are one) |
^ | Bitwise exclusive or (just one bit is one) |
<< | Left shift (muliplies by power of 2) |
>> | Right shift (divides by power of 2) |
~ | One's complement |
NB the One's complement operator takes a single
argument, the others two.
Examples
We take the example of an unsigned char (one byte,
ie 8 bits). Curiously there is no way to write numbers as binary in
C; the nearest is hexadecimal, but for clarity we shall show the calculation
in binary.
Expression | Calculation |
00110011 & 11110000 |
00110011 11110000 --------
00110000 |
00110011 | 11110000 |
00110011 11110000 -------- 11110011 |
00110011 ^ 11110000 |
00110011 11110000 -------- 11000011 |
10110011 << 2 |
10110011 -------- 11001100 |
11110000 >> 3 |
11110000 -------- 00011110 |
~11110000 |
11110000 -------- 00001111 |
Binary flags
The most common use of binary operators is to define
flags, ie options that are either on or off.
Typically there's a header file with various constants
each of which is an exact power of 2 (ie has only one bit set):
#define OPTION1 0x01 /* 1 binary 00000001 */
#define OPTION2 0x02 /* 2 binary 00000010 */
#define OPTION3 0x04 /* 4 binary 00000100 */
#define OPTION4 0x08 /* 8 binary 00001000 */
#define OPTION5 0x10 /* 16 binary 00010000 */
#define OPTION6 0x20 /* 32 binary 00100000 */
The code sets, unsets and tests options as follows:
unsigned int flags;
int main(void) {
flags |= OPTION3; /* Set OPTION3 */
flags &= ~OPTION4; /* Unset OPTION4 */
if ( flags & OPTION3)
printf("Option 3 is set\n");
}
The setting and testing of flags is fairly clear, the unsetting of
OPTION4 is a little more complicated:
OPTION4, like
all options, has just one bit set so
~OPTION4 has every bit
except that one set. So
flags &= ~OPTION4
has the following effect:
- The bit that was one in OPTION4 is zero ~OPTION4
so the binary and for that bit must always be zero.
- The bit that was zero in OPTION4 is one
~OPTION4 so the binary and for that bit will be one if and
only if the corresponding bit in flags was one. That is to
say, it is not changed.
Example in binary
00111011 & ~00001000
~00001000 = 11110111
00111011
11110111
--------
00110011