Every rule for naming variables, functions, and other entities in C — compiler-enforced rules, professional naming conventions, scope, lifetime, and the reserved patterns even experienced programmers occasionally forget.
📅
Last Updated
March 2026
⏱️
Read Time
23 min
🎯
Level
Beginner
What is an Identifier in C?
An identifier is a name you, the programmer, invent to label something in your C program — a variable, a function, an array, a struct, a macro, or any other user-defined entity. Every time you write int totalMarks; or void calculateArea(void);, the words totalMarks and calculateArea are identifiers: names that exist purely because you chose them, carrying no built-in meaning to the compiler beyond whatever you use them to represent.
This is precisely what separates an identifier from a keyword. A keyword like int or return has a fixed, unchangeable meaning baked into the C language itself. An identifier has no inherent meaning at all — totalMarks could just as easily be named tm, x, or studentScoreSum, and the program would behave identically. The compiler does not care what you name things; it only cares that your naming follows a small set of strict rules, and that every identifier you use was properly declared before you use it.
Despite this freedom, identifiers are arguably the single most important stylistic choice you make as a programmer. Code is read far more often than it is written, and a well-chosen identifier like studentAverageGrade instantly communicates intent, while a poorly chosen one like x2 forces every future reader — including you, six months later — to reverse-engineer what it was meant to represent. This chapter covers both halves of that story: the rules the compiler enforces, and the conventions professionals follow that the compiler does not enforce at all.
Compiler-Enforced Rules for Identifiers
C defines a small, strict set of rules that every identifier must follow. Violating any of these produces an immediate compile-time error — there is no flexibility or interpretation involved; the compiler checks these rules mechanically, character by character.
▶
Rule 1 — Allowed characters only — An identifier may contain only uppercase letters (A-Z), lowercase letters (a-z), digits (0-9), and the underscore (_). No spaces, hyphens, punctuation, or symbols like @, $, or % are permitted anywhere in an identifier.
▶
Rule 2 — Cannot begin with a digit — The very first character must be a letter or an underscore. total1 is valid; 1total is not, because the compiler needs to distinguish the start of a name from the start of a numeric constant unambiguously.
▶
Rule 3 — Case sensitivity — C treats uppercase and lowercase letters as completely distinct. total, Total, and TOTAL are three entirely different identifiers as far as the compiler is concerned, and can even coexist as three separate variables in the same scope.
▶
Rule 4 — Cannot match a reserved keyword — None of C's 32+ reserved keywords (int, if, return, struct, and so on) can be used as an identifier, since the compiler has already assigned them a fixed grammatical meaning.
▶
Rule 5 — Significant character limit — The C standard guarantees at least 31 significant characters are recognised for identifiers with internal linkage, and at least 63 for those with external linkage. In practice, every modern compiler (GCC, Clang, MSVC) supports far longer names without any practical limitation.
▶
Rule 6 — Must be unique within its scope — Two identifiers with the same name cannot both be declared in the exact same scope (for example, two variables both named count inside the same function block), though the same name can be reused safely in separate, non-overlapping scopes.
🖥️ Cvalid_vs_invalid.c
int totalMarks = 90; // ✅ Valid: letters only, starts with a letter
int _tempValue = 5; // ✅ Valid: starts with underscore (legal, though discouraged)
int student_1 = 1; // ✅ Valid: letters, digit, underscore
int Count, count, COUNT; // ✅ Valid: three distinct identifiers (case-sensitive)
// int 2ndPlace = 5; // ❌ INVALID: starts with a digit
// int my-score = 10; // ❌ INVALID: hyphen is not allowed
// int total marks = 90; // ❌ INVALID: contains a space
// int float = 3; // ❌ INVALID: 'float' is a reserved keyword
// int @rate = 5; // ❌ INVALID: '@' is not an allowed character
How the Compiler Validates an Identifier — Flowchart
It helps to see exactly what mental checklist the compiler effectively runs through the moment it encounters something that looks like it might be an identifier in your source code.
📝 Candidate Word Founde.g. 'totalMarks', '2ndPlace'
❌ Reject: Illegal Charactere.g. space, hyphen, symbol
📖 Compare Against Keyword ListMatches int, if, return, etc.?
matches a keyword
🔑 Classify as KEYWORDCannot be reused as a name
✅ Valid IdentifierAccepted into the symbol table
Code Execution Flow — from source to output
Every 'reject' branch in this flowchart corresponds to a real compiler error message you will eventually see, such as 'expected identifier before numeric constant' (Rule 2 violation) or 'expected identifier before int' (Rule 4 violation, when a keyword is mistakenly used as a name). Once an identifier clears every check, it is accepted and added to the compiler's symbol table, ready to be tracked for type and scope throughout the rest of compilation.
Professional Naming Conventions (Not Enforced, But Expected)
Beyond the compiler's strict rules lies a much larger, unenforced world of convention — habits professional C developers follow consistently, not because the compiler demands it, but because clear, predictable naming makes code dramatically easier to read, maintain, and debug.
🐫
camelCase for Variables & Functions
The most common convention in modern C: the first word lowercase, each subsequent word capitalised — totalMarks, calculateAverage(), studentCount. Widely used in application-level and beginner-friendly codebases.
🐍
snake_case as an Alternative
All lowercase, words separated by underscores — total_marks, calculate_average(), student_count. Extremely common in systems programming, the Linux kernel, and much of the C Standard Library itself (e.g. strlen, malloc).
📢
ALL_CAPS for Macros and Constants
#define MAX_STUDENTS 100 and const int DAYS_IN_WEEK = 7; — capitalising constants signals 'this value is fixed' at a glance, instantly distinguishing it from an ordinary variable.
📏
Descriptive Over Short
studentAverageMarks communicates far more than sam or x, even though all three compile identically. The extra few characters of typing cost almost nothing compared to the reading time saved for every future maintainer.
🔡
Consistent Prefixes for Related Groups
Grouping related identifiers with a shared prefix — studentName, studentAge, studentGPA — visually clusters them together and makes autocomplete far more useful in modern editors.
Single letters like i, j, and k remain an accepted convention specifically for short-lived loop counters, but should be avoided for anything with broader meaning or a longer lifetime in the program.
Convention
Example
Most Common In
camelCase
totalMarks, calculateArea
Application code, beginner tutorials, many modern C codebases
snake_case
total_marks, calculate_area
Linux kernel, POSIX APIs, much of the C Standard Library
PascalCase
TotalMarks, CalculateArea
Less common in C; more typical of C++/C# struct/type names
ALL_CAPS
MAX_STUDENTS, PI_VALUE
Macros (#define) and true constants everywhere
Hungarian notation (legacy)
iCount, szName, pPointer
Older Windows API code; largely discouraged in modern style guides
Identifier Scope — Where a Name Is Visible
An identifier's scope is the region of a program where that name can actually be referenced. C defines several distinct kinds of scope, and understanding them prevents both 'undeclared identifier' errors and a subtler class of bugs caused by accidentally shadowing one identifier with another of the same name.
▶
Block Scope — An identifier declared inside a pair of curly braces { } — including inside a function, if statement, or loop — is visible only from its point of declaration to the closing brace of that specific block.
▶
Function Scope — Applies specifically to labels used with goto — a label is visible anywhere within the function it is declared in, regardless of block nesting, but nowhere outside that function.
▶
File Scope (Global Scope) — An identifier declared outside every function, at the top level of a source file, is visible from its declaration point to the end of that file, and potentially to other files too if declared extern.
▶
Function Prototype Scope — Parameter names written inside a function's prototype/declaration (not its definition) exist only within that prototype itself, purely for documentation purposes — for example, the total in int add(int total, int extra);.
🖥️ Cscope_example.c
#include <stdio.h>
int globalCount = 100; // File scope: visible everywhere in this file
void printValues(void) {
int localCount = 5; // Block scope: visible only inside this function
printf("global: %d, local: %d\n", globalCount, localCount);
{
int localCount = 99; // A NEW variable, shadows the outer localCount here
printf("inner localCount: %d\n", localCount); // prints 99
}
printf("outer localCount: %d\n", localCount); // prints 5 again
}
int main() {
printValues();
// printf("%d", localCount); // ERROR: localCount not visible here
return 0;
}
The inner block's localCount in this example shadows the outer one — meaning it temporarily hides the outer variable of the same name for the duration of that inner block, without actually destroying or modifying it. This is completely legal C, but professional style guides generally discourage deliberately reusing names this way, since it can confuse readers (and occasionally the programmer themselves) about which variable a given line is actually referring to.
Linkage and Lifetime — How Long an Identifier Exists
Beyond scope (where a name is visible), C also defines linkage (whether a name refers to the same entity across different scopes or files) and lifetime (how long the underlying storage that identifier refers to actually exists in memory).
Linkage Type
Meaning
Typical Example
No linkage
Each declaration refers to a unique, unrelated entity
A local variable inside a function, e.g. int count;
Internal linkage
Visible and shared throughout one source file, but invisible to other files
A static global variable or static function
External linkage
Visible and shared across multiple source files in the same program
An ordinary global variable or function, or one declared extern
Lifetime Category
Meaning
Typical Example
Automatic
Created when its block begins executing, destroyed when the block ends
A normal local variable, e.g. int x; inside a function
Static
Exists for the entire duration of the program's execution
A variable declared static, or any global variable
Dynamic (allocated)
Created and destroyed explicitly by the programmer using malloc()/free()
Memory obtained via malloc() and released via free()
These two concepts are related to, but distinct from, scope. A static local variable, for instance, has block scope (visible only inside its function) but static lifetime (it retains its value between calls, existing for the entire program's run) — precisely the behaviour shown in the static int calls = 0; example covered in the C Keywords chapter.
Internal vs External Identifiers
The C standard draws a specific distinction between identifiers used purely within a single file (internal identifiers) and those intended to be shared across multiple files in a larger, multi-file project (external identifiers) — a distinction that matters directly for the minimum significant-character guarantees mentioned earlier.
▶
Internal identifiers — Names only ever referenced within the single source file where they are declared — most local variables, and any static global variable or static function. The standard guarantees at least 31 significant characters are considered by every conforming compiler.
▶
External identifiers — Names intended to be shared and linked across multiple .c files — ordinary (non-static) global variables and functions, typically declared extern in other files that need to use them. The standard guarantees at least 63 significant characters, though historically some older linkers imposed stricter limits, which is partly why this distinction exists at all.
🖥️ Cutils.c — an external identifier
int sharedTotal = 0; // External identifier: visible to other files via extern
int calculateSum(int a, int b) { // External identifier: callable from other files
return a + b;
}
🖥️ Cmain.c — using the external identifier
#include <stdio.h>
extern int sharedTotal; // Declares: defined elsewhere, but usable here
extern int calculateSum(int, int); // Declares the function's signature
int main() {
sharedTotal = calculateSum(10, 20);
printf("Sum: %d\n", sharedTotal);
return 0;
}
Reserved Identifier Patterns — Names You Should Never Choose
Beyond the formal keyword list, the C standard also reserves certain patterns of identifier names specifically for the compiler and the C Standard Library's own internal use. These are technically legal to write, and many compilers will not immediately reject them — but using them yourself risks silent, hard-to-diagnose conflicts with the implementation.
▶
Names beginning with a double underscore (__init) — Reserved everywhere in the program, in every scope, for the compiler and standard library's exclusive use.
▶
Names beginning with an underscore followed by an uppercase letter (_Temp) — Also reserved everywhere, following the exact pattern the C11 standard used for its own new keywords like _Bool and _Atomic.
▶
Names beginning with a single underscore at file scope (_config) — Reserved specifically for the implementation when declared outside any function, though local variables inside a function with a single leading underscore are technically permitted (if still stylistically discouraged).
▶
Standard library identifiers (printf, malloc, size_t, errno) — Not formally 'reserved' the same way, but redefining or shadowing any of these is almost always an unintentional and serious mistake, since it can silently replace behaviour your own code and included headers may still depend on.
The single safest professional habit: never start your own identifiers with an underscore at all. This one rule of thumb sidesteps every reserved-pattern collision described above without requiring you to memorise the precise, somewhat intricate details of which underscore patterns are reserved in which specific scope.
Identifiers vs Keywords — Side-by-Side Comparison
Aspect
Identifier
Keyword
Who defines it
The programmer, for their own use
The C language standard itself
Meaning
None inherently — entirely defined by how you use it
Fixed and unchangeable, defined by the grammar
Can be redefined?
N/A — you invent it fresh each time
❌ Never — permanently reserved
Total count possible
Unlimited — as many as a program needs
32 in C89, more added by later standards
Examples
totalMarks, calculateArea, Student
int, if, return, struct, while
Governed by
Naming rules (Rules 1-6) + style conventions
The ISO C grammar specification directly
Common Identifier Mistakes Beginners Make
✅ Advantages
Descriptive Naming from Day OneBuilding the habit of writing studentAge instead of a or x early on pays off enormously once programs grow beyond a few dozen lines, and costs nothing extra to learn from the start.
One Consistent Convention Per ProjectChoosing either camelCase or snake_case and sticking to it consistently throughout a single project (rather than mixing both) makes the entire codebase easier to scan and predict.
Reserving ALL_CAPS Purely for ConstantsFollowing the widespread convention of using capitals only for macros and true constants means anyone reading MAX_USERS instantly knows, without checking, that this value never changes.
Avoiding Leading Underscores EntirelySidestepping reserved-identifier patterns completely by simply never starting an identifier with an underscore, regardless of the exact technical rule involved.
❌ Disadvantages
Using a Keyword as a Variable NameAttempting int for = 5; or float double = 2.5; fails immediately, since both for and double are reserved keywords — a surprisingly common early mistake before the full keyword list becomes familiar.
Starting an Identifier With a DigitWriting int 3rdAttempt = 1; produces a compile error, since the compiler needs the very first character to unambiguously signal 'this is a name', not the start of a number.
Overly Short, Meaningless NamesNaming everything x, y, temp, or data without any further context makes code technically correct but very difficult for anyone (including your future self) to understand months later.
Mixing Multiple Naming ConventionsSwitching between totalMarks, total_score, and TotalCount within the same small project creates unnecessary visual inconsistency and cognitive overhead for readers.
Relying on Case Sensitivity for Distinct MeaningDeclaring both total and Total as separate variables intended to hold genuinely different things is legal but confusing — a reader skimming quickly can very easily mistake one for the other.
Extended Valid & Invalid Identifier Examples
This reference table walks through a wide variety of borderline and instructive examples, explaining precisely why each one is accepted or rejected by the compiler.
Identifier
Valid?
Reason
studentMarks
✅ Valid
Letters only, starts with a letter, follows camelCase
Legal syntax, but risks colliding with reserved patterns at file scope
Marks2024
✅ Valid
Digits are permitted anywhere except as the very first character
2024Marks
❌ Invalid
Begins with a digit
student-marks
❌ Invalid
Hyphen is not an allowed character in C identifiers
student marks
❌ Invalid
Contains a space, which always terminates a token
int
❌ Invalid
Exactly matches a reserved keyword
Int
✅ Valid
Different capitalisation than the keyword int — C is case-sensitive
total$score
❌ Invalid
The dollar sign is not part of C's allowed identifier character set
__reserved
✅ Valid syntax, but reserved
Begins with a double underscore — reserved for the compiler/library
MAX_LIMIT
✅ Valid
All uppercase — conventional style for macros and true constants
How Identifier Rules Have Evolved Across C Standards
While the core identifier rules have remained remarkably stable since C89, later standards refined the details, particularly around how many characters are guaranteed to be significant, and how far Unicode-based names are supported.
First formal identifier length guarantees; the external limit of only 6 characters, case-insensitive, reflected very old linker limitations
C99
63 / 31
Significantly raised both guarantees; introduced limited support for universal character names (Unicode escapes) in identifiers
C11
63 / 31 (unchanged from C99)
Improved and clarified rules for using universal character names within identifiers
C17 / C18
63 / 31 (unchanged)
No changes to identifier rules — purely a bug-fix and clarification release
C23
63 / 31 (unchanged)
Improved practical Unicode identifier support, aligning more closely with other modern languages
In practice, every mainstream compiler today (GCC, Clang, MSVC) comfortably exceeds even the C23 minimums by a huge margin, routinely supporting identifiers hundreds of characters long without complaint — these standard-mandated minimums exist purely as a portability guarantee, not a practical ceiling you are ever likely to bump into in ordinary code.
C Identifiers — Interview Questions
Identifier-focused questions are a staple of entry-level interviews and academic viva examinations, since they test whether a candidate truly understands C's naming rules rather than having simply avoided breaking them by accident.
An identifier is a name the programmer chooses to label a variable, function, array, or other user-defined entity — such as totalMarks or calculateArea. A keyword is a reserved word with a fixed, predefined meaning built into the C language itself, such as int or return, and can never be reused as an identifier. Identifiers carry no built-in meaning; their meaning comes entirely from how the programmer defines and uses them.
An identifier may only contain letters, digits, and underscores; it cannot begin with a digit; it is case-sensitive, so total and Total are distinct names; it cannot exactly match any reserved keyword; and it must be unique within its own scope. The standard also guarantees a minimum number of significant characters (at least 31 for internal identifiers, at least 63 for external ones), though modern compilers support far more in practice.
Yes, C is fully case-sensitive. total, Total, and TOTAL are treated as three entirely distinct identifiers by the compiler, and could theoretically all exist as separate variables within the same scope simultaneously, each holding a completely different, unrelated value.
Scope defines the region of a program where a particular identifier can actually be referenced. C's main scope categories are block scope (visible only within the enclosing curly braces), function scope (used specifically for goto labels, visible anywhere in that function), file scope (visible from declaration to the end of the source file, for identifiers declared outside any function), and function prototype scope (parameter names inside a bare declaration, existing only within that declaration itself).
Internal linkage means an identifier (such as a static global variable or static function) is visible and consistently refers to the same entity only within the single source file where it is declared — invisible to any other file in the project. External linkage means the identifier can be shared and refer to the same entity across multiple separate source files, typically via an extern declaration in the files that need to use it.
The C standard reserves specific underscore-based patterns — a double leading underscore anywhere, an underscore followed by an uppercase letter anywhere, and a single leading underscore at file scope — exclusively for the compiler and standard library's own internal use. Using these patterns yourself risks an unintended, sometimes silent naming collision with the implementation, so the safest professional practice is to avoid leading underscores entirely, regardless of the precise rule that would technically apply in a given case.
Shadowing occurs when an identifier declared in an inner, nested block has the same name as one already declared in an enclosing outer block — the inner declaration temporarily hides (shadows) the outer one for the remainder of that inner block, without destroying it. This is completely legal in C, but it is generally discouraged in professional style guides, since it can confuse readers about which variable a given statement is actually referring to.
Scope describes where in the source code an identifier's name can be referenced. Lifetime describes how long the underlying storage that identifier refers to actually exists in memory during program execution. These are related but distinct — for example, a static local variable has block scope (only visible inside its function) but static lifetime (its storage persists for the entire duration of the program, retaining its value between separate calls to that function).
This guarantee exists to ensure portability across different compilers and linkers, some of which historically had genuine internal limits on identifier length, particularly for external (cross-file) linkage. By guaranteeing at least a certain number of characters are always considered meaningful (63 for external identifiers under modern standards), the standard ensures reasonably descriptive names remain fully portable, even though virtually all modern compilers support names far longer than this guaranteed minimum.
Yes, as long as they exist in entirely separate, non-overlapping scopes — for example, two different functions can each independently declare their own local variable named count without any conflict, since each count only exists and is only visible within its own function's block scope. Two identifiers with the same name cannot, however, coexist within the exact same scope.
Practice Questions — Test Your Understanding
1. Identify which of these are valid C identifiers, and explain why any invalid ones fail: myAge, 7dwarfs, total-sum, _config, Return, my Var.
Easy
✅ AnswerValid: myAge (starts with a letter), _config (legal syntax, though discouraged since it begins with an underscore), Return (different capitalisation than the keyword return, so it is a distinct, valid identifier). Invalid: 7dwarfs (begins with a digit), total-sum (contains a hyphen, not an allowed character), my Var (contains a space, which is never permitted inside an identifier).
2. What is the difference between the identifiers Count and count in a C program?
Easy
✅ AnswerBecause C is case-sensitive, Count and count are treated as two entirely separate, unrelated identifiers by the compiler. They could be declared as two completely distinct variables within the same scope, each holding independent, unrelated values, with no connection between them beyond superficially similar spelling.
3. Explain why int double = 5; fails to compile, while int Double = 5; compiles successfully.
Medium
✅ Answerdouble is a reserved C keyword representing a data type, so it cannot be used as an identifier under any circumstances — attempting to do so produces a syntax error. Double, however, with a capital D, does not exactly match the lowercase keyword double at all (since C is case-sensitive), making it a perfectly legal, ordinary identifier as far as the compiler is concerned.
4. In the code below, what does each printf statement output, and why? int x = 10; { int x = 20; printf("%d", x); } printf("%d", x);
Medium
✅ AnswerThe first printf, inside the inner block, prints 20 — the inner declaration of x shadows the outer one for the duration of that block. The second printf, after the inner block has ended, prints 10 — the inner x's scope has ended entirely, so the original outer x (which was never actually modified) is the one referenced again.
5. Why is it considered poor practice, though not illegal, to name your own variable __buffer?
Medium
✅ AnswerIdentifiers beginning with a double underscore are reserved by the C standard specifically for the compiler and standard library's internal use, in every scope. While many compilers will not immediately reject this as a syntax error, using such a name risks an unintended naming collision with something the compiler or a library header already defines internally, potentially causing confusing, hard-to-diagnose behaviour — so professional style guides strongly discourage it even where it happens to compile without complaint.
6. What is the practical difference between an identifier with internal linkage and one with external linkage in a two-file project?
Hard
✅ AnswerAn identifier with internal linkage (such as a static global variable) exists and is usable only within the single source file it is declared in — a second file in the same project cannot access it at all, even by name, since it has no visibility outside that file. An identifier with external linkage (an ordinary, non-static global variable or function) can be shared across files: a second file can declare it with extern and refer to the very same underlying entity defined in the first file.
7. A beginner names every loop counter and temporary variable in a 200-line program x, y, z, a, b, c. What specific problems does this create, beyond simply 'being unclear'?
Hard
✅ AnswerBeyond general unreadability, single-letter names offer no searchability (searching a codebase for the letter x returns thousands of irrelevant matches), no self-documentation (a reader must trace back to each declaration to understand what the variable represents), and a significantly higher risk of accidental reuse or shadowing bugs, since short generic names are far more likely to unintentionally collide across different blocks or functions than descriptive, purpose-specific names would be.
8. Why does the C standard guarantee different minimum significant-character counts for internal versus external identifiers (31 vs 63 in modern standards)?
Hard
✅ AnswerInternal identifiers are only ever processed by a single compiler working on a single file, which has always been relatively free to support long names easily. External identifiers, however, must additionally survive being processed by the linker, which historically (particularly on older systems) sometimes imposed stricter length or case-sensitivity limitations when matching names across separately compiled object files. The lower historical guarantee for external identifiers reflected this additional linker-level constraint, even though modern linkers have long since removed any such practical limitation.
Conclusion — Naming Well Is a Skill Worth Mastering
Identifiers sit at an interesting intersection in C: the compiler enforces only a small, mechanical set of rules about what characters and patterns are legal, yet the choices you make within that freedom shape how readable, maintainable, and bug-resistant your code becomes far more than almost any other single habit.
You now understand the six compiler-enforced naming rules, the professional conventions (camelCase, snake_case, ALL_CAPS for constants) that experienced C developers follow by habit, the different categories of scope and linkage that determine where and how long an identifier is meaningful, the internal-vs-external identifier distinction tied to multi-file projects, and the reserved underscore patterns the standard sets aside for its own use — knowledge that will save you from an entire category of confusing bugs and cryptic compiler errors.
Concept Covered
Why It Matters Going Forward
Six naming rules
Prevents an entire class of 'why won't this compile' frustration
Naming conventions
Makes your code readable to yourself and others months later
Scope (block/function/file)
Explains exactly where a given name can and cannot be used
Linkage & lifetime
Explains static's dual behaviour and multi-file variable sharing
Reserved underscore patterns
Avoids silent collisions with the compiler/standard library
Identifiers vs keywords
Prevents the single most common early beginner naming mistake
The natural next step is C Variables and Data Types, where every identifier you have just learned to name correctly finally gets attached to real, working storage — actual memory that holds values, gets modified, and drives your program's logic.
A name is the very first decision you make about anything in your program — make it count. Clear, rule-following, conventionally styled identifiers are a habit that compounds in value with every additional line of code you write. 🏷️
Frequently Asked Questions (FAQ)
Yes, digits are fully allowed anywhere within an identifier except as the very first character. Names like total1, score2024, and item99 are all completely valid, since each begins with a letter and only uses digits afterward.
The C standard only guarantees a minimum number of significant characters compilers must support (at least 63 for external identifiers under modern standards), not a strict maximum. In practice, every modern compiler like GCC, Clang, and MSVC supports identifiers far longer than this guaranteed minimum, so a practical length limit is rarely, if ever, a genuine concern for typical C code.
A hyphen (-) is already reserved as the subtraction operator in C's grammar, so allowing it inside identifiers as well would create serious ambiguity — the compiler could not reliably tell whether my-var meant a single identifier or the expression my minus var. The underscore carries no other grammatical meaning in C, making it a safe, unambiguous character to permit within names instead.
Either is a valid, widely used professional convention — camelCase is common in many application-level and beginner-oriented C codebases, while snake_case is the dominant style in the Linux kernel, POSIX system APIs, and much of the C Standard Library itself. The single most important rule is consistency: pick one convention for a given project and apply it uniformly throughout, rather than mixing styles.
The compiler reports a redeclaration or redefinition error at compile time, refusing to build the program, since a single scope cannot contain two distinct entities sharing the exact same identifier name simultaneously. This is different from shadowing, which involves two separate, non-overlapping nested scopes and is legal.
Variable names are one specific, very common category of identifier, but the term identifier is broader — it also covers function names, struct and union tags, enum constant names, typedef aliases, macro names, and goto labels. Every variable name is an identifier, but not every identifier is necessarily a variable name.