C Part I · Module 2 · Values, Variables, Types and Representation
Bits, Bytes and Hexadecimal Notation
Chapter 2.2 said an object occupies bytes. It did not say what is inside one. This chapter does, and in doing so gives you a skill rather than a fact: the ability to look at a value and picture its bit pattern, and to read the notation engineers use to write those patterns down. Hexadecimal is often taught as arithmetic homework, which is the wrong emphasis entirely — nobody converts bases by hand in practice. What matters is that one hexadecimal digit corresponds to exactly four bits, which makes hex a compact way of reading and writing bit patterns directly. Everything in the rest of this module, every bit operation in Module 3, and every register field in Part II is easier once that correspondence is automatic.
Foundation12 min readCPart IFundamentalsRepresentation
Part I · Module 2 · Chapter 2.4 · Bits, Bytes and Hexadecimal Notation
1. What Is Inside a Byte
Chapter 2.2 established that memory is a run of numbered bytes and that an object occupies several consecutive ones. What it did not say is what a byte actually holds.
A byte holds a fixed number of bits. A bit is the smallest thing storage can distinguish: it is in one of two states, conventionally written 0 and 1.
On essentially every system you will ever use, a byte holds 8 bits. C requires at least 8 and lets an implementation provide more; the exact number for your implementation has a name that Chapter 2.12 introduces. For this module, picture 8.
Eight bits look like this:
one byte
bit position 7 6 5 4 3 2 1 0
contents 0 1 0 1 1 0 1 1Bit positions are numbered from the right, starting at 0. That looks backwards until you notice it matches how decimal digits work — the rightmost digit of 253 is the one worth the least — and then it stops looking backwards permanently.
2. A Value Is Not Its Bit Pattern
Before going further, a distinction that this whole module depends on.
The number five is a value. It is an idea: a quantity. It does not have ones and zeros in it.
00000101 is a representation of that value: one particular way of recording it in eight bits. The same value could be represented differently — with more bits, or by a completely different scheme — and it would still be the value five.
A value is what you mean. A representation is how it is recorded in storage.
This is worth being pedantic about now, because collapsing the two is the root of an entire family of later confusions: why the same bit pattern can mean two different numbers depending on the type (Chapter 2.7), why a character and a small number look identical in storage (Chapter 2.8), why 0.1 cannot be recorded exactly (Chapter 2.9), and why data crossing a boundary between two languages has to agree on representation and not just on value (Part II).
3. Counting in Binary
A run of bits records a number the same way a run of decimal digits does — each position is worth a fixed amount, and you add up the positions that are set.
In decimal, positions are worth 1, 10, 100, 1000 — each ten times the one to its right. In binary each position is worth twice the one to its right:
position 7 6 5 4 3 2 1 0
worth 128 64 32 16 8 4 2 1
pattern 0 0 0 0 0 1 0 1
add up 4 + 1 = 5
pattern 0 0 0 1 1 0 1 1
add up 16 + 8 + 2 + 1 = 27That is the entire mechanism. Two useful landmarks fall straight out of it:
- All eight bits set is
11111111, which adds up to 255. - Each extra bit doubles the range. Eight bits can record 256 different patterns,
00000000through11111111.
You will not often do this addition by hand. You will often need to know roughly how big a pattern is, and which position a particular bit sits in, and for that the table above is enough.
4. Why Hexadecimal Exists
Binary is honest and unreadable. Compare:
0000111110100100 binary
4004 decimal? no — this is not decimalNobody can read the first at a glance, and rewriting it in decimal destroys the thing you cared about, which was the pattern.
Hexadecimal solves this exactly. It has sixteen digits — 0–9, then A, B, C, D, E, F for ten to fifteen — and the reason that number was chosen is this:
One hexadecimal digit records exactly four bits.
Sixteen possible digit values, sixteen possible four-bit patterns. The correspondence is perfect, with nothing left over, which is why hex feels natural for bit patterns and decimal does not.
0000 = 0 0100 = 4 1000 = 8 1100 = C
0001 = 1 0101 = 5 1001 = 9 1101 = D
0010 = 2 0110 = 6 1010 = A 1110 = E
0011 = 3 0111 = 7 1011 = B 1111 = FYou do not need to memorise that table. You need to know it exists and that the mapping is four bits per digit; the rest becomes automatic within a week of using it.
5. Reading Hex Without Arithmetic
Here is the practical skill, and it involves no conversion at all. To read a bit pattern in hex, chop it into groups of four from the right and replace each group with its digit:
binary 1111 1111
group each group of four → one hex digit
hex F F
so 11111111 is FFAnd a longer one:
binary 0000 0001 1111 0100
hex 0 1 F 4
so 0000000111110100 is 01F4Going the other way is the same move in reverse — expand each hex digit into its four bits. This is why engineers write addresses, masks and register contents in hexadecimal: you can see the bits through the notation. In decimal you cannot; 500 tells you nothing about which bits are set, while 01F4 tells you immediately.
6. Writing Hexadecimal in C
C lets you write a value in hexadecimal directly. The notation is 0x followed by hex digits:
#include <stdio.h>
int main(void)
{
int written_in_decimal = 255;
int written_in_hex = 0xFF;
printf("%d %d\n", written_in_decimal, written_in_hex);
return 0;
}Output:
255 255That output is the point of the program. 255 and 0xFF are the same value, written two ways, and by the time the program runs there is no trace of which notation you used. The 0x prefix is a message to the compiler about how to read the digits that follow — nothing more. It does not make the object different, does not change its type, and does not survive into the running program.
The same is true of any hex literal:
#include <stdio.h>
int main(void)
{
int value = 0x1F4;
printf("value is %d\n", value);
return 0;
}Output:
value is 500So which should you write? Use whichever notation makes the meaning clearer. A count of items, a maximum, a number of retries — write those in decimal, because the quantity is what matters. A bit pattern, a mask, a register value, an address — write those in hex, because the bits are what matter, and decimal hides them.
7. Where This Is Going
It is reasonable to ask why a whole chapter goes to notation. Three answers, all of which arrive soon:
- Chapter 2.6 and 2.7 are about how a bit pattern is interpreted, which you cannot discuss without being able to look at one.
- Module 3 introduces operators that work on individual bits rather than on the value as a whole.
0x0Fis readable as an operand;15is not. - Part II is largely about agreeing with hardware on exactly which bits mean what. Every register description you will ever read is written in hex, for the reason in §5.
Until then, the immediate payoff is the one in §2: you can now talk about a value and its representation as two different things, which is what the next two chapters need.
8. What You Should Be Able to Say Now
- A bit is a single two-state unit; a byte holds a fixed number of them, 8 on essentially every system you will meet and at least 8 by C's rules.
- Bit positions are numbered from the right, starting at 0.
- A value is what you mean; a representation is how it is recorded. They are not the same thing.
- In binary each position is worth twice the one to its right; eight bits record 256 different patterns, with all-ones adding up to 255.
- One hexadecimal digit is exactly four bits — which is the entire reason hexadecimal is used.
- To read hex, group bits in fours from the right; to expand hex, do the reverse. No arithmetic required.
0xFFand255are the same value. The prefix tells the compiler how to read your digits and leaves no trace afterwards.- Write decimal for quantities, hex for bit patterns.
0bbinary literals are C23; on this curriculum's C17 baseline they are not portable.
9. Exercise
Exercise 1 — add up the positions. What value does each pattern record? 00000001, 00001111, 10000000, 00100100.
Exercise 2 — group into hex. Convert by grouping, not by arithmetic: 11111111, 0001 0000, 1010 1010, 0000 0011 1111 1111.
Exercise 3 — expand into bits. Write out the bits for 0x0F, 0x80, 0xA0, 0x1234.
Exercise 4 — predict the program. What does this print, and why is the answer not 10?
int value = 0x10;
printf("%d\n", value);Exercise 5 — choose the notation. For each, say whether you would write the value in decimal or hexadecimal, and why: the maximum number of retries; a pattern selecting the low four bits of a byte; the number of students in a class; the starting address of a region of memory.
Exercise 6 — explain the distinction. A colleague says: "So 0xFF is a hexadecimal number and 255 is a decimal number — they are different numbers stored differently." Using §2 and §6, explain in two sentences what is wrong with that.
10. Summary
- A byte holds bits — 8 on any system you are likely to meet, at least 8 by C's rules. Positions are numbered from the right, starting at 0.
- A value is not its representation. Five is a quantity;
00000101is one way of recording it. Keeping those apart is what the next three chapters depend on. - Binary positions double from right to left. Eight bits give 256 patterns, all-ones being 255.
- One hex digit is exactly four bits. That correspondence — not arithmetic — is why hexadecimal exists and why engineers read it fluently.
- Read hex by grouping, not by converting.
0xFFshould look like eight set bits, not like the number 255. 0xFFand255are one value written two ways. The notation is a message to the compiler and leaves no trace in the running program.- Binary literals are C23, not part of this curriculum's C17 baseline.
You can now look at a bit pattern and say what it records — as long as the value is not negative. Nothing so far has explained how a minus sign survives into a run of bits, and it is not obvious that it can. Before that question can be answered, though, there is a more basic one to settle: why C offers several different sizes of whole number at all, rather than one that always works.
Standards & specifications
- Governing standard
- ISO/IEC 9899 (the C language standard)(opens ISO/IEC (JTC 1/SC 22/WG 14) in a new tab)
Defines the C language itself — the type system, expression semantics, object model, storage duration and the behaviour categories a conforming implementation must honour. It is a FAMILY of editions (C89/C90, C99, C11, C17, C23) and this curriculum names the edition wherever a claim depends on one. It does not define any foreign-function interface to a hardware description language, and it does not define platform behaviour: what a memory-mapped access does on real silicon is the implementation's and the device's contract, not this standard's. Working drafts published by WG14 are freely available and are what most engineers read day to day; a draft is not the published standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the C Programming curriculum.
