Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
C Character Set Explained
Lecture 1 of the Elements of C chapter. Part of our free embedded systems course: learn the letters, digits, symbols and delimiters every C program is built from.
The C character set is the collection of letters, digits, symbols and spacing characters that the compiler is willing to read in your source file. Everything you will ever write in C, from a one-line printf to a Linux driver, is built from these characters and nothing else. If you are following this free embedded systems course to prepare for product-company interviews, this is the foundation: interviewers love to probe what the compiler does with a character, not just what a program prints.
By the end of this lecture you will be able to explain how the compiler turns raw characters into tokens, tell delimiters from operators, and write small programs that classify and manipulate characters using their numeric codes.
What You Will Learn
- What the C character set contains and why the standard calls it the basic source character set.
- How letters, digits and special characters are used in real C syntax.
- The difference between the source character set and the execution character set.
- What whitespace really does, and why
int xis not the same asintx. - The job of each delimiter: colon, semicolon, parentheses, brackets, braces, hash and comma.
- How to classify characters with
ctype.hand convert between characters and numbers. - Typical beginner errors, a quiz, and interview questions asked on this topic.
Prerequisites
- A working compiler. On Ubuntu or Debian run
sudo apt install gcc; on Windows use WSL or MinGW. - Ability to save a
.cfile and compile it from a terminal. Chapter 1 of this free embedded C course covered setup. - No earlier C knowledge beyond
printfis required.
Why the C Character Set Matters
A C source file is just text. Before the compiler can understand a single idea in it, it performs lexical analysis: it reads characters one by one and groups them into the smallest meaningful units, called tokens. A token can be a keyword like while, a name like total, a number like 42, a string, an operator like +, or a punctuator like ;. The character set decides which characters are allowed to take part in this process.
Because the very first stage is character-driven, a single wrong character, for example a curly quote pasted from a web page, stops the build immediately. Understanding the set saves hours of confusing compiler messages.
The Three Building Blocks of the C Character Set
The basic character set is divided into letters, digits and special characters. Whitespace is added on top of these as a fourth group that controls layout.
| Group | Members | Count | Typical use |
|---|---|---|---|
| Letters | A to Z and a to z | 52 | Keywords, variable and function names |
| Digits | 0 to 9 | 10 | Numeric constants, parts of names |
| Graphic symbols | Punctuation and operator characters | 29 | Operators, delimiters, quotes, escapes |
| Whitespace | Space, horizontal tab, vertical tab, form feed, newline | 5 | Separating tokens, formatting |
Adding 52 letters, 10 digits and 29 graphic symbols gives the 91 graphic characters of the basic set. Notice that the underscore is counted among the 29 symbols, yet the language treats it like a letter when you build names.
Alphabets: Case Matters
C distinguishes uppercase from lowercase everywhere. Speed, speed and SPEED are three unrelated names, and While is not the keyword while. Behind the scenes every letter is stored as a small integer. In ASCII, 'A' is 65 and 'a' is 97. Uppercase and lowercase letters of the same pair differ by exactly 32, which is a single bit (bit 5). You will use this fact in the second example.
Digits: Characters Versus Numbers
The character '7' and the number 7 are different things. The character has a code (55 in ASCII); the number is a value. The C standard guarantees that the digit characters are consecutive, so '7' - '0' always gives 7 on any platform. That guarantee does not exist for letters on every encoding, so digit arithmetic is portable while letter arithmetic is only safe on ASCII-compatible systems.
Special Characters and What They Do in C
Instead of memorising a flat list of names, group the symbols by the role they play in the language.
| Role | Characters | Where you meet them |
|---|---|---|
| Arithmetic operators | + - * / % |
total = a + b; |
| Relational and logical | < > = ! & | |
if (x >= 10 && y != 0) |
| Bitwise | & | ^ ~ |
mask = flags & 0x0F; |
| Grouping delimiters | ( ) [ ] { } |
Calls, arrays, blocks |
| Separators | , ; : . |
Argument lists, statement ends, labels, struct members |
| Conditional operator | ? : |
max = (a > b) ? a : b; |
| Quoting | ' " |
'A' and "hello" |
| Escape introducer | |
"linen" (next lecture) |
| Preprocessor | # |
#include, #define |
| Name builder | _ |
sensor_value |
Symbols that are not in the basic set
Older textbooks list @, $ and the back-quote among the special characters. They are not part of the basic source character set and have no meaning in C syntax. You may still place them inside string literals and comments. GCC also accepts $ inside identifiers as an extension, but you should avoid it in portable code.
Whitespace: The Invisible Separator
Space, horizontal tab, vertical tab, form feed and newline are collectively called whitespace. Their main job is to separate tokens. The compiler needs int total with a gap, because without it the lexer would see one long name, inttotal. Beyond that gap, extra whitespace is ignored, which is why you can indent code freely.
The lexer uses a rule called maximal munch: it always builds the longest token it can. That is why a+++b is read as a++ + b and not a + ++b. Whitespace is your tool for steering the lexer when that matters.
Source Character Set Versus Execution Character Set
There are two character sets in play, and mixing them up is a classic interview trap.
| Aspect | Source character set | Execution character set |
|---|---|---|
| Where it applies | The text of your .c file |
Characters inside the running program |
| Minimum content | The 91 graphic characters plus whitespace | The same, plus control characters like newline and backspace |
| Example | Writing the two characters and n |
One newline character with code 10 |
| Chosen by | Compiler, editor encoding | Compiler and target platform |
On modern Linux both are usually ASCII-compatible UTF-8, so the difference feels invisible. On a microcontroller toolchain or a mainframe, it is real. This is also why the next lecture introduces escape sequences: they are the bridge from characters you can type to characters the program can use.
C Delimiters (Punctuators) in Detail
Delimiters give a program its structure. The standard calls them punctuators, but most books, and many interviewers, use the word delimiter.
| Delimiter | Name | Purpose | Example |
|---|---|---|---|
: |
Colon | Ends a label, separates the parts of ? :, follows case and default |
again: |
; |
Semicolon | Terminates a statement or declaration | total = 0; |
( ) |
Parentheses | Group expressions, enclose call arguments and conditions | if (x > 0) |
[ ] |
Square brackets | Declare and index arrays | marks[2] |
{ } |
Curly braces | Form a block, enclose initialisers and function bodies | { 70, 85 } |
# |
Hash | Begins a preprocessor directive | #define N 3 |
, |
Comma | Separates declarators and arguments; also an operator | int a, b; |
Hands-On Examples: Practise the C Character Set
Every example below was compiled with gcc -Wall -Wextra -std=c17 and the output shown is real. Type them in yourself rather than pasting; the muscle memory is part of the learning.
Example 1: Classify Every Character
Goal: walk through a string and count letters, digits, spaces and everything else. This shows the groups of the character set in action and introduces ctype.h.
#include <stdio.h>
#include <ctype.h>
int main(void)
{
const char *text = "Sensor#7 reads 42.5C @ 09:30!";
int letters = 0, digits = 0, spaces = 0, others = 0;
for (const char *p = text; *p != ''; p++) {
unsigned char ch = (unsigned char)*p;
if (isalpha(ch))
letters++;
else if (isdigit(ch))
digits++;
else if (isspace(ch))
spaces++;
else
others++;
}
printf("Text : %sn", text);
printf("Letters : %dn", letters);
printf("Digits : %dn", digits);
printf("Spaces : %dn", spaces);
printf("Others : %dn", others);
return 0;
}
Build and run:
gcc -Wall -Wextra -std=c17 classify.c -o classify
./classify
Expected output:
Text : Sensor#7 reads 42.5C @ 09:30!
Letters : 12
Digits : 8
Spaces : 4
Others : 5
- The cast to
unsigned charbefore callingisalphais deliberate. The ctype functions accept values in the unsigned range, and passing a negativecharis undefined behaviour. - The string contains
#,.,@,:and!, which makes the five “others”.
Try it yourself: change the string to your own name and a phone number
Replace the text with something like "Asha 98480-12345". You should see 4 letters, 10 digits, 1 space and 1 other (the hyphen). If your counts differ, check whether you accidentally typed a non-ASCII dash.
Example 2: Characters Are Small Numbers
Goal: prove that a character is an integer in disguise, and use that fact to change case and convert a digit to a value.
#include <stdio.h>
int main(void)
{
char upper = 'G';
char lower = upper ^ 0x20; /* flip bit 5 */
char digit = '7';
printf("'%c' has code %dn", upper, upper);
printf("'%c' has code %dn", lower, lower);
printf("Gap between 'a' and 'A' : %dn", 'a' - 'A');
printf("Digit '%c' is the number %dn", digit, digit - '0');
for (char c = 'A'; c <= 'E'; c++)
printf("%c=%d ", c, c);
printf("n");
return 0;
}
'G' has code 71
'g' has code 103
Gap between 'a' and 'A' : 32
Digit '7' is the number 7
A=65 B=66 C=67 D=68 E=69
upper ^ 0x20flips bit 5, turning'G'(71) into'g'(103). The same trick converts back.digit - '0'is the portable way to turn a digit character into its value.- The loop
for (char c = 'A'; c <= 'E'; c++)works because characters support arithmetic and comparison like any integer.
Try it yourself: convert ‘z’ to uppercase without using toupper
Use 'z' - 32 or 'z' ^ 0x20; both give 90, which is 'Z'. Remember this only works for letters, because applying the XOR to a digit or symbol produces a different, unrelated character.
Example 3: Special Characters as Operators
Goal: see the arithmetic, bitwise, logical and pointer roles of special characters in one run.
#include <stdio.h>
int main(void)
{
int a = 12, b = 5;
int *p = &a;
printf("a + b = %d, a - b = %dn", a + b, a - b);
printf("a * b = %d, a / b = %d, a %% b = %dn", a * b, a / b, a % b);
printf("a & b = %d, a | b = %d, a ^ b = %d, ~a = %dn", a & b, a | b, a ^ b, ~a);
printf("!a = %d, a > b = %d, a == b = %dn", !a, a > b, a == b);
printf("value through *p = %dn", *p);
printf("100%% donen");
return 0;
}
a + b = 17, a - b = 7
a * b = 60, a / b = 2, a % b = 2
a & b = 4, a | b = 13, a ^ b = 9, ~a = -13
!a = 0, a > b = 1, a == b = 0
value through *p = 12
100% done
- The percent sign has two jobs. In
a % bit computes the remainder. In aprintfformat string you must write%%to print a literal percent. - With a = 12 (binary 1100) and b = 5 (binary 0101), AND gives 0100 = 4, OR gives 1101 = 13, XOR gives 1001 = 9.
- The asterisk is multiplication in
a * bbut dereference in*p. The ampersand is bitwise AND between two values but address-of in&a. Context decides, which is why the lexer alone cannot understand a program.
Try it yourself: predict ~b before running
For b = 5 the answer is -6. The tilde flips every bit, and on a two’s complement machine flipping all bits of n gives -(n + 1).
Example 4: Delimiters at Work
Goal: use a colon label, square brackets, braces, commas, a semicolon and a hash directive in one small program.
#include <stdio.h>
#define LIMIT 3
int main(void)
{
int marks[LIMIT] = { 70, 85, 90 }, total = 0, i = 0;
again:
total += marks[i];
i++;
if (i < LIMIT)
goto again;
{
int average = total / LIMIT;
printf("total=%d average=%dn", total, average);
}
return 0;
}
total=245 average=81
#define LIMIT 3starts with a hash, so the preprocessor handles it before compilation.again:is a label: a name followed by a colon.goto again;jumps back to it, forming a loop.- The bare pair of braces creates an inner block whose variable
averageexists only inside it. - 245 divided by 3 is 81 in integer arithmetic; the fractional part is discarded.
Try it yourself: remove one semicolon and read the error
Delete the semicolon after total += marks[i]. GCC reports an “expected ‘;'” style error that names the token which followed. The compiler only realises something is wrong when that next token arrives, so the real fault is at the end of the previous line. Learning to look one line up is a real-world debugging skill.
Common Mistakes with the C Character Set
| Mistake | Symptom | Fix |
|---|---|---|
| Pasting curly quotes from a document | Error such as stray ‘342’ in program | Retype the quotes as plain " and ' |
Using 'abc' for a string |
Warning about a multi-character constant | Single quotes are for one character, double quotes for strings |
Writing = instead of == in a condition |
Condition always true or false | Compile with -Wall, which flags it |
Forgetting %% in printf |
Garbage or missing output | Double the percent sign |
Passing a plain char to isalpha |
Undefined behaviour for negative values | Cast to unsigned char |
Using @ or $ in code |
Stray character error | Keep them inside strings and comments |
Best Practices, Performance and Security Notes
- Save source files as UTF-8 without a byte-order mark and keep code itself pure ASCII; put non-ASCII text only in comments or in string data you intend to handle.
- Prefer
isalpha,isdigitandisspaceover hard-coded numeric ranges. They are clearer and respect the platform encoding. - Always compile with
-Wall -Wextra. Most character-level mistakes are caught as warnings. - Performance: character classification is cheap, usually a table lookup, so do not hand-roll range checks for speed.
- Security: when you validate input, decide explicitly which characters are allowed (an allow-list) instead of trying to block bad ones. Unexpected control characters in input are a common source of injection and log-forging bugs in embedded Linux services.
Summary and Key Takeaways
- The C character set is 52 letters, 10 digits, 29 graphic symbols and the whitespace characters.
- The lexer groups characters into tokens using maximal munch; whitespace separates tokens.
- Characters are small integers;
'7' - '0'is 7 and a case flip is a single XOR with 32 in ASCII. @,$and the back-quote are not in the basic source set.- Delimiters: colon for labels, semicolon to end statements, parentheses for grouping, brackets for arrays, braces for blocks, hash for the preprocessor, comma as a separator.
- Source and execution character sets are different ideas; escape sequences connect them.
Quiz: Test Your Understanding
Pick an answer first, then open the box to check.
Question 1
How many graphic characters are in the basic C source character set?
A) 52 B) 62 C) 91 D) 128
Show answer
C. 52 letters + 10 digits + 29 symbols = 91.
Question 2
Which of these is NOT part of the basic source character set?
A) # B) ~ C) @ D) _
Show answer
C. The at sign has no role in C syntax and is outside the basic set.
Question 3
What does '9' - '0' evaluate to?
Show answer
9, an int. Digit characters are guaranteed to be consecutive.
Question 4
Which delimiter ends a statement?
A) colon B) comma C) semicolon D) hash
Show answer
C, the semicolon.
Question 5
How is a+++b tokenised?
Show answer
As a ++ + b. Maximal munch grabs ++ first, so it means (a++) + b.
Question 6
What is the numeric difference between 'a' and 'A' in ASCII?
Show answer
32, which is bit 5.
Question 7
Which statement prints a literal percent sign?
A) printf("%"); B) printf("%%"); C) printf("%");
Show answer
B. The format parser treats a single percent as the start of a conversion.
Question 8
True or false: extra spaces between tokens change the meaning of a C program.
Show answer
False, with exceptions: spaces inside string literals are data, and a space may be needed to separate two tokens that would otherwise merge, as in int x.
Interview Questions on the C Character Set
Q1. What is the difference between a character constant and a string literal?
Show answer
'A' is a single character with type int in C and the value 65. "A" is an array of two chars, the letter and a terminating null. Confusing them is a classic bug, because passing 'A' where a char * is expected compiles with a warning and crashes at run time.
Q2. What is maximal munch?
Show answer
The lexer rule that, at each position, builds the longest sequence of characters that forms a valid token. It explains why x+++++y fails to compile: it tokenises as x ++ ++ + y, and the second ++ is applied to a non-lvalue.
Q3. Why cast to unsigned char before calling isalpha?
Show answer
The ctype functions take an int that must be representable as unsigned char or equal EOF. A plain char holding a byte above 127 may be negative on signed-char platforms, which makes the call undefined.
Q4. Is the comma a delimiter or an operator?
Show answer
Both. In declarations and function calls it separates items. In an expression such as x = (a++, b) it is the comma operator that evaluates the left side, discards it, and yields the right side.
Q5. Write a portable function that tells if a character is a hexadecimal digit without using ctype.
Show answer
Check three ranges: c >= '0' && c <= '9', c >= 'a' && c <= 'f', c >= 'A' && c <= 'F'. Digit ranges are guaranteed contiguous; letter ranges are contiguous in ASCII, which is why production code usually prefers isxdigit.
Q6. What is the difference between the source and execution character sets?
Show answer
The source set is what the compiler reads in your file; the execution set is what exists when the program runs. A backslash followed by n in source becomes a single newline in the execution set. The two may use different encodings when cross compiling.
Frequently Asked Questions
What is the C character set in simple words?
It is the list of characters you are allowed to type in a C program: letters, digits, a fixed group of symbols and whitespace.
Does C support Unicode characters in source code?
Modern compilers read UTF-8 files and allow non-ASCII text in strings and comments. Using non-ASCII letters in identifiers is allowed by the standard but poorly portable, so avoid it in embedded work.
Is the underscore a letter in C?
The standard lists it as a symbol, but for building identifiers it behaves like a letter, so it can start a name.
Why does my program fail with a stray character error?
Most often a smart quote, long dash or invisible non-breaking space was pasted from a document. Retype the line.
Are delimiters and operators the same thing?
No. Operators compute values; delimiters give structure. Some symbols, such as the comma and the colon, play both roles depending on context.
Is this free embedded systems course enough for interview preparation?
It is designed to be: each lecture pairs theory with runnable examples, a quiz and interview questions. Work through the examples by hand for the best result.
Conclusion
You have now seen the C character set from the compiler’s point of view: a stream of characters that is sliced into tokens, with whitespace as separators and delimiters as the structural glue. The programs you ran proved the key ideas: characters are numbers, the same symbol can have different jobs depending on context, and tiny character mistakes produce confusing errors. In the next lecture of this free embedded C programming course we move to escape sequences and trigraphs, which let a program use characters that cannot simply be typed.
Keep going with the free course
Finish the quiz, rerun each example with your own input, and continue with the next lecture on escape sequences.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

1 Comment