Previous Page
Next Page

1.6. Identifiers

The term identifier refers to the names of variables, functions, macros, structures and other objects defined in a C program. Identifiers can contain the following characters:

  • The letters in the basic character set, a-z and A-Z. Identifiers are case-sensitive.

  • The underscore character, _.

  • The decimal digits 0-9, although the first character of an identifier must not be a digit.

  • Universal character names that represent the letters and digits of other languages.

The permissible universal characters are defined in Annex D of the C standard, and correspond to the characters defined in the ISO/IEC TR 10176 standard, minus the basic character set.

Multibyte characters may also be permissible in identifiers . However, it is up to the given C implementation to determine exactly which multibyte characters are permitted and what universal character names they correspond to.

The following 37 keywords are reserved in C, each having a specific meaning to the compiler, and must not be used as identifiers:

auto

enum

restrict

unsigned

break

extern

return

void

case

float

short

volatile

char

for

signed

while

const

goto

sizeof

_Bool

continue

if

static

_Complex

default

inline

struct

_Imaginary

do

int

switch

double

long

typedef

else

register

union

The following examples are valid identifiers:

x dollar Break error_handler scale64

The following are not valid identifiers:

1st_rank switch y/n x-ray

If the compiler supports universal character names, then a is also an example of a valid identifier, and you can define a variable by that name:

    double a = 0.5;

Your source code editor might save the character a in the source file as the universal character \u03B1.

When choosing identifiers in your programs, remember that many identifiers are already used by the C standard library. These include the names of standard library functions, which you cannot use for functions you define or for global variables. See Chapter 15 for details.

The C compiler provides the predefined identifier _ _func_ _, which you can use in any function to access a string constant containing the name of the function. This is useful for logging or for debugging output; for example:

    #include <stdio.h>
    int test_func( char *s )
    {
      if( s == NULL) {
        fprintf( stderr,
               "%s: received null pointer argument\n", _  _func_  _ );
        return -1;
      }
      /* ... */
    }

In this example, passing a null pointer to the function test_func( ) generates the following error message:

    test_func: received null pointer argument

There is no limit on the length of identifiers. However, most compilers consider only a limited number of characters in identifiers to be significant. In other words, a compiler might fail to distinguish between two identifiers that start with a long identical sequence of characters. To conform to the C standard, a compiler must treat at least the first 31 characters as significant in the names of functions and global variables (that is, identifiers with external linkage), and at least the first 63 characters in all other identifiers.

1.6.1. Identifier Name Spaces

All identifiers fall into exactly one of the following four categories, which constitute separate name spaces:

  • Label names.

  • Tags, which identify structure, union and enumeration types.

  • Names of structure or union members. Each structure or union constitutes a separate name space for its members.

  • All other identifiers, which are called ordinary identifiers.

Identifiers that belong to different name spaces may be the same without causing conflicts. In other words, you can use the same name to refer to different objects, if they are of different kinds. For example, the compiler is capable of distinguishing between a variable and a label with the same name. Similarly, you can give the same name to a structure type, an element in the structure, and a variable, as the following example shows:

    struct pin { char pin[16];  /* ... */ };
    _Bool check_pin( struct pin *pin )
    {
      int len = strlen( pin->pin );
      /* ... */
    }

The first line of the example defines a structure type identified by the tag pin, containing a character array named pin as one of its members. In the second line, the function parameter pin is a pointer to a structure of the type just defined. The expression pin->pin in the fourth line designates the member of the structure that the function's parameter points to. The context in which an identifier appears always determines its name space with no ambiguity. Nonetheless, it is generally a good idea to make all identifiers in a program distinct, in order to spare human readers unnecessary confusion.

1.6.2. Identifier Scope

The scope of an identifier refers to that part of the translation unit in which the identifier is meaningful. Or to put it another way, the identifier's scope is that part of the program that can "see" that identifier. The type of scope is always determined by the location at which you declare the identifier (except for labels, which always have function scope ). Four kinds of scope are possible:


File scope

If you declare an identifier outside all blocks and parameter lists, then it has file scope . You can then use the identifier anywhere after the declaration and up to the end of the translation unit.


Block scope

Except for labels, identifiers declared within a block have block scope . You can use such an identifier only from its declaration to the end of the smallest block containing that declaration. The smallest containing block is often, but not necessarily, the body of a function definition. In C99, declarations do not have to be placed before all statements in a function block. The parameter names in the head of a function definition also have block scope, and are valid within the corresponding function block.


Function prototype scope

The parameter names in a function prototype have function prototype scope . Because these parameter names are not significant outside the prototype itself, they are meaningful only as comments, and can also be omitted. See Chapter 7 for further information.


Function scope

The scope of a label is always the function block in which the label occurs, even if it is placed within nested blocks. In other words, you can use a goto statement to jump to a label from any point within the same function that contains the label. (Jumping into nested blocks is not a good idea, though: see Chapter 6 for details.)

The scope of an identifier generally begins after its declaration. However, the type names, or tags, of structure, union, and enumeration types and the names of enumeration constants are an exception to this rule: their scope begins immediately after their appearance in the declaration, so that they can be referenced again in the declaration itself. (Structures and unions are discussed in detail in Chapter 10; enumeration types are described in Chapter 2.) For example, in the following declaration of a structure type, the last member of the structure, next, is a pointer to the very structure type that is being declared:

    struct Node { /* ... */
                  struct Node *next; };          // Define a structure type
    void printNode( const struct Node *ptrNode); // Declare a function

    int printList( const struct Node *first )    // Begin a function definition
    {
      struct Node *ptr = first;

      while( ptr != NULL ) {
        printNode( ptr );
        ptr = ptr->next;
      }
    }

In this code snippet, the identifiers Node, next, printNode, and printList all have file scope . The parameter ptrNode has function prototype scope , and the variables first and ptr have block scope .

It is possible to use an identifier again in a new declaration nested within its existing scope, even if the new identifier does not have a different name space. If you do so, then the new declaration must have block or function prototype scope, and the block or function prototype must be a true subset of the outer scope. In such cases, the new declaration of the same identifier hides the outer declaration, so that the variable or function declared in the outer block is not visible in the inner scope. For example, the following declarations are permissible:

    double x;               // Declare a variable x with file scope
    long calc( double x );  // Declare a new x with function prototype scope

    int main( )
    {
      long x = calc( 2.5 ); // Declare a long variable x with block scope

      if( x < 0 )           // Here x refers to the long variable
      { float x = 0.0F;     // Declare a new float variable x with block scope
        /*...*/
      }
      x *= 2;               // Here x refers to the long variable again
      /*...*/
    }

In this example, the long variable x delcared in the main( ) function hides the global variable x with type double. Thus there is no direct way to access the double variable x from within main( ). Furthermore, in the conditional block that depends on the if statement, x refers to the newly declared float variable, which in turn hides the long variable x.


Previous Page
Next Page