You do not need to become a professional C or C++ programmer to read Android source. You do need to recognize the machinery that source excerpts assume: values have types, functions accept inputs and return results, conditions choose paths, arrays group values, and pointers refer to objects. This lesson builds a small C program on your workstation and then uses it to decode common kernel-shaped C. It does not build Android, run on a phone, or make a raw system call.
Before you start#
Read Workstation setup, Terminal and Git, and Linux files and processes. This lab expects a working C compiler command named cc, a text editor or the shown terminal here-document, and a Bash-compatible shell. If cc --version fails, stop and use the compiler setup guidance in Workstation setup; do not install packages with administrator privileges by guessing.
Learning goals#
After this lesson, you should be able to follow a short C function from its inputs through a branch to its return code; distinguish a pointer from the object it points at; identify a struct and a callback table; explain what headers, macros, compilation, and linking contribute; and state why C++ std:: examples may use exceptions and RAII without treating C and C++ as the same language. These are reading skills, not proof that you can safely write production kernel code.
From text to a running process
- Source and headers
sample.c contains statements; headers provide declarations.
- Compile → object code
Translation checks and emits code for the selected target.
- Link → executable
Object files and required library code form sample.
- Run ./sample → process
The OS creates execution state and resources; runtime behavior still needs checking.
A source file is human-readable text such as sample.c. A compiler checks and translates it; a linker combines the translated code with needed library code into an executable, here named sample. The command cc sample.c -o sample commonly asks the compiler driver to perform both steps. Headers such as <stdio.h> provide declarations so the compiler can check calls like printf; they are not themselves the implementation of printf. The C library and runtime provide that implementation. When you run ./sample, the operating system starts a process. Printing a line generally goes through a library wrapper and eventually an OS interface; it is not a direct syscall in the C source. The C language reference explains declarations, functions, and translation; the existing lesson on programs and system calls distinguishes wrappers from kernel requests.
For a larger program, source may be compiled into object files first and then linked together with libraries. A successful compile means the toolchain accepted that translation; it does not prove the program's logic is correct, that another architecture can run it, or that Android will include it. -Wall -Wextra requests common diagnostics; -Werror makes those diagnostics fail the build (GCC warning options), which is useful for a tiny exercise but can make unrelated warning-policy differences matter in other projects. AOSP uses its own build descriptions and cross-compilation toolchains; do not substitute this host command for an Android build.
Read the punctuation, not just the nouns
Read the text version
- Pointer object p: int *p = &count → Integer object count: int count = 3 (*p follows the reference)
int count = 3;
if (count > 0) {
count = count - 1;
}
int is a type, count a variable name, and 3 its initial value. if controls whether the braced statements run. = assigns a value; > compares values. A function packages work behind a name and may receive parameters and return a result. return 0; from main conventionally reports success to the host (C main); a nonzero status conventionally signals a failure. It is a convention between program and caller, not a universal error code.
An array stores a fixed sequence of elements of one type: int values[3] = {4, 8, 15};. C indices start at zero, so values[0] is 4. C does not automatically attach a reliable length to an array pointer; keep the length as a separate value and check indexes before access. Reading or writing out of bounds is undefined behavior (UB): the language promises no particular outcome (cppreference: undefined behavior). Do not try to trigger UB as a demonstration.
A pointer stores a reference to an object (often represented as an address). &count means “the address of count”; int *p = &count; declares a pointer to an integer; *p means “the integer reached through p.” The same * symbol appears in declaration and dereference syntax. p->field accesses a field through a pointer to a struct and is shorthand for (*p).field. A pointer is not automatically valid, owned, or safe: it can be null, stale, or point somewhere it must not. In these first examples, avoid raw allocation and keep object lifetime simple. C pointer declarations and indirection · C arrays
A struct groups named fields, potentially of different types:
struct Reading { const char *label; int value; };
struct Reading r = {"temperature", 21};
Here r.value is 21; r.label points to a string literal. const char * means code should not modify the characters through that pointer. The string literal's storage lasts for the program's execution, so this particular pointer remains valid. That does not make every pointer safe.
Callback tables, macros, and errors
A callback is a function passed by address so another piece of code can call it later. A function pointer has a declared function type, for example int (*run)(const struct Reading *): it points to a function taking a pointer to a Reading and returning an integer. A callback table is a struct containing such function pointers. Kernel interfaces often use this pattern: a framework keeps a table of operations, then calls the registered operation when an event occurs. Registration and lifetime rules determine whether a callback is valid; the table alone does not.
#include <...> is a preprocessor directive selecting declarations from a header. A #define creates a preprocessor macro: it substitutes tokens before ordinary compilation, rather than creating a typed variable or function. Macros can be useful for constants and conditional compilation, but their expansion can surprise readers. In source review, search for the macro definition before deciding what an unfamiliar macro does.
Many C library functions report failure through a return value and may set errno, a thread-local integer describing a recent library/system error (cppreference: errno). Check the specific function's documented convention: -1 plus errno is common for POSIX calls (syscalls(2), NOTES), not universal to all functions or raw kernel interfaces. A kernel function may return a negative error number directly; Android/Linux source often spells these as -EINVAL or -EFAULT. Do not interpret every negative number as a byte count or assume that userspace errno rules apply unchanged inside the kernel.
A small amount of C++ syntax
C++ shares much C syntax, but adds language and standard-library features. std:: names the standard library namespace: std::string is a text type (std::basic_string stores and manipulates character sequences); std::vector<int> is a resizable sequence; std::cout is a stream. Angle brackets in std::vector<int> supply a type parameter. An exception can report an exceptional failure: throw std::runtime_error("failed"); transfers control to a matching catch, while stack unwinding destroys local objects on the way out.
RAII (resource acquisition is initialization) ties a resource's lifetime to an object. A std::string, std::vector, or std::unique_ptr cleans up its owned resources when its lifetime ends, including during exception unwinding. This is a C++ ownership technique, not a guarantee that every object is safe or that kernel C has C++ destructors. In Android source, std::unique_ptr<T> usually communicates unique ownership; a raw T* does not by itself say who owns the object. You need surrounding code and API contracts. C++ standard-library containers · std::unique_ptr · C++ RAII
Practice#
Host-only, local, and bounded: use a new empty practice directory under your home folder, not an Android device, AOSP checkout, or directory containing important files. Stop if the chosen path already exists; pick another name rather than overwriting. In a Bash-compatible terminal, replace YOURNAME with a short name you choose:
mkdir "$HOME/android-c-reading-YOURNAME" && cd "$HOME/android-c-reading-YOURNAME"
mkdir without -p fails if that directory already exists; && runs cd only if creation succeeds. If the line fails, stop and choose a new name—do not run the next block from the old directory. The here-document below writes exactly the source file; cat > sample.c would replace an existing file, so only run this in the newly created empty directory. The EOF marker must be on a line by itself.
cat > sample.c <<'EOF'
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#define READING_COUNT 3
struct Reading {
const char *label;
int value;
};
typedef int (*show_fn)(const struct Reading *reading);
static int show_reading(const struct Reading *reading) {
if (reading == NULL) {
return -1;
}
printf("%s=%d\n", reading->label, reading->value);
return 0;
}
int main(int argc, char **argv) {
const struct Reading readings[READING_COUNT] = {
{"cpu", 2}, {"memory", 4}, {"storage", 8}
};
const show_fn callbacks[1] = {show_reading};
long index = 0;
if (argc > 1) {
char *end = NULL;
errno = 0;
index = strtol(argv[1], &end, 10);
if (errno != 0 || end == argv[1] || *end != '\0' ||
index < 0 || index >= READING_COUNT) {
fprintf(stderr, "usage: %s [0..2]\n", argv[0]);
return 2;
}
}
const struct Reading *selected = &readings[index];
return callbacks[0](selected);
}
EOF
Before compiling, trace the code: argc is the number of command-line arguments, and argv is an array of strings; argv[1] is optional text. strtol converts it to a long; end points to the first unconverted character. The checks reject conversion errors, trailing characters, and indexes outside the three-element array. &readings[index] takes an element's address. selected is a pointer to a Reading; -> follows it. callbacks[0] is a function pointer, and calling it invokes show_reading. return 2 reports invalid input; successful output returns zero.
Compile and run (the first command is on your workstation):
cc -std=c11 -Wall -Wextra -Werror sample.c -o sample
./sample
./sample 1
./sample nope
echo "exit=$?"
Expected successful output is cpu=2, then memory=4. The invalid-input run writes usage: ./sample [0..2] to standard error, and the final line should be exit=2. Compiler diagnostic wording varies. If cc is missing or compilation reports an error, stop and inspect the exact message; do not run ./sample unless compilation succeeded. The executable name and diagnostic path can vary with the compiler.
For a source-reading transfer, imagine a kernel table with .read = read_data. The callback entry says which function may handle a read; it does not prove when it is called, what locks are held, or whether its buffer is user memory. Those answers require the registration, caller, function contract, and lifetime context. Later lessons will revisit these boundaries.
Knowledge check#
Close the answers below while you work. On paper, explain the data flow in ./sample 1: input text → integer → bounds check → selected element → callback → output and exit status. Then predict what changes if the valid range is 0..2 but the input is 3. Finally, explain why a non-null pointer is not by itself proof that dereferencing it is safe, and name two facts you would need before trusting a kernel callback table. Do not test out-of-bounds access.
Answers#
argv[1] is the string "1"; strtol produces 1; the range check accepts it; &readings[1] points to the memory entry; callbacks[0] invokes show_reading; it prints memory=4 and returns zero. Input 3 fails the upper-bound check, prints the usage diagnostic, and returns status 2 without indexing the array. A pointer can be dangling, null, or point to an object whose lifetime/permissions do not fit the operation. To understand a callback table, locate its registration and the call sites/contracts, including lifetime and synchronization conditions.
Checkpoint#
Save sample.c plus a short note containing the compiler command, successful output, invalid-input diagnostic/status, and one sentence distinguishing user-space example code from kernel code. Ready: you can explain all six stages without reading the answer and can identify the explicit bounds check. Revise: if types, return status, or &/* remain unclear, annotate those exact lines and recompile after changing only a printed label. Not yet evidenced: if you did not run it, the code-reading may still count as reading practice, but it is not evidence of a local build; use the workstation compiler setup, then retry. Do not call this programming proficiency or kernel competence.
Sources#
- C pointers — reference for pointer declaration, address-of, indirection, null pointers, and function pointers; supports the syntax explanations, not the lesson's specific code.
- C arrays and C structs — language references for arrays and aggregate types; used to ground indexing and fields.
- C
strtol—str_endis set past the last numeric character interpreted (tostrif no conversion occurs); overflow setserrnotoERANGE. Checked against the example's validation. - GCC warning options —
-Werrorturns warnings into errors; used for the host-only compile command. - C
main— zero andEXIT_SUCCESSindicate successful termination. - cppreference:
errno—errnois a thread-local modifiableintlvalue. - cppreference: undefined behavior — lists out-of-bounds memory access among UB examples.
std::basic_stringandstd::unique_ptr— library references for the C++ text and ownership examples.- C++ containers and RAII — reference material for standard containers, ownership/lifetime, and cleanup; the lesson gives orientation rather than full C++ instruction.
- AOSP Android 14
Activity.java— a pinned Java source example from the preceding Android overview; this lesson does not claim its C program is Android source. - Memory and System Calls — next lesson connects a host process and library wrapper to the user/kernel boundary; its C snippet is illustrative and not a direct syscall.