Elementary Overview
A C variable is a named object with a type. The type tells the compiler how the stored bits should be interpreted, what operations are valid, how much storage the object requires, and what range of values it can represent. This lesson continues directly from OSC.001: C Compiler and Toolchain and moves from building a C program to understanding the data inside it.
The central idea is simple: a variable name is not the value itself. It is a source-code name associated with an object whose bytes live somewhere in memory while the program runs. An int, unsigned int, and char can all occupy memory, but their types cause the compiler to interpret and manipulate those bits differently.
Learning Objectives
- Declare, define, initialize, assign, and read basic C variables.
- Distinguish
char,signed char,unsigned char,short,int,long, andlong long. - Explain what
signedandunsignedchange. - Use integer and character literals correctly.
- Use
sizeofand understand why its result has typesize_t. - Read integer limits from
<limits.h>instead of assuming machine-specific ranges. - Connect source-level variables to bytes, binary values, RAM, and CPU operations.
- Avoid common errors involving uninitialized variables, format specifiers, narrowing, and signed/unsigned comparisons.
Declaring and Initializing Variables
A declaration introduces a name and type. Initialization gives the object its first value. Assignment changes the value later. Keeping those ideas separate makes compiler diagnostics easier to understand.
int count = 42;
char grade = 'A';
unsigned int packets = 60000U;
count = 43;
In the example, count is an int, grade is a char, and packets is an unsigned int. The U suffix marks the integer literal as unsigned. The final statement is assignment, not initialization, because count already exists.
Initialize Automatic Variables Before Reading Them
An automatic local variable without an initializer does not automatically become zero. Reading an indeterminate value can produce meaningless output and can invoke undefined behavior. A beginner-safe habit is to initialize variables at the point where a sensible initial value is known.
int total = 0; // known initial value
int samples = 0; // known initial value
This rule becomes especially important in embedded systems, drivers, networking code, firmware, and hardware-control software because an uninitialized value may influence a register write, array index, packet length, or control decision.
C Integer Types Form a Family
C does not define one universal integer size. The language provides a family of integer types: char, short, int, long, and long long, together with signed and unsigned forms. The exact sizes are implementation-dependent within requirements imposed by the C standard.
signed char temperature_delta = -12;
unsigned char status_byte = 255;
short offset = -32000;
int count = 42;
unsigned int mask = 0xFFU;
long distance = 1000000L;
long long timestamp = 9000000000LL;
A common desktop system uses 8-bit bytes, 16-bit short, 32-bit int, and 64-bit long long, but portable C code should not hard-code those assumptions unless the target platform is intentionally fixed. When exact-width integers are required, later lessons will introduce <stdint.h> types such as uint32_t.
char and int data types with signed and unsigned modifiers.Signed Versus Unsigned
A signed integer type can represent negative and nonnegative values. An unsigned integer type represents only nonnegative values and therefore uses its available value bits for a range beginning at zero. Unsigned arithmetic also has precisely defined modulo behavior, which is useful for bit manipulation, counters, masks, checksums, hardware registers, and some low-level protocols.
Unsigned does not mean “better” or “safer.” Mixing signed and unsigned expressions can trigger conversions that surprise beginners. Compiler warnings should remain enabled so suspicious comparisons and conversions are visible during development.
gcc -std=c17 -Wall -Wextra -Wpedantic variables.c -o variables
Warnings are part of the engineering workflow. A clean build with strong warnings is more informative than a build that merely produces an executable.
char Is an Integer Type
A C char stores an integer value that is commonly used to represent a character code. The character literal 'A' is therefore connected to a numeric value in the execution character set. On ASCII-compatible systems, 'A' is 65, but portable source should normally use the character literal rather than hard-coding the numeric code when the intent is a character.
char grade = 'A';
printf("grade as character = %c\n", grade);
printf("grade as integer = %d\n", grade);
Plain char is a distinct type, and whether it behaves as signed or unsigned for values outside the basic character range depends on the implementation. Code that requires an explicitly signed one-byte integer-like type should use signed char; code that requires explicitly nonnegative byte values should use unsigned char.
char can be signed on one target and unsigned on another, and why portable code should avoid assuming its signedness.Variables Become Bytes in Memory
At runtime, a variable occupies storage and its value is encoded into bits. The CPU loads, stores, compares, shifts, adds, and otherwise manipulates those values through registers and memory operations. This connects C directly to lower-level topics such as memory-mapped I/O and device registers, DMA, and CPU cache.

sizeof Measures Storage in C Bytes
The unary sizeof operator reports the size of a type or object in C bytes. By definition, sizeof(char) is always 1. That does not by itself guarantee that a C byte contains exactly eight bits; CHAR_BIT from <limits.h> reports the number of bits in a byte for the implementation.
#include <stdio.h>
#include <limits.h>
int main(void) {
printf("sizeof(char) = %zu\n", sizeof(char));
printf("sizeof(short) = %zu\n", sizeof(short));
printf("sizeof(int) = %zu\n", sizeof(int));
printf("sizeof(long) = %zu\n", sizeof(long));
printf("sizeof(long long) = %zu\n", sizeof(long long));
printf("CHAR_BIT = %d\n", CHAR_BIT);
return 0;
}
The result of sizeof has type size_t. With printf, the portable conversion specifier for a size_t value is %zu. Using %d for a sizeof result can produce a format warning because int and size_t are not the same type.
sizeof.sizeof returns size_t, and platform differences can expose incorrect printf format assumptions.Use <limits.h> Instead of Guessing Integer Ranges
The C standard defines minimum capabilities and relationships among the integer types, but an implementation chooses actual widths within those rules. The header <limits.h> exposes limits such as INT_MIN, INT_MAX, UINT_MAX, LONG_MAX, and CHAR_BIT.
#include <stdio.h>
#include <limits.h>
int main(void) {
printf("INT_MIN = %d\n", INT_MIN);
printf("INT_MAX = %d\n", INT_MAX);
printf("UINT_MAX = %u\n", UINT_MAX);
return 0;
}
This is more reliable than assuming that every int is 32 bits or that every long is 64 bits. Those assumptions may be true on a specific workstation and false on another ABI, microcontroller, operating system, compiler target, or embedded toolchain.
int is architecture- and implementation-dependent rather than simply equal to the CPU’s word size.Integer Literals Also Have Types
Literal values written directly in source code have types too. Decimal 42 is an integer literal, 'A' is a character constant, 0x2A is a hexadecimal integer literal, and suffixes such as U, L, and LL influence integer literal types.
int decimal = 42;
int hexadecimal = 0x2A;
unsigned int mask = 0xFFU;
long distance = 1000000L;
long long ticks = 9000000000LL;
Choosing a suffix is not decoration. It can affect expression types, conversions, warnings, and whether a value is representable in the intended type.
A Complete Inspection Program
#include <stdio.h>
#include <limits.h>
int main(void) {
int count = 42;
unsigned int packets = 60000U;
char grade = 'A';
printf("count = %d\n", count);
printf("packets = %u\n", packets);
printf("grade = %c\n", grade);
printf("grade numeric value = %d\n", grade);
printf("sizeof(count) = %zu\n", sizeof(count));
printf("sizeof(packets) = %zu\n", sizeof(packets));
printf("sizeof(grade) = %zu\n", sizeof(grade));
printf("INT_MIN = %d\n", INT_MIN);
printf("INT_MAX = %d\n", INT_MAX);
printf("UINT_MAX = %u\n", UINT_MAX);
printf("CHAR_BIT = %d\n", CHAR_BIT);
return 0;
}
Compile with warnings enabled:
gcc -std=c17 -Wall -Wextra -Wpedantic variables.c -o variables
./variables
The exact sizes and limits printed by the program document the current compiler target. Running the same source on another architecture is a simple portability experiment.
Common Mistakes
- Reading an uninitialized automatic variable. Initialize it before use.
- Assuming
intis always 32 bits. Checksizeof(int),CHAR_BIT, and<limits.h>. - Printing
sizeofwith%d. Use%zu. - Assuming plain
charis always signed. Usesigned charorunsigned charwhen signedness is part of the requirement. - Ignoring warnings from signed/unsigned comparisons. Inspect the conversion rather than silencing the warning blindly.
- Choosing a type only by habit. Select a type whose range and semantics match the data.
Exercises
- Create variables of type
char,short,int,long, andlong long. Print the result ofsizeoffor each. - Print
CHAR_BIT,INT_MIN,INT_MAX, andUINT_MAX. - Create
char symbol = 'A';and print it once with%cand once with%d. - Compile a program containing an uninitialized local integer with
-Wall -Wextra. Observe which warnings appear when the variable is used. - Create a signed integer and an unsigned integer, compare them, and study any compiler warning before changing the program.
- Run the inspection program on two different systems or compiler targets and record any type-size differences.
Knowledge Check
- What information does a C variable’s type provide to the compiler?
- What is the difference between initialization and assignment?
- Why is
unsigned intnot simply a larger version ofint? - Is plain
charguaranteed to be signed? - What does
sizeof(char)always equal? - What type does
sizeofproduce? - Which
printfconversion is normally used forsize_t? - Which standard header exposes
INT_MAXandUINT_MAX? - Why should code avoid assuming
intis always 32 bits? - What does the
Usuffix mean on an integer literal?
Answers
- It defines how the object’s stored bits are interpreted, the operations permitted, storage requirements, and representable values.
- Initialization establishes the first value when an object is created; assignment stores a new value into an existing object.
- Unsigned changes the represented value set and arithmetic rules, including modulo behavior; it is not merely a larger signed integer.
- No. Plain
charmay behave as signed or unsigned depending on the implementation. - Exactly 1 C byte.
size_t.%zu.<limits.h>.- The C implementation chooses actual integer widths within language requirements, and different targets use different ABIs and architectures.
- It requests an unsigned integer literal type according to the literal-selection rules.
Next Step
This lesson establishes the typed integer foundation needed for expressions and operators. The next C lesson can build on it with arithmetic, integer promotions, conversions, overflow behavior, and operator precedence while preserving the 000-level foundational progression.
BitcoinVersus.Tech Editor’s Note: Examples intentionally use portable inspection techniques rather than assuming one CPU, operating system, ABI, or compiler target.
Support independent BitcoinVersus.Tech technical education with Bitcoin: 3C9o19EH5HSiwEPyCTmEKzxhNCbo2X6TTb
BitcoinVersus.Tech educational material is provided for learning and engineering reference.

Leave a Reply