JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

Arrays, Strings, and Structs

So far each variable held one value. Now we group many of the same thing into an array, discover that a C string is just an array of characters with a quiet rule at its end, and bundle different things together into a struct. Three ways to build bigger shapes out of the simple boxes you already know.

From one box to a row of boxes

Every variable you have met so far holds exactly one value — one `int`, one `double`, one `char`. But programs constantly need many of the same thing: ten test scores, a thousand pixels, the bytes of a message. An array is C's answer: a single name for a contiguous row of boxes, all the same type, laid out back to back in memory. Write `int scores[5];` and you get five `int` boxes in a row, and write `int scores[5] = {90, 85, 100, 70, 60};` to fill them at birth.

You reach into the row by index, written in square brackets: `scores[0]` is the first box, `scores[1]` the second, and `scores[4]` the last. The single most important thing to burn in now is that C counts from zero. An array of length 5 has valid indices 0, 1, 2, 3, 4 — there is no `scores[5]`. The index is really a distance from the start: `scores[0]` is zero boxes along, `scores[3]` is three boxes along. That picture will matter enormously when pointers arrive, because indexing turns out to be arithmetic on addresses underneath.

How big is it? sizeof and a row in memory

Because an array is just N boxes of the same type laid end to end, its total size is wonderfully predictable: `sizeof(scores)` is `5 * sizeof(int)`, which on a typical machine is `5 * 4 = 20` bytes. The sizeof operator is the honest way to ask this, and a handy idiom drops out of it: the number of elements in an array is `sizeof(arr) / sizeof(arr[0])` — the whole size divided by the size of one box. That works no matter what type the elements are.

int scores[5] = {90, 85, 100, 70, 60};

index:     0     1     2     3     4
         +----+----+----+----+----+
scores:  | 90 | 85 |100 | 70 | 60 |
         +----+----+----+----+----+
offset:   +0   +4   +8  +12  +16    (bytes, if sizeof(int)==4)

sizeof(scores)            == 20
sizeof(scores)/sizeof(scores[0]) == 5
Five int boxes laid out back to back; index 3 sits 12 bytes from the start.

A string is an array of char with a quiet ending

C has no built-in string type. What it has is the `char` — a one-byte box that usually holds a text character — and a convention. A C string is just an array of `char` holding your letters, followed by one extra byte with the value 0 to mark the end. That terminator is written `'\0'` and is called the null terminator; this whole arrangement is the null-terminated string. So `char greeting[] = "hi";` is not 2 bytes but 3: `'h'`, `'i'`, and a hidden `'\0'`.

Why a trailing zero instead of storing the length? Because then a string is just a starting point, and any code can walk forward byte by byte until it meets the `'\0'` — that is exactly how `strlen()` measures a string and how `printf("%s", ...)` knows when to stop printing. It is a clever, frugal trick, and it is also the source of an entire family of bugs. If the `'\0'` is ever missing or overwritten, the walk does not stop at the end of your data — it charges on through whatever memory follows, reading garbage until it stumbles onto a zero byte by luck or crashes.

This is where the cost shows up. When you copy text into a fixed-size `char` buffer, you must leave room for that hidden terminator and never write past the end of the buffer. Copy a 20-character name into `char name[16];` and you overrun the array, scribbling over whatever sits after it — a buffer overflow, and historically one of the most exploited bugs in all of computing. The forgivable cousin is the off-by-one error: you allocate exactly `strlen(text)` bytes and forget the `+1` for the `'\0'`. Counting room for the terminator is not optional; it is part of what a C string is.

Structs: bundling different things under one name

An array groups many of the same type. A struct does the opposite job: it bundles several different values into a single named thing. A point has an x and a y; a person has a name, an age, and a height. Rather than juggle three loose variables and hope you keep them in sync, you define one struct that holds them together as named members, and from then on you pass that one object around as a unit.

Defining one is short. Write `struct Point { int x; int y; };` to declare the shape, then `struct Point p = {3, 4};` to make an object with `x` set to 3 and `y` to 4. You reach a member with a dot: `p.x` is 3, `p.y` is 4, and `p.x * p.x + p.y * p.y` works out to 25. The shape is defined once; from it you can stamp out as many `Point` objects as you like, each carrying its own pair of values bundled together.

A struct can contain arrays, other structs, anything — it is genuinely a new little type you designed. Typing `struct Point` everywhere gets wordy, so C lets you make a shorter alias with typedef, after which you can write just `Point` — the `typedef` half of struct, union, enum, and typedef. Two relatives round out the family: a `union`, where all members share the same storage so only one is meaningful at a time, and a `struct` whose members are sized in individual bits, a bit-field, handy for packing flags tightly.

Enums: names instead of magic numbers

The last shape in this guide is the smallest and the friendliest. Often a value is really one of a short, fixed set of choices: a traffic light is RED, YELLOW, or GREEN; a card has one of four suits. You could encode these as the bare integers 0, 1, 2 — but then your code is littered with naked numbers whose meaning lives only in your head. An enum lets you give those choices real names; it is the `enum` member of the same struct/union/enum/typedef family you just met.

Write `enum Color { RED, YELLOW, GREEN };` and C hands `RED` the value 0, `YELLOW` 1, and `GREEN` 2 automatically, counting up unless you assign your own numbers. Under the hood an enum value is just an `int`, so it costs nothing extra — but now `if (light == RED)` reads like the sentence it is, and the compiler can warn you if a `switch` forgets a case. This is the same instinct as naming a variable instead of leaving a literal constant floating in the code: names make programs say what they mean.

Step back and you'll see the shape of this whole guide. Arrays give you many of the same; structs give you several different, bundled; enums give you a named choice from a few; and strings are simply the first of these — a `char` array — wearing a one-byte ending. Each is built straight from the typed boxes of guide one, with no new magic. They are also the doorway to the next rung: arrays, strings, and structs all live somewhere in memory, and the moment you want to talk about where — to pass a big struct cheaply, or grow an array at runtime — you will need their addresses, and addresses are pointers.