Variables and Numbers

A variable is a box with a name. You put stuff in it.

That’s it. When you write int x = 5;, you’re telling the computer: “I need a box. Call it x. Put the number 5 inside.”

Memory: A Quick Picture

At its core, your computer’s memory is just a massive array of bits - billions of tiny switches that can each be either 0 or 1. That’s all there is. Everything your computer does - every number, every letter, every image - is ultimately stored as patterns of zeros and ones.

But working with individual bits would be tedious, so we group them. By convention, we group 8 bits into a byte. This isn’t a law of physics - it’s a choice that became standard. Early computers used different groupings (6-bit bytes existed), but 8 bits won out and became nearly universal.

Think of memory as a very long row of numbered slots:

Address:  [0]   [1]   [2]   [3]   [4]   [5]   ...   [billions]
Content:  0xFF  0x00  0x42  0x1A  0x00  0x00  ...

Each slot:

  • Has a unique address (its position in the row, starting from 0)
  • Holds exactly one byte (8 bits, so values 0-255)
  • Is the smallest unit you can directly address

The word size is how many bits the processor naturally works with at once. On a 64-bit system, the processor prefers to grab 64 bits (8 bytes) in one operation. On older 32-bit systems, it was 32 bits (4 bytes). This affects how big pointers are and how the processor aligns data for efficiency - but as a beginner, just know that “64-bit” means the system’s natural chunk size is 64 bits.

When you create a variable, the computer reserves one or more of these byte-slots for you. An int might take 4 slots (4 bytes = 32 bits). A char takes just 1 slot. The variable’s name is your way of referring to that location without memorizing the address.

Integers

The most common type is int, short for integer. An integer is a number with no decimal point - positive, negative, or zero.

int score = 100;
int temperature = -5;
int year = 2024;

An int can hold numbers from about negative 2 billion to positive 2 billion. That’s enough for most things.

Need bigger numbers?

long big_number = 5000000000;  // "long" holds bigger values

If your number will never be negative, use unsigned:

unsigned int players = 42;  // Only zero or positive (0 to about 4 billion)

Integer Types and Their Ranges

Each integer type has a fixed size and a guaranteed minimum range. Signed types can hold negative and positive values. Unsigned types can only hold zero and positive values, but their maximum is higher.

Standard Integer Types

Quick summary: Don’t memorize these tables! Just know that int works for most numbers, long long is for huge numbers, and unsigned types only hold zero or positive values. Use the tables below as a reference when you need exact limits.

TypeSizeMinimum ValueMaximum Value
char1 byte-128127
unsigned char1 byte0255
short2 bytes-32,76832,767
unsigned short2 bytes065,535
int4 bytes*-2,147,483,6482,147,483,647
unsigned int4 bytes*04,294,967,295
long4+ bytes-2,147,483,6482,147,483,647 (minimum)
unsigned long4+ bytes04,294,967,295 (minimum)
long long8 bytes-9,223,372,036,854,775,8089,223,372,036,854,775,807
unsigned long long8 bytes018,446,744,073,709,551,615

*On most modern systems. The C standard only guarantees int is at least 16 bits.

Signed vs Unsigned

By default, integer types are signed (can be negative). Adding unsigned makes them only hold non-negative values:

int x = -5;           // Valid: signed by default
unsigned int y = -5;  // Bug: wraps to a huge positive number

Use unsigned when you know the value will never be negative (like counting items or measuring sizes).

Fixed-Width Types (C99)

These types guarantee an exact size no matter what computer you’re on. “Portable code” just means code that works the same way on different computers. You probably won’t need these right away, but they’re useful to know about.

To use them, add #include <stdint.h> at the top of your program:

TypeGuaranteed SizeRange
int8_texactly 8 bits-128 to 127
uint8_texactly 8 bits0 to 255
int16_texactly 16 bits-32,768 to 32,767
uint16_texactly 16 bits0 to 65,535
int32_texactly 32 bits-2,147,483,648 to 2,147,483,647
uint32_texactly 32 bits0 to 4,294,967,295
int64_texactly 64 bits-9.2×10¹⁸ to 9.2×10¹⁸
uint64_texactly 64 bits0 to 1.8×10¹⁹

Remember from earlier: a byte is 8 bits, so it can represent 2⁸ = 256 different values. That’s why uint8_t maxes out at 255 (0 through 255 is 256 values).

Data Widths: Words, Doublewords, and Beyond

You’ll encounter terminology that describes data by its width - how many bits it occupies. These terms come from processor architecture and appear in documentation, debuggers, and low-level code.

TermBitsBytesC Type (typical)Use Case
Bit11/8(bit fields)Single flag, on/off
Nibble41/2(none)One hex digit (0-F)
Byte81char, uint8_tASCII character, small number
Word162short, uint16_tOlder systems’ native size
Doubleword (DWORD)324int, uint32_t32-bit systems’ native size
Quadword (QWORD)648long long, uint64_t64-bit systems’ native size

Historical note: “Word” originally meant the processor’s native size - what it could handle in one operation. On early 16-bit systems, a word was 16 bits. When 32-bit processors arrived, rather than redefine “word,” we added “doubleword” (double a 16-bit word = 32 bits). Then “quadword” for 64 bits. This is why the terminology feels inconsistent - it’s layered history.

On modern 64-bit systems, the processor’s actual native word is 64 bits, but we still use the old terminology. You’ll see “DWORD” constantly in Windows programming, meaning a 32-bit value.

Characters: More Complex Than You’d Think

A char in C is exactly 1 byte (8 bits). This was perfect for ASCII, which only needed 7 bits for 128 characters (English letters, digits, punctuation).

But the world has more than 128 characters. Unicode aims to represent every writing system - over 140,000 characters and growing. One byte isn’t enough.

Key Unicode terminology:

  • Code point: A unique number assigned to each character in Unicode. Written as U+XXXX (e.g., U+0041 is ‘A’, U+1F600 is 😀). Unicode has room for over 1.1 million code points.

  • Rune: A term for a code point, coined by Ken Thompson in 1991 for Plan 9 (the same system where UTF-8 was invented). Originally a 16-bit value, now typically 32-bit to cover all Unicode. The term spread from Plan 9 into BSD, various Unix C libraries, Go, .NET, and Android. Not in the official Unicode spec (which uses “code point”), but widely used in systems programming.

  • Code unit: The fixed-size chunks an encoding uses. UTF-8 uses 8-bit code units (bytes). UTF-16 uses 16-bit code units. A single code point might need multiple code units.

  • Grapheme cluster: What humans perceive as a single “character.” The letter é could be one code point (U+00E9) or two (U+0065 ’e’ + U+0301 combining accent). Emoji like 👨‍👩‍👧 are actually multiple code points joined together.

EncodingCode Unit SizeC TypeNotes
ASCII8 bitscharEnglish only, 128 characters
UTF-88 bits (1-4 per code point)char[]Web standard, ASCII-compatible, variable width
UTF-1616 bits (1-2 per code point)uint16_t, wchar_t*Windows native, variable width
UTF-3232 bits (1 per code point)uint32_t, wchar_t*Fixed width, simple but uses 4x the space

*wchar_t (“wide character”) is 16 bits on Windows, 32 bits on Linux/Mac - another historical inconsistency.

The takeaway: when you see char, think “byte” not “character.” A single code point might need 1-4 bytes in UTF-8. And what looks like one character to a human (a grapheme cluster) might be multiple code points. We’ll cover strings properly later, but for now, know that text is more complicated than it looks.

Why This Matters

When you’re reading documentation or debugging, you’ll see things like:

  • “Returns a DWORD” - that’s a 32-bit unsigned integer
  • “Expects a QWORD-aligned address” - the address should be divisible by 8
  • “Store as wchar_t” - use wide characters (16 or 32 bits depending on platform)

Understanding these widths helps you pick the right type and understand what code is actually doing at the machine level.

What Happens When Numbers Get Too Big?

If you put a number that’s too big into a variable, it wraps around. Like a car’s odometer.

unsigned char small = 255;  // This is the maximum
small = small + 1;          // Add 1 more
printf("%d\n", small);      // Prints: 0 (it wrapped!)

When an odometer hits 999999, it rolls back to 000000. Same thing here.

This is called integer overflow. For unsigned types, overflow is “well-defined” - that just means the language rules tell you exactly what will happen. Unsigned numbers always wrap around predictably.

Warning about signed types: What happens when a signed integer overflows is undefined behavior in C. “Undefined behavior” means the language rules don’t say what should happen - it’s like a board game with no rule for a weird situation. The computer might do something reasonable, or it might do something completely unexpected. You can’t count on any specific result.

On most computers today (which use a system called two’s complement - a way of storing negative numbers in binary), you’ll see wrapping like this:

char x = 127;    // Maximum for signed char
x = x + 1;       // Add 1
// On MOST systems: x becomes -128
// But technically: the compiler can do ANYTHING - this is undefined behavior!

The lesson: don’t rely on signed overflow doing anything predictable. It’s a bug waiting to happen.

For most programs, int is big enough. Don’t worry about this unless you’re counting something huge.

Decimal Numbers

For numbers with decimal points, use double:

double pi = 3.14159;
double price = 19.99;
double temperature = 98.6;

Why “double”? It’s short for “double precision.” It can hold about 15 digits. There’s also float (about 7 digits), but double is usually better.

double average = 85.5;
printf("Average: %f\n", average);    // Prints: Average: 85.500000
printf("Average: %.1f\n", average);  // Prints: Average: 85.5 (one decimal)
printf("Average: %.2f\n", average);  // Prints: Average: 85.50 (two decimals)

Making Variables

Before you use a variable, you must declare it. Tell C what type it is and what to call it.

int age;         // Declare: "I need an integer called age"
age = 25;        // Assign: "Put 25 in age"

int height = 180;  // Declare and assign at the same time

You can make multiple variables of the same type:

int x, y, z;
int width = 10, height = 20;

Naming Rules

Variable names in C:

  • Must start with a letter or underscore (_)
  • Can contain letters, numbers, and underscores
  • Cannot have spaces or special characters
  • Are case-sensitive - this means uppercase and lowercase letters are treated differently (age and Age are different variables!)
  • Cannot be C keywords (like int, if, return)

Good names tell you what’s inside:

int player_score;     // Good: you know what it is
int ps;               // Bad: what does "ps" mean?
int numberOfLives;    // Also good (camelCase style)
int number_of_lives;  // Also good (snake_case style)

Short names like i, j, x, y are fine for simple counters.

Printing Variables

Use printf with special codes to print variables. These codes are called format specifiers - they tell printf what format the data is in (integer, decimal, character, etc.).

int age = 15;
double height = 5.5;
char grade = 'A';

printf("Age: %d\n", age);        // %d for integers
printf("Height: %f\n", height);  // %f for decimals
printf("Grade: %c\n", grade);    // %c for characters

The code tells printf what kind of value is coming:

  • %d - integer (the “d” stands for decimal integer, not double)
  • %f - floating-point (numbers with decimal points)
  • %c - character
  • %s - string (text) - we’ll learn this next time

You can print multiple values:

printf("I am %d years old and %.1f feet tall.\n", age, height);

Getting Input

Use scanf to read what the user types:

int age;
printf("How old are you? ");
scanf("%d", &age);
printf("You are %d years old!\n", age);

Important: See that & before age? You must include it. Always.

For now, just remember: scanf always needs the & before variable names. We’ll explain why in Part 10 when we learn about pointers.

Here’s a complete example:

#include <stdio.h>

int main(void) {
    int age;
    double height;

    printf("Enter your age: ");
    scanf("%d", &age);

    printf("Enter your height in feet: ");
    scanf("%lf", &height);  // Note: %lf for reading doubles

    printf("You are %d years old and %.1f feet tall.\n", age, height);

    return 0;
}

(When reading a double with scanf, use %lf. When printing, both %f and %lf work.)

Math

C can do arithmetic:

int a = 10;
int b = 3;

printf("%d + %d = %d\n", a, b, a + b);   // 10 + 3 = 13
printf("%d - %d = %d\n", a, b, a - b);   // 10 - 3 = 7
printf("%d * %d = %d\n", a, b, a * b);   // 10 * 3 = 30
printf("%d / %d = %d\n", a, b, a / b);   // 10 / 3 = 3 (not 3.33!)
printf("%d %% %d = %d\n", a, b, a % b);  // 10 % 3 = 1 (remainder)

Watch out for integer division! When you divide two integers, C gives you an integer back. 10 / 3 gives 3, not 3.333. The decimal part is thrown away.

To get a decimal result, make at least one number a double:

double result = 10.0 / 3;  // 3.333...
// OR
double result = (double)10 / 3;  // Same thing

Shortcuts

C has shortcuts for common operations:

int x = 10;

x = x + 5;  // x is now 15
x += 5;     // Same thing: add 5 to x

x = x - 3;  // x is now 12
x -= 3;     // Same thing: subtract 3

x = x * 2;  // x is now 18
x *= 2;     // Same thing: multiply by 2

x = x + 1;  // x is now 19
x++;        // Same thing: add 1

x = x - 1;  // x is now 18
x--;        // Same thing: subtract 1

Constants

Sometimes you have values that should never change. Use const:

const double PI = 3.14159;
const int MAX_PLAYERS = 4;

If you try to change a const variable, the compiler stops you. This prevents bugs.

Complete Example

#include <stdio.h>

int main(void) {
    int age = 15;
    double gpa = 3.7;
    char grade = 'A';

    // Do some math
    int years_to_18 = 18 - age;
    double needed_gpa = 4.0 - gpa;

    // Print everything
    printf("Age: %d\n", age);
    printf("Years until 18: %d\n", years_to_18);
    printf("GPA: %.1f\n", gpa);
    printf("Points to 4.0: %.1f\n", needed_gpa);
    printf("Current grade: %c\n", grade);

    return 0;
}

Try It Yourself

  1. Write a program that asks for two numbers and prints their sum, difference, and product
  2. Write a program that converts Celsius to Fahrenheit using: F = C × 9/5 + 32
  3. What’s the largest number you can store in an int? Try finding out!
  4. What happens if you divide 7 by 2 as integers? What about as doubles?

Common Mistakes

  • Integer division surprise: 5/2 is 2, not 2.5
  • Forgetting the & in scanf: scanf("%d", age) won’t work - needs &age
  • Overflow: Putting too big a number in too small a box
  • Using int for everything: Pick the right type for your data

Next Up

In Part 3, we’ll learn about characters and ASCII - how computers represent text as numbers.


Enjoyed This?

If this helped something click, subscribe to my YouTube channel. More content like this, same approach - making things stick without insulting your intelligence. It’s free, it helps more people find this stuff, and it tells me what’s worth making more of.