Skip to content
Physics and Astronomy
Home Our Teaching Resources C programming PHY3134 misc.html
Back to top

Computational Physics
Bits and pieces

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:
  1. 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.

  2. 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).

  3. 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:
OperatorDescription
&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.

ExpressionCalculation
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

                                                                                                                                                                                                                                                                       

Validate   Link-check © Copyright & disclaimer Privacy & cookies Share
Back to top