External data: .data, .bss, and .text
prerequisite
So far a variable has lived in a register or in a slot of the current function's frame, and both are gone once the function returns. This lesson covers variables that live outside every function, in memory the program keeps from the moment it starts until it ends. Because they sit outside any one function, they are called external data.
A running program's memory is split into sections: named regions that each hold one kind of thing. Three of them matter here.
.textholds the instructions, plus any constants that never change. The operating system marks it read-only..dataholds variables that start with a value written in the source..bssholds variables that start at zero. The program file records only how big they are, and the operating system fills them with zero bytes when the program starts.
Four kinds of variable
C has four kinds of variable, told apart by where they are declared and by the word static. Each one maps onto a place in memory.
| In C | Lives in | Lasts | Its name can be used by |
|---|---|---|---|
a local: int n; inside a function | the frame | one call | that function |
a static local: static int n; inside a function | .data or .bss | the whole run | that function |
a global: int n; outside every function | .data or .bss | the whole run | every function, in every file |
a static global: static int n; outside every function | .data or .bss | the whole run | every function in the same file |
A variable written with a starting value goes in .data; one with no starting value goes in .bss, where it starts at zero. (gcc also sends a variable written as = 0 to .bss, since the result is the same.) A constant, such as const int limit = 60;, goes in .text in course programs. (gcc itself puts constants in a section called .rodata, for read-only data; the effect is the same.)
In assembly, the last three rows look alike: a label in front of some storage. The label is visible to other files only when the file says .global with its name; without that line the name stays private to its file, which is what static means for a C global. Which functions use a static local is up to you, since the assembler does not check.
A counter that survives the return
Here are two functions that should hand out numbered tickets. The first keeps its count in a static local, the second in an ordinary local:
int next_ticket(void) { static int served = 0; // one copy, made when the program loads served = served + 1; return served;}int next_ticket_frame(void) { int count = 0; // a new copy on every call count = count + 1; return count;}In the assembly version, next_ticket keeps its counter in .data under the label served_m. Each call loads the word, adds one and stores it back, and the new value is still in memory on the next call. next_ticket_frame does the same work on a local in its frame, which is set to 0 on every entry and thrown away at ret. main calls each one three times and prints:
counter in .data: ticket 1counter in .data: ticket 2counter in .data: ticket 3counter in frame: ticket 1counter in frame: ticket 1counter in frame: ticket 1pitfall
A variable in .data is one copy shared by every call. A recursive function that keeps its working value there finds each deeper call writing over it. A value that belongs to one call goes in that call's frame.
Two ways to reach a label
Every access to external data starts by getting its address into a register.
ldr x9, =served_m is the form course programs use. The assembler stores the full 64-bit address of served_m in a literal pool, a small table of constants it places near the code, and the ldr loads the address from that table.
The other form builds the address in two instructions, without a table. Memory is divided into pages, blocks of 4096 bytes. adrp x9, label puts the address of the page that holds the label into x9, and add x9, x9, :lo12:label adds the low 12 bits of the label's address, which is its position inside the page. This is the pair gcc writes for C code. Both forms leave the same address in x9.
Three sections in one program
The next program counts how many of eight marks pass. Each piece of data sits in the section that fits it:
- The marks start with values, so they are in
.data. - The two counters need no starting value, so they are in
.bss..bssguarantees zero, so the program adds to them without clearing them first..skip 4reserves 4 bytes for each, and.balign 4puts the first on a 4-byte boundary, the alignment a word load expects. - The pass mark never changes, so it is a constant in
.text, reached withadrpandadd. Everything else is reached withldr =.
Five of the eight marks are 60 or more, so the program prints passed: 5, failed: 3.
warning
.text is read-only on the servers. A store into pass_mark stops the program with a segmentation fault, the error Linux raises when a program touches memory in a way it is not allowed to. The playground does not enforce this yet, so a store into .text that runs here can still crash on the server. Keep anything you plan to change in .data or .bss.
pitfall
A common mistake from this lesson, with a broken program and its fix that you can run:
Check yourself
- A program needs a 4000-byte buffer that starts out empty. Which section should it go in, and what would
.datacost instead? - What does
next_ticket_framereturn on its tenth call, and why? - After
adrp x9, fmt_tallyalone, doesx9hold the address offmt_tally? - A helper file wants to call a function named
tallyin this file. What line does this file need?
answers
show answers
.bss. It starts at zero and takes no room in the program file; in.datathe file would carry 4000 zero bytes.- 1, because its counter is a frame local, made again and set to 0 on every call.
- Only if
fmt_tallyhappens to start a page.adrpgives the page's address; theaddwith:lo12:supplies the rest. .global tally.
Practice
- Sum a global array: walk an array that lives in
.bss. - Are these dice loaded?: tally rolls in a
.bssarray and draw a histogram. - Vending machine brain: a subroutine that keeps its state in
.bsswords from one call to the next. - Basic quiz: external data, then core and challenge: sections and variable kinds.
- Fill in the blank: external data (core) and Predict: external data (core): where each kind of variable lives.