Identifiers in C: Rules, Naming Conventions & Examples
Identifiers in C: A Comprehensive Tutorial for Programmers
In C programming, an identifier is a name used to identify a variable, function, array, structure, union, enumeration, label, or any other user-defined item. Identifiers are fundamental building blocks—they allow programmers to refer to program elements by meaningful names rather than raw memory addresses. Choosing appropriate identifiers is critical for code readability, maintainability, and correctness.
This tutorial provides an in-depth exploration of identifiers in C: the rules that govern them, naming conventions, how they interact with the compilation process, common pitfalls, and best practices for writing clean, robust code.
Conceptual Introduction & Fundamentals
What is an Identifier?
An identifier is a sequence of characters that uniquely names a program entity. When you write:
c
int age = 25;
age is an identifier that represents a memory location holding an integer value. Similarly, in:
c
double calculateArea(double radius) { ... } calculateArea and radius are identifiers.
Identifiers are compile-time symbols. After compilation, they are translated into memory addresses or offsets; they do not exist in the executable as text strings (except for debugging information). However, they are essential during development for human understanding and for the compiler to associate names with declarations and definitions.
Purpose of Identifiers
- Uniquely name variables, functions, and types.
- Enable referencing those entities later in the program.
- Improve readability by conveying intent (e.g.,
totalCostvsx). - Support modular programming by providing names for functions and structures across translation units.
Difference Between Identifiers and Keywords
Keywords are reserved words that have special meaning to the C compiler (e.g., int, if, while, return). They cannot be used as identifiers because the compiler would interpret them as language constructs, not user-defined names. Identifiers, on the other hand, are programmer-defined names that follow specific rules.
Scope and Lifetime (Brief Overview)
Identifiers have scope—the region of the program where the identifier is visible. C supports:
- Block scope: Identifiers declared inside a block
{ }are visible only within that block. - File scope: Identifiers declared outside any function are visible from the declaration to the end of the file.
- Function prototype scope: Parameter names in a function prototype are visible only within the prototype.
- Function scope: Labels (used with
goto) have function scope.
Lifetime refers to how long the storage associated with an identifier persists: automatic (stack), static, allocated, or thread-local. While scope and lifetime are distinct concepts, the identifier itself is just a name; these rules determine when and where the name can be used.
Syntax & Core Mechanics
Rules for Naming Identifiers
The C standard (C99/C11) defines the following rules for identifiers:
- Allowed characters:
- Uppercase letters
A–Z - Lowercase letters
a–z - Digits
0–9 - Underscore
_
- Uppercase letters
- First character: Must be a letter (uppercase or lowercase) or an underscore. It cannot be a digit.
- Case sensitivity: C is case-sensitive.
myVar,MyVar, andMYVARare three distinct identifiers. - Keywords are prohibited: You cannot use any of the 32 C keywords (e.g.,
int,return,if,while,struct,enum, etc.) as an identifier. - No length limit specified by the standard: The standard requires compilers to support at least 31 significant initial characters for external identifiers and 63 for internal identifiers (C99). Many compilers support longer names, but it is wise to keep names reasonably short.
- Reserved identifiers: The C standard reserves certain identifier patterns for the implementation. You should avoid using:
- Identifiers that begin with an underscore followed by an uppercase letter (e.g.,
_Foo). - Identifiers that begin with two underscores (e.g.,
__myvar). - Identifiers that begin with an underscore at file scope (e.g.,
_global).
- Identifiers that begin with an underscore followed by an uppercase letter (e.g.,
Valid and Invalid Examples
| Valid Identifiers | Invalid Identifiers | Reason |
|---|---|---|
count | 2count | Starts with digit |
_temp | int | Keyword |
myVariable | my-variable | Hyphen not allowed |
MAX_SIZE | my variable | Space not allowed |
func1 | $value | $ not allowed |
_ | while | Keyword |
Note: Although an identifier can consist of a single underscore _, it is often reserved; avoid it for clarity.
Naming Conventions
Naming conventions are not enforced by the compiler but are widely adopted to improve code readability. Common styles include:
- camelCase: First word lowercase, subsequent words capitalized. Used for variables and functions.
Example:frequencyCount,getUserName. - PascalCase: Every word capitalized. Used for structure names, enum types, and sometimes function names.
Example:struct Car,enum Color. - UPPER_SNAKE_CASE: All uppercase with underscores between words. Used for constants and macros.
Example:#define MAX_BUFFER_SIZE 1024,const double PI = 3.14159; - lower_snake_case: All lowercase with underscores. Sometimes used for variables in embedded or kernel code.
Example:int total_count;
Choose one style and apply it consistently across a project.
Using Identifiers in Declarations
Identifiers appear in declarations and definitions to associate a type with a name.
c
int number; // variable declaration double calculateArea(double r); // function prototype (identifier 'calculateArea' and parameter 'r') struct Point { int x; int y; }; // structure tag 'Point', members 'x', 'y' typedef unsigned long size_t; // typedef name 'size_t' The same identifier can be reused in different scopes without conflict, but within the same scope, it must be unique among variables, functions, tags, and labels (with some exceptions for structure tags, which have a separate namespace).
Full Practical Code Example with Explanation
This section presents a complete, robust C program that demonstrates the proper use of identifiers following recommended conventions. The program calculates the area of a circle using a structure, a function, constants, and interactive input. It includes error handling and portable code (C99 compliant).
Program: Circle Area Calculator
c
/** * circle_area.c * Calculates the area of a circle given its radius. * Demonstrates identifier usage, naming conventions, and robust input. */ #include <stdio.h> #include <stdlib.h> #include <math.h> // for M_PI if available; otherwise we define our own constant /* Use a constant for PI. If M_PI is not defined by the system, define it. */ #ifndef M_PI #define M_PI 3.14159265358979323846 #endif /* Define a structure to represent a circle. */ typedef struct { double radius; } Circle; /* Function prototype: calculates area of a circle. * The function takes a pointer to Circle for efficiency. */ double calculateArea(const Circle *c); int main(void) { char inputBuffer[64]; // buffer for user input double radiusValue; char *endPtr; // for strtod error checking /* Prompt user for radius */ printf("Enter the radius of the circle: "); fflush(stdout); // ensure prompt is displayed /* Read a line of input using fgets (safe) */ if (fgets(inputBuffer, sizeof(inputBuffer), stdin) == NULL) { fprintf(stderr, "Error reading input.\n"); return EXIT_FAILURE; } /* Convert string to double with error checking */ radiusValue = strtod(inputBuffer, &endPtr); /* Check for conversion errors: * - endPtr == inputBuffer: no digits found * - *endPtr != '\n' and *endPtr != '\0': extra characters after number * - radiusValue <= 0: radius must be positive */ if (endPtr == inputBuffer || (*endPtr != '\n' && *endPtr != '\0') || radiusValue <= 0) { fprintf(stderr, "Invalid input. Please enter a positive number.\n"); return EXIT_FAILURE; } /* Create a Circle object */ Circle myCircle; myCircle.radius = radiusValue; /* Calculate area using the function */ double area = calculateArea(&myCircle); /* Display result */ printf("Area of circle with radius %.2f is %.4f\n", myCircle.radius, area); return EXIT_SUCCESS; } /* Function definition: calculates area = pi * r^2 */ double calculateArea(const Circle *c) { /* Use the constant M_PI and the structure member radius */ return M_PI * c->radius * c->radius; } Explanation of Identifiers Used
- Macro constant
M_PI: If not provided by<math.h>, we define it using#define M_PI .... All uppercase with underscores—typical for constants. - Structure tag
Circle: PascalCase, a noun, as it represents a data type. - Typedef name
Circle: Same as structure tag, but in C, structure tags and typedef names share the same namespace for ordinary identifiers (they cannot be the same). Here we usetypedef struct { ... } Circle;—the tag is not given a separate name, soCircleis the typedef name. - Member
radius: camelCase, descriptive of the data it holds. - Function
calculateArea: camelCase, a verb phrase describing the action. - Parameter
c: Short but clear in context (pointer to Circle). In a real project, you might name itcirclePtr. - Variables
inputBuffer,radiusValue,endPtr,area,myCircle: Descriptive names following camelCase convention. - Constant
M_PIis used directly.
Expected Output
Sample run 1 (valid input):
text
Enter the radius of the circle: 5.0 Area of circle with radius 5.00 is 78.5398
Sample run 2 (invalid input):
text
Enter the radius of the circle: abc Invalid input. Please enter a positive number.
Sample run 3 (negative radius):
text
Enter the radius of the circle: -3 Invalid input. Please enter a positive number.
The program demonstrates robust input handling, proper use of identifiers, and standard C library functions (fgets, strtod) that avoid unsafe operations.
Common Pitfalls, Edge Cases & Memory Considerations
Identifiers may seem simple, but misuse can lead to compile-time errors, subtle bugs, and maintenance nightmares. Below are the most common pitfalls and important considerations.
1. Using Keywords as Identifiers
C reserves 32 keywords for its own use. Attempting to name a variable int, return, if, while, or const will cause a compilation error.
c
int const = 90; // Error: 'const' is a keyword
Solution: Always check the list of C keywords before naming. Use an editor with syntax highlighting that flags keywords.
2. Starting Identifiers with Digits
The first character must be a letter or underscore. 2fast or 3dPoint are invalid. The compiler will likely report an error like “expected identifier”.
3. Case Sensitivity Confusion
C treats myVar and myvar as completely different identifiers. This can lead to hard-to-find bugs:
c
int total = 100; Total = 200; // Error: 'Total' not declared (case mismatch)
Solution: Be consistent with casing. Use descriptive names that are unlikely to be confused by case alone.
4. Using Reserved Identifiers
Identifiers beginning with an underscore followed by an uppercase letter or another underscore are reserved for the implementation. Similarly, all identifiers beginning with an underscore at file scope are reserved. Using them can conflict with compiler internals or standard library macros.
c
int _MyVariable; // Undefined behavior if used at file scope int __count; // Reserved
Best practice: Avoid leading underscores entirely, except for local variables where _ prefix is sometimes used by convention (but still risky).
5. Naming Conflicts with Standard Library Functions
If you declare a variable or function with the same name as a standard library function (e.g., printf, malloc), you may shadow it or cause link errors. For example:
c
int printf = 5; // This hides the standard printf in this scope printf("Hello"); // Error: called object 'printf' is not a function Solution: Do not use names of standard library functions for your own identifiers.
6. Too Short or Cryptic Names
Using single-letter names like a, b, x, temp makes code difficult to understand. While i, j, k are acceptable for loop counters, most variables deserve meaningful names.
c
int a = 10; // What is 'a'? int age = 10; // Clear
7. Exceeding Implementation Limits
The C standard guarantees at least 31 significant characters for external identifiers and 63 for internal identifiers. If you use names longer than the compiler’s significant limit, two different identifiers might be considered the same, leading to collisions. Most modern compilers support much longer names, but it is prudent to keep names under 31 characters for maximum portability.
8. Shadowing in Inner Scopes
Declaring a variable with the same name as an outer variable in an inner block shadows the outer variable. The outer variable becomes inaccessible inside the block. This can lead to unintended modifications if not careful.
c
int count = 10; { int count = 5; // Inner count shadows outer printf("%d\n", count); // prints 5 } printf("%d\n", count); // prints 10 Solution: Avoid reusing names in nested scopes unless intentional and well-documented.
9. Multiple Definitions Across Translation Units
For external identifiers (global variables or functions with external linkage), the same identifier cannot be defined more than once across all translation units that are linked together. This is a violation of the One Definition Rule (ODR).
c
// file1.c int globalCounter = 0; // file2.c int globalCounter = 1; // Link error: multiple definition
Solution: Use extern declarations in headers and define the variable in a single source file.
10. Memory Considerations
Identifiers themselves do not occupy runtime memory. They are compile-time symbols mapped to addresses or offsets. However, the scope and storage class of an identifier determine when and where memory is allocated for the associated object.
- Automatic variables (local non-static) are allocated on the stack each time the block is entered and deallocated when the block exits. Using too many large automatic variables can lead to stack overflow.
- Static variables (local or global) are allocated in the data segment for the lifetime of the program.
- Allocated memory (via
malloc) is managed manually and must be freed; the identifier (pointer) holds the address.
Choosing the correct storage class for an identifier is crucial for performance and resource usage. For example, a large array should be declared static or allocated dynamically rather than as a huge local variable.
Summary & Best Practices
Identifiers are the names you give to program elements. Following the rules and adopting good naming conventions is essential for writing correct, maintainable C code.
Key Takeaways
- Identifiers must start with a letter or underscore, may contain letters, digits, and underscores, and are case-sensitive.
- They cannot be C keywords and should not use reserved patterns (leading underscore with uppercase or double underscore).
- Naming conventions (camelCase, PascalCase, UPPER_SNAKE_CASE) improve readability; choose a style and be consistent.
- Scope rules determine where an identifier is visible; avoid shadowing.
- Identifiers are compile-time symbols; they do not consume runtime memory, but their associated storage class affects memory allocation.
Best Practices
- Use descriptive, meaningful names. A name like
totalStudentsis far better thants. - Follow a consistent naming convention across the project. Document the convention.
- Avoid leading underscores to prevent conflicts with reserved identifiers.
- Do not reuse standard library names for your own identifiers.
- Keep names reasonably short (under 31 characters for external identifiers) to ensure portability.
- Use
constand#definefor constants and name them in UPPER_SNAKE_CASE. - Use verbs for function names (e.g.,
calculateTotal,printReport) and nouns for variables and structures. - Avoid shadowing by giving variables unique names, especially in nested scopes.
- For global variables, use a prefix to avoid collisions (e.g.,
g_for global,s_for static). - Compile with warnings enabled (
-Wall -Wextra -pedantic) to catch misuse of identifiers (e.g., unused variables, shadowing warnings).
By mastering identifiers, you lay a solid foundation for writing clean, efficient, and professional C code.



