Easier to read with cream paper, a tint, more contrast or larger text? Choose your colours and text, once, for every page.

Free lesson

How C stores variables in memory

After this lesson: I can say how big a variable is and where it sits in memory.

50 min lesson6 commandsCompilers and computer architecture lab: gcc, flex, bison and RISC-V in one VM

A variable is bytes at an address

A variable in C is a name for a few bytes at one place in memory. Knowing how many bytes it takes and where it sits explains most of the surprises in C programs.

A game keeps a player's score in an int. The score lives in four bytes side by side, and the first of them has an address. Every later idea in this module builds on that picture.

By the end of this lesson

Note

I can say how big a variable is and where it sits in memory.

Key words

WordWhat it meansExample
Byteeight bits, the smallest piece of memory with an address
Addressthe number that says where a byte sits in memory
sizeofan operator that gives a size in bytessizeof(int)
Structa group of named fields stored in one block
Alignmentthe rule that a value starts at a multiple of its size
Paddingunused bytes the compiler adds to keep alignment

The cells students fill in are left blank in this preview.

Every type has a size in bytes

Memory is a long row of bytes, and each byte has an address, a number that counts from the start. A variable's type says how many bytes it takes. The operator sizeof gives that number.

The table shows the common sizes on 64-bit Linux, the system in the lab. They are the same on an Intel and an Arm processor.

Sizes of the common types

TypeBytesHolds
char1One letter or a small whole number
short2A small whole number
int4A whole number
long8A large whole number
double8A number with a fraction
pointer8An address

The cells students fill in are left blank in this preview.

Write and build a size checker

Command
printf '%s\n' '#include <stdio.h>' 'int main(void) {' '  printf("char %zu\n", sizeof(char));' '  printf("short %zu\n", sizeof(short));' '  printf("int %zu\n", sizeof(int));' '  printf("long %zu\n", sizeof(long));' '  printf("double %zu\n", sizeof(double));' '  printf("pointer %zu\n", sizeof(int *));' '  return 0;' '}' | gcc -Wall -x c - -o sizes

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Print the size of each type

Command
./sizes

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Every byte has its own address

Neighbouring bytes have neighbouring addresses. An int takes four bytes, so the next int in an array starts four addresses later. The address of a value is the address of its first byte.

The bytes of one number also have an order. On the lab's processors the smallest part comes first, which is called little-endian.

Worked example: 258 in four bytes

ByteValueEach unit is worth
Byte 021
Byte 11256
Byte 2065,536
Byte 3016,777,216
Total2 + 256 = 258

The cells students fill in are left blank in this preview.

Look at the bytes of one number

Code: c
#include <stdio.h>

int main(void) {
  int n = 258;
  // &n is the address of n. This reads the same four bytes one at a time.
  unsigned char *p = (unsigned char *)&n;
  for (int i = 0; i < 4; i++) printf("byte %d is %d\n", i, p[i]);
  int row[3] = {7, 8, 9};
  // How many bytes apart are two neighbours?
  printf("gap %ld\n", (char *)&row[1] - (char *)&row[0]);
  return 0;
}

Write and build the byte viewer

Command
printf '%s\n' '#include <stdio.h>' 'int main(void) {' '  int n = 258;' '  unsigned char *p = (unsigned char *)&n;' '  for (int i = 0; i < 4; i++) printf("byte %d is %d\n", i, p[i]);' '  int row[3] = {7, 8, 9};' '  printf("gap %ld\n", (char *)&row[1] - (char *)&row[0]);' '  return 0;' '}' | gcc -Wall -x c - -o bytes

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Show the four bytes and the gap

Command
./bytes

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Padding keeps values aligned

A processor reads a four-byte int fastest when its address is a multiple of four. So the compiler follows an alignment rule: a value starts at a multiple of its own size.

When a small field comes before a big one in a struct, the compiler leaves padding, unused bytes, to keep the rule. It also pads the end so every struct in an array stays aligned.

Worked example: three fields, twelve bytes

BytesWhat is there
0Field a, one char
1 to 3Padding, so that b can start at 4
4 to 7Field b, one int
8Field c, one char
9 to 11Padding, so the whole struct is a multiple of 4

The cells students fill in are left blank in this preview.

The same fields in two orders

Code: c
struct loose { char a; int b; char c; };
struct tight { int b; char a; char c; };

// loose is 12 bytes. tight holds the same fields in 8.

Write and build the struct test

Command
printf '%s\n' '#include <stdio.h>' '#include <stddef.h>' 'struct loose { char a; int b; char c; };' 'struct tight { int b; char a; char c; };' 'int main(void) {' '  printf("loose %zu, offset of b %zu, offset of c %zu\n", sizeof(struct loose), offsetof(struct loose, b), offsetof(struct loose, c));' '  printf("tight %zu\n", sizeof(struct tight));' '  return 0;' '}' | gcc -Wall -x c - -o pad

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Print both sizes and two offsets

Command
./pad

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

A common mistake

Warning

Many people think a struct is as big as its fields added up. It is often bigger. The compiler adds padding to keep each field aligned. Putting the biggest fields first usually removes most of it.

About this lab and its source: Compilers and computer architecture lab: gcc, flex, bison and RISC-V in one VM

Teaching a class? Set this module for your students with a teacher guide, marked assignments and a lab for every student.