What is a variable?

Real-World Analogy

Imagine you have a set of labeled boxes on a shelf. Each box has a sticker on the front (the variable name) and holds something inside (the value). In C, you must also write on the box exactly what type of thing it can hold — a whole number, a decimal number, a single letter, and so on. You cannot put a decimal number in a "whole numbers only" box.

In Python and Java, you have likely written things like x = 5 or int x = 5. In C, it works very similarly — but there are important differences you must understand.

C is statically typed. This means every variable must have its type declared before you use it — and that type cannot change. In Python, you can write x = 5 and then later x = "hello" — Python lets you switch types freely. C does not. Once you say a variable holds an integer, it will always hold an integer.

Why does C do this? Because C compiles directly to machine code that runs on the CPU. The CPU needs to know exactly how many bytes to set aside in memory for each variable. An integer takes 4 bytes, a character takes 1 byte, and a decimal number takes 8 bytes. Knowing the type up front means C can lay out memory perfectly efficiently.

You declare a variable with this pattern: type, then name, then semicolon. You can also give it an initial value immediately. That is called initialization.

Python / Java (what you know)
# Python — no type needed
x = 42
name = "Alice"
height = 1.75
x = "changed"  # totally fine

// Java — type declared
int x = 42;
String name = "Alice";
double height = 1.75;
C (what you are learning)
/* C — type always required */
int x = 42;
char name[6] = "Alice";
double height = 1.75;

/* x = "changed"; would be a
   COMPILE ERROR in C */
C has no String type

Java has String. Python has str. C has neither. Strings in C are arrays of characters ending with a special null character \0. You will learn all about this in Topic 09 — for now, just be aware there is no String keyword in C.

Where do variables live? Each variable you declare inside a function lives on the stack — a region of memory automatically managed by C. When your function returns, all its local variables are automatically erased. You will explore the stack in depth in Topic 12 (Memory Management).

Variables declared outside all functions are called global variables. They live for the entire life of the program and are accessible from anywhere in the code.

You got the big idea!

A variable is a named slot in memory. In C, you must declare the type so the compiler knows how big the slot needs to be. The type never changes. That is it — you just understood the foundation of C.

Declaring and initializing variables

Here is the annotated syntax for the two most common patterns — declaration only, and declaration with initialization:

int  age;
 ^      ^   ^
 |      |   └── semicolon: every C statement ends with this — don't forget it!
 |      └────── the variable name — you choose this (must start with a letter or _)
 └──────────── the type — tells C this variable holds a whole number (4 bytes)

int  age  =  25;
 ^      ^    ^    ^   ^
 |      |    |    |   └── semicolon
 |      |    |    └────── the initial value stored in the variable
 |      |    └─────────── assignment operator — stores the value into the variable
 |      └──────────────── variable name
 └─────────────────────── type

double price = 9.99;   // double: 8-byte decimal number
char   grade = 'A';    // char: a single character, always in single quotes
float  pi    = 3.14f;  // float: 4-byte decimal (the 'f' suffix marks it as float)
Key rules: (1) Type comes before the name. (2) Every statement ends with ;. (3) Characters use single quotes 'A', not double quotes. (4) If you declare without initializing, the value is garbage — whatever bytes happened to be in that memory location. Always initialize.

All C Data Types — Quick Reference

Type Size (typical) What it stores Range / Example Usage
char 1 byte A single ASCII character or small integer char c = 'A'; Characters, small counters
unsigned char 1 byte Positive-only small integer 0 to 255 Raw bytes, pixel values
short 2 bytes Small whole number −32,768 to 32,767 Memory-constrained situations
int 4 bytes Whole number (the go-to type) −2,147,483,648 to 2,147,483,647 Counters, indices, most integers
unsigned int 4 bytes Positive-only whole number 0 to 4,294,967,295 Sizes, counts (never negative)
long 8 bytes (64-bit) Large whole number ±9.2 × 10¹⁸ File sizes, large counters
float 4 bytes Decimal number (less precise) float x = 3.14f; When memory matters more than precision
double 8 bytes Decimal number (more precise) double x = 3.14159; Default for floating-point (prefer this)
Sizes are not guaranteed!

The sizes above are typical for a 64-bit Linux/macOS system — which is what you use in this course. But strictly speaking, C's standard only guarantees minimum sizes. On some embedded platforms an int might be 2 bytes. Solution: include <stdint.h> and use int32_t (exactly 32 bits), int64_t, uint8_t etc. when you need a guaranteed size.

Signed vs Unsigned — what does it mean?

By default, all integer types in C are signed — they can hold both positive and negative values. The top bit of the byte is used as a "sign bit" (0 = positive, 1 = negative).

Adding unsigned removes the sign bit, doubling the positive range. Use unsigned when you know a value can never be negative (array sizes, counts, etc.). This matters because mixing signed and unsigned in comparisons causes subtle bugs — a topic covered in depth in Topic 30 (Security).

The sizeof operator

sizeof(type) tells you exactly how many bytes a type or variable takes on your machine. It returns a value of type size_t (which you print with %zu). Use it whenever you need to know the byte size at runtime — never hard-code sizes.

size_t s = sizeof(int);   // s == 4 on 64-bit
printf("%zu\n", sizeof(double));  // prints 8

int x = 42;
printf("%zu\n", sizeof(x));    // also 4 — sizeof works on variables too

// ^  always use %zu (size_t format) not %d for sizeof results

Complete programs you can compile and run

Example 1 — Declare, initialize, print, and reassign variables Week 1 Lecture
#include <stdio.h>    /* Include standard I/O so we can use printf */

int main(void) {      /* int main: the function that runs when the program starts */
                      /* void: this main takes no command-line arguments */

    /* --- DECLARATION ONLY (value is garbage until assigned!) --- */
    int age;          /* declares a slot for a whole number called 'age' */
    age = 20;         /* now we store 20 into it */

    /* --- DECLARATION WITH INITIALIZATION (preferred) --- */
    double height = 1.75;   /* decimal number, initialized immediately */
    char   grade  = 'B';    /* a single character — note single quotes */
    int    score  = 95;

    /* --- PRINTING --- */
    printf("Age:    %d\n",   age);     /* %d = print an integer */
    printf("Height: %f\n",   height);  /* %f = print a double */
    printf("Grade:  %c\n",   grade);   /* %c = print a character */
    printf("Score:  %d\n",   score);

    /* --- REASSIGNMENT --- */
    score = 100;      /* store a new value — old value (95) is gone forever */
    printf("New score: %d\n", score);

    /* --- sizeof --- */
    printf("int is %zu bytes\n",    sizeof(int));    /* 4 */
    printf("double is %zu bytes\n", sizeof(double)); /* 8 */
    printf("char is %zu bytes\n",   sizeof(char));   /* 1 */

    return 0;   /* 0 means the program ran successfully */
}
Expected Output
Age: 20
Height: 1.750000
Grade: B
Score: 95
New score: 100
int is 4 bytes
double is 8 bytes
char is 1 bytes
Example 2 — Unsigned types, signed/unsigned behaviour, and stdint.h Week 1 Lecture + Lesson
#include <stdio.h>
#include <stdint.h>   /* For portable exact-width types: int32_t, uint8_t, etc. */

int main(void) {

    /* --- SIGNED vs UNSIGNED --- */
    int   signed_num   = -42;          /* can hold negative numbers */
    unsigned int pos   = 4294967295u;  /* u suffix = unsigned literal */

    printf("Signed:   %d\n",  signed_num);
    printf("Unsigned: %u\n",  pos);    /* %u = unsigned int format */

    /* --- OVERFLOW DEMO (signed vs unsigned wrap) --- */
    /* What happens when unsigned goes below 0? It wraps around! */
    unsigned int wrap = 0;
    wrap = wrap - 1;   /* subtracting 1 from 0 wraps to 4294967295 */
    printf("0 - 1 (unsigned): %u\n", wrap);  /* prints 4294967295 */

    /* --- PORTABLE EXACT-WIDTH TYPES --- */
    int32_t  a = 2147483647;  /* exactly 32 bits, signed — always! */
    uint8_t  b = 255;         /* exactly 8 bits, unsigned (0..255) */
    int64_t  c = 9000000000LL;/* exactly 64 bits — note the LL suffix for large literals */

    printf("int32_t max: %d\n",    a);
    printf("uint8_t max: %hhu\n",  b);  /* %hhu = unsigned char format */
    printf("int64_t big: %lld\n",  c);  /* %lld = long long format */

    return 0;
}
Expected Output
Signed: -42
Unsigned: 4294967295
0 - 1 (unsigned): 4294967295
int32_t max: 2147483647
uint8_t max: 255
int64_t big: 9000000000
NEVER use Variable-Length Arrays (VLAs)

C99 allows int arr[n]; where n is a variable. Do not use this in COMP2017. The stack is small. If n depends on user input, a large value will crash the program. Use malloc() instead (Topic 12). You will lose marks for VLAs in assessments. Use the -Wvla compiler flag to catch them: gcc -Wvla yourfile.c -o yourfile

Practice problems from lectures, tutorials & exam

Problem 1 — Predict the output From: Week 2 Tutorial

What does the following C program print? Explain each output line.

#include <stdio.h>

int main(void) {
    char c = 'Z';
    int  n = c;
    printf("%c\n", c);
    printf("%d\n", c);
    printf("%d\n", n);
    return 0;
}
/* OUTPUT:
   Z
   90
   90
*/
Explanation: In C, char is just a small integer. The character 'Z' is stored as its ASCII value, which is 90.

Line 1: printf("%c\n", c) — the %c format specifier tells printf to interpret the value as a character and print its symbol → prints Z.

Line 2: printf("%d\n", c) — the %d format specifier treats the same value as a decimal integer → prints 90 (the ASCII code of 'Z').

Line 3: int n = c copies the value 90 into an int. %d prints 90.

Key takeaway: char and small integers are interchangeable in C at the hardware level. The format specifier you use in printf controls how the value is displayed.
Problem 2 — Fix the declaration errors From: Week 2 Tutorial

The following code has three errors. Identify each error and write the corrected version.

#include <stdio.h>

int main(void) {
    int x = 3.14;         /* line 1 */
    char letter = "A";    /* line 2 */
    int count              /* line 3 */
    printf("%d %c %d\n", x, letter, count);
    return 0;
}
#include <stdio.h>

int main(void) {
    double x = 3.14;      /* Fix 1: use double, not int, for a decimal */
    char letter = 'A';    /* Fix 2: single quotes for a char, not double */
    int count = 0;        /* Fix 3: missing semicolon + always initialize */
    printf("%lf %c %d\n", x, letter, count);
    return 0;
}
Error 1: int x = 3.14 — an int cannot hold a decimal value. The .14 part is silently truncated, so x becomes 3. Fix: use double x = 3.14.

Error 2: char letter = "A" — double quotes create a string (an array of characters), not a single character. A char must be assigned with single quotes: 'A'.

Error 3: int count — missing the semicolon ; that must end every C statement. Also, count is uninitialized, meaning it holds a garbage value. Always initialize to a known value.
Problem 3 — Temperature conversion program From: Week 1 Lecture (Demo)

Write a C program that reads a Fahrenheit temperature as an integer from the user, converts it to Celsius using the formula C = (F − 32) × 5 / 9, and prints the result as an integer.

#include <stdio.h>

int main(int argc, char **argv) {
    int ftemp;   /* declare the variable BEFORE using it */

    printf("Please enter a fahrenheit temperature: ");

    /* scanf reads from the keyboard. &ftemp is the ADDRESS of ftemp —
       scanf needs the address so it can write the value directly into
       the variable. You will learn WHY this is needed in Topic 08 (Pointers). */
    scanf("%d", &ftemp);

    /* Integer arithmetic: multiply before dividing to reduce truncation error.
       (ftemp - 32) * 5 / 9  — note: parentheses first, then * left-to-right */
    printf("%d fahrenheit is %d centigrade\n",
           ftemp, (ftemp - 32) * 5 / 9);

    return 0;
}
Key decisions:
1. We use int ftemp because the problem says "integer". If we wanted decimal precision, we would use double.
2. scanf("%d", &ftemp) — the & gives scanf the address of the variable. This is a preview of pointers — scanf needs to know where to write the value in memory. Don't worry about the details yet; just remember the pattern: when reading with scanf, always put & before the variable name (except for strings/arrays).
3. The formula multiplies by 5 first, then divides by 9. This is important because integer division discards the remainder — if we divided first we would lose precision.
Problem 4 — Spot the Bug: Unsigned underflow From: Exam Practice / Cheatsheet "UB gotchas"

The following loop is intended to count down from 5 to 0. It runs forever instead. Why, and how do you fix it?

#include <stdio.h>

int main(void) {
    unsigned int i;
    for (i = 5; i >= 0; i--) {
        printf("%u\n", i);
    }
    return 0;
}
#include <stdio.h>

int main(void) {
    /* FIX: use signed int so i can go negative and the loop can exit */
    int i;
    for (i = 5; i >= 0; i--) {
        printf("%d\n", i);
    }
    return 0;
}
/* Output: 5, 4, 3, 2, 1, 0 — then stops correctly */
The bug: unsigned int can never be negative. When i reaches 0 and the loop does i--, instead of becoming -1 and failing the i >= 0 check, it wraps around to 4,294,967,295 (the largest unsigned int). This is called unsigned underflow or unsigned wraparound. The condition i >= 0 is always true for an unsigned int (0 is always ≥ 0), so the compiler may even warn you about this.

Fix: Change unsigned int i to int i. A signed int can go to -1, which fails the i >= 0 check correctly and stops the loop.

Rule: Never use unsigned for loop counters that count down to zero.
Problem 5 — What is the output? (static variables) From: 2024 Final Exam Q2

What is the expected output of the following code? This question is worth 1 mark.

void foo(int x) {
    if (x < 5) {
        static int y = 5;  /* static local variable */
        x = y + x;
        printf("%d, ", x);
        y += 1;
    }
}

int main(void) {
    for (int i = 7; i >= 0; i--)
        foo(i);
    return 0;
}
/* OUTPUT:
   9, 9, 9, 9, 9,
*/
Key concept — static local variables:
A static local variable is initialized once at program start (not re-created on each call) and its value persists between calls. Here, static int y = 5 means y starts at 5 and carries its updated value into every subsequent call to foo.

Step 1 — Identify which calls enter the if:
The loop runs i = 7, 6, 5, 4, 3, 2, 1, 0. The body of foo only executes when x < 5, so foo(7), foo(6), and foo(5) do nothing. The five calls that produce output are foo(4) through foo(0).

Step 2 — Execution trace:
Call # x passed y at entry x = y + x output y after call
1455 + 4 = 99,6
2366 + 3 = 99,7
3277 + 2 = 99,8
4188 + 1 = 99,9
5099 + 0 = 99,10
Why is every output 9? Each call, x decreases by 1 while y increases by 1 — the two changes cancel exactly, so y + x is always 9.

Output: 9, 9, 9, 9, 9,

Integer Literals & Memory Representation

C lets you write integer constants in four number bases. The base you choose is purely for human convenience — the compiler produces the same machine code either way. But the syntax matters: get it wrong and you silently get the wrong value.

Binary, Hex, Octal & Decimal C Literal Syntax

Base Prefix / Convention Example literal Decimal value Notes
Decimal none int x = 42; 42 The default. No prefix.
Hexadecimal 0x or 0X int x = 0xFF; 255 Digits 0–9, A–F. Common for addresses, bitmasks, colour values.
Hexadecimal 0x int x = 0xDEADBEEF; 3735928559 Classic debug sentinel value. Fits in a 32-bit unsigned int.
Octal leading 0 int x = 0755; 493 Used for Unix file permissions (chmod). A lone leading zero makes it octal — not decimal!
Binary 0b or 0B int x = 0b1010; 10 C99+ GCC extension. Digits 0–1 only. Useful for bitmasks. Not in the C standard before C23 — prefer hex in portable code.
The Leading-Zero Octal Gotcha

Writing int x = 010; looks like ten, but it is octal 10 = decimal 8. This is one of C's oldest traps. Never pad integers with leading zeros unless you actually mean octal. The compiler will not warn you — it silently does the wrong thing.

Literal syntax — same value, four bases Week 2 Lecture (p.4)
#include <stdio.h>

int main(void) {
    int a = 255;        /* decimal   */
    int b = 0xFF;       /* hex       */
    int c = 0377;       /* octal     */
    int d = 0b11111111; /* binary (GCC extension) */

    /* All four variables hold the same value: 255 */
    printf("%d %d %d %d\n", a, b, c, d);   /* 255 255 255 255 */

    /* printf can print in different bases too */
    printf("hex: %x  octal: %o  decimal: %d\n", a, a, a);
    /* hex: ff  octal: 377  decimal: 255 */

    /* The octal gotcha */
    int x = 010;   /* THIS IS 8, NOT 10 */
    printf("010 in decimal is: %d\n", x);  /* prints 8 */

    return 0;
}
Expected Output
255 255 255 255
hex: ff  octal: 377  decimal: 255
010 in decimal is: 8

Two's Complement — How Negative Integers Are Stored

Signed integers in C use two's complement encoding. The key insight is that -1 is stored as all bits set to 1. For a 32-bit int, that is 0xFFFFFFFF. Two's complement is why unsigned arithmetic wraps rather than going negative — there is no separate encoding for negatives; it is all in how the bits are interpreted.

The practical consequences you need to know right now:

int x = -1;
/* stored in memory as: 1111 1111 1111 1111 1111 1111 1111 1111 */
/* = 0xFFFFFFFF                                                  */

/* Reinterpret those same bits as unsigned: */
printf("%u\n", (unsigned int)-1);
/* prints: 4294967295  (2^32 - 1)                                */

int y = -128;   /* for an int8_t: stored as 1000 0000 = 0x80 */
int z =  127;   /* for an int8_t: stored as 0111 1111 = 0x7F */
Why does this matter? Mixing signed and unsigned types in comparisons causes subtle bugs — the signed value is silently converted to unsigned first, turning negative numbers into enormous positive values. See Problem 4 above and Topic 30 (Security) for exploits this enables. For the full theory of two's complement arithmetic, see Topic 32 — Number Systems.

Endianness — Byte Order in Memory

When you store a multi-byte integer like int x = 0x12345678;, the four bytes (0x12, 0x34, 0x56, 0x78) must go into four consecutive memory addresses. The question is: which byte goes first?

  • Little-endian — lowest byte at the lowest address. Used by x86/x64 (your laptop, the COMP2017 servers).
  • Big-endian — highest byte at the lowest address. Used by network protocols (TCP/IP), some embedded chips.

This usually does not matter within a single program on one machine. It matters when you read a binary file written on a different architecture, or send integers over a network — you must convert between host byte order and network byte order.

Memory layout of int x = 0x12345678; on a little-endian (x86) machine:

Address Byte stored Role
0x100 0x78 Least-significant byte (stored first on little-endian)
0x101 0x56
0x102 0x34
0x103 0x12 Most-significant byte (stored last on little-endian)
Quick memory test

Cast a multi-byte integer to unsigned char * and print each byte — you will see the little-endian order directly. This technique is used in binary file parsing and network code. Full theory and conversion functions (htonl, ntohl) are covered in Topic 32 — Number Systems.

IEEE 754 — How float and double Work in C

C's float (32-bit) and double (64-bit) types use the IEEE 754 floating-point standard. IEEE 754 stores numbers as a sign bit, an exponent, and a fraction (mantissa). This representation is an approximation — most decimal fractions cannot be represented exactly in binary, just as 1/3 cannot be written exactly in decimal.

The most famous consequence: 0.1 + 0.2 does not equal 0.3 in C.

Floating-point comparison gotcha Week 2 Lecture (p.11–12)
#include <stdio.h>
#include <math.h>   /* for fabs() — compile with -lm */

int main(void) {
    double a = 0.1;
    double b = 0.2;
    double c = 0.3;

    /* BAD: never compare floats with == */
    if (a + b == c) {
        printf("equal\n");       /* this line is NEVER reached */
    } else {
        printf("not equal\n");   /* this prints instead — floating point surprise! */
    }

    /* Print the actual stored values to see why */
    printf("0.1 + 0.2 = %.17f\n", a + b);  /* 0.30000000000000004 */
    printf("0.3       = %.17f\n", c);       /* 0.29999999999999999 */

    /* CORRECT: use an epsilon (tolerance) comparison */
    if (fabs((a + b) - c) < 1e-9) {
        printf("close enough — treated as equal\n");
    }

    return 0;
}
Expected Output
not equal
0.1 + 0.2 = 0.30000000000000004
0.3 = 0.29999999999999999
close enough — treated as equal
Rule: Never compare floats with ==

Always use a small tolerance: fabs(a - b) < 1e-9. The exact threshold depends on your use case — 1e-9 is typical for double. Using == on floats is a logic bug that the compiler will not catch. The full IEEE 754 bit layout (sign + exponent + mantissa) is covered in Topic 32 — Number Systems.

Quick Check — The Octal Gotcha

What does int x = 010; equal in decimal? Week 2 Lecture (p.4) — common exam trap

A student writes the following, intending to store the number ten:

int x = 010;
printf("%d\n", x);

What does it actually print? Choose one:

A) 10
B) 8  ✓ Correct — a leading zero makes it octal. Octal 10 = 1×8 + 0×1 = 8.
C) 2 (treating it as binary)
D) Compile error
Explanation

In C, any integer literal that starts with 0 (and is not 0x or 0b) is interpreted as octal (base 8). So 010 means 1×8 + 0 = 8, not ten. This silently produces the wrong value with no warning — making it one of the most dangerous C gotchas for beginners. Never pad a decimal number with leading zeros.

Key terms — flip to reveal the answer

Card 1 of 10
Question — click to flip
Answer
Click the card to flip

Test your understanding

Variables & Data Types Quiz 0 / 0 answered