Tuesday, January 5, 2010

Objective-C Tuesdays: extern and global variables

Welcome back and happy new year! Last time we looked at global variables and their initialization. Today we'll look at how to share global variables between .c and .m code files.

Frequently, you define a global variable in one code file and use it in another. For instance, the file MyView.m might contain the global variable doubleTapEnabled:
// MyView.m
#include <Foundation/Foundation.h>
// ...
BOOL doubleTapEnabled = YES;
// ...
To use this variable in main.m you might do this:
// main.m
#include <Foundation/Foundation.h>

BOOL doubleTapEnabled;

int main(void) {
  // ...
  NSLog(@"doubleTapEnabled = %i", doubleTapEnabled);
  // ...
}
This will work the way you expect, but there's a subtle potential gotcha here. Notice that in MyView.m we initialize doubleTapEnabled to YES. When the compiler sees that, it interprets that statement as a global variable definition and allocates space for the doubleTapEnabled variable and sets its initial value.

However, in main.m we don't give doubleTapEnabled an initial value, which makes that statement ambiguous: it could be a global variable declaration for doubleTapEnabled or a definition, with doubleTapEnabled initialized to zero (NO).

The difference between a declaration and a definition can be confusing since they're closely related (and the two words are unfortunately very similar). A declaration tells the compiler that a global variable, struct, class or function exists somewhere. A definition gives the compiler all the information it needs to generate the code for a global variable, struct, class or function.

A statement like
BOOL doubleTapEnabled;
can be either a declaration or a definition. At link time, if an unambiguous definition isn't found, the global variable will be created and initialized to zero; if an unambiguous definition is found, that definition will be used to create the global variable.

So if you do something like this:
// ERROR: won't link

// MyView.m
// ...
BOOL doubleTapEnabled = YES; // unambiguous definition
// ...

// main.m
// ...
BOOL doubleTapEnabled = YES; // unambiguous definition
// ...
You will get a linker error like "duplicate symbol _doubleTapEnabled" because you told the compiler to create the same global variable in two different places.

This is where the extern keyword comes in. You use extern with global variables to create unambiguous declarations. In addition to helping the compiler, it also clues in anyone reading the code that the global variable is defined (and possibly initialized) elsewhere, but you're simply using it here. So we can rewrite our example like this:
// MyView.m
// ...
BOOL doubleTapEnabled = YES; // unambiguous definition
// ...

// main.m
// ...
extern BOOL doubleTapEnabled; // defined in MyView.m
// ...
And now doubleTapEnabled is happily unambiguous everywhere.

It's common style to add extern to all global variable declarations in your header (.h) files and provide a corresponding definition in a source (.m or .c) file. So the header file MyView.h would look like:
// MyView.h
// ...
extern BOOL doubleTapEnabled;
// ...
The source file MyView.m is the same:
// MyView.m
// ...
BOOL doubleTapEnabled = YES; // unambiguous definition
// ...
And main.m now looks like:
// main.m
// ...
#include "MyView.h"
// ...
Next time, we'll look at using the static keyword to make to make a global variable "private" to a source file.

No comments: