C Programming Review¶
Overview¶
This first session of CS 315 establishes the development environment and reviews the C programming language, which is the primary language we use to explore computer architecture. We begin with SSH setup for passwordless access to the department's Stargate gateway and BeagleV RISC-V machines, then review C as a compiled language: how source is turned into an executable with gcc, the core constructs (functions, data/variables, statements, expressions), control flow, function definitions, static typing, primitive types and their sizes, strings as character arrays, and a forward look at structs and pointers. The goal is to make sure everyone can compile and run a C program in the course environment and has a shared mental model of how C programs are organized.
Learning Objectives¶
- Configure SSH key-based, passwordless access from your laptop to Stargate and the BeagleV machines, including correct file permissions
- Explain the difference between a compiled language (C) and an interpreted language (Python)
- Compile a C program with
gccand run the resulting executable - Identify the four core C constructs: functions, data/variables, statements, and expressions
- Describe where data lives at run time: globals, the stack (local variables), and the heap (
malloc/free) - Write functions with a return type, parameters, and a body, and use the basic control-flow statements (
if,for,while,switch) - Explain static vs. dynamic typing and list the primitive C types with their typical sizes
- Understand that C has no built-in string type and that strings are NUL-terminated character arrays
Prerequisites¶
- A GitHub account and basic command-line familiarity (
cd,ls,cat,less) - Prior programming experience in some language (Java, Python, or C)
- Access to a laptop running macOS, Linux, or Windows with WSL
- The course shell-basics guide skimmed beforehand
1. Setup Help and Office Hours¶
The course relies on the department's shared RISC-V hardware, so the first task is getting connected. The instructor offered drop-in setup help:
| When | Where | Purpose |
|---|---|---|
| 10:00 - 10:30 am | CS Labs | Hands-on setup help |
| 1:40 - 2:30 pm | HR 412A | Office hours |
The two machines you connect to are:
- Stargate - the department gateway/jump host that is reachable from the public internet.
- BeagleV - the RISC-V boards where you actually compile and run code. These are reached through Stargate.
The first lab deadline was extended to Wednesday (instead of Monday), and the autograder is configured by Tuesday. See /assignments/lab01/ for the full lab specification.
2. SSH and Passwordless Login¶
SSH (Secure Shell) lets you log in to a remote machine over an encrypted connection. Typing a password every time is slow and error-prone, so we set up key-based authentication: your laptop holds a private key, the server holds the matching public key, and the two are used to prove your identity automatically.
The Key Pair¶
An SSH key pair has two halves:
- Private key (
<key>) - stays on your laptop. Never share or copy it off your machine. - Public key (
<key>.pub) - safe to copy to any server you want to log in to.
flowchart LR
subgraph Laptop["Laptop (~/.ssh)"]
CFG["config"]
PRIV["<key> (private)"]
PUB["<key>.pub (public)"]
end
subgraph Stargate["Stargate (~/.ssh)"]
AUTH["authorized_keys"]
end
PUB -- "scp" --> AUTH
PRIV -. "proves identity" .-> AUTH
What Lives Where¶
The diagram from class shows the layout of the ~/.ssh directory on each side:
On your laptop (~/.ssh/):
On Stargate (~/.ssh/):
You copy the public key over (for example with scp) and append its contents to ~/.ssh/authorized_keys on the server. After that, the server can verify the laptop's private key without a password.
Generating and Installing a Key¶
# 1. Generate a key pair on your laptop (ed25519 is a good default)
ssh-keygen -t ed25519 -C "you@example.edu"
# -> creates ~/.ssh/id_ed25519 (private) and ~/.ssh/id_ed25519.pub (public)
# 2. Copy the public key to Stargate (easiest with ssh-copy-id)
ssh-copy-id you@stargate.cs.usfca.edu
# Equivalent manual steps if ssh-copy-id is unavailable:
scp ~/.ssh/id_ed25519.pub you@stargate.cs.usfca.edu:~/
ssh you@stargate.cs.usfca.edu 'cat id_ed25519.pub >> ~/.ssh/authorized_keys'
Permissions Matter¶
The class diagram marks perms on both the laptop and the server side because SSH refuses to use keys or an authorized_keys file that other users could read or modify. If permissions are too open, SSH silently falls back to asking for a password. Set them like this:
chmod 700 ~/.ssh # directory: owner only
chmod 600 ~/.ssh/<key> # private key: owner read/write
chmod 644 ~/.ssh/<key>.pub # public key: world-readable is fine
chmod 600 ~/.ssh/authorized_keys # on the server side
The config File¶
The ~/.ssh/config file lets you give short names to hosts and route through Stargate to a BeagleV board automatically:
Host stargate
HostName stargate.cs.usfca.edu
User you
IdentityFile ~/.ssh/id_ed25519
Host beagle
HostName beagle1.cs.usfca.edu
User you
IdentityFile ~/.ssh/id_ed25519
ProxyJump stargate # hop through Stargate automatically
ForwardAgent yes # forward your key so GitHub works on the board
With this in place, ssh beagle connects through the gateway in one step. ForwardAgent yes lets the BeagleV machine use your laptop's key for GitHub operations (SSH agent forwarding), so you do not have to copy private keys onto shared hardware.
Quieting the Login Banner¶
To suppress the long login banner on Stargate, create an empty .hushlogin file in your home directory on that machine:
3. C Is a Compiled Language¶
Python runs through an interpreter: the interpreter reads your source and executes it directly. C is compiled: a separate program, the compiler, translates your source text into a machine-code executable ahead of time. You run the executable, not the source.
flowchart LR
SRC["hello.c (source)"] -->|"gcc -o hello hello.c"| EXE["hello (executable)"]
EXE -->|"./hello"| OUT["program output"]
The compile command from class:
gccis the GNU C compiler.-o hellonames the output executablehello.hello.cis the C source file.
Running the Executable¶
You must type ./ in front of the name. The leading ./ means "the file named hello in the current directory." Standard commands like ls live in directories listed in your PATH (such as /usr/bin), but the current directory is not on PATH by default, so the shell will not find a bare hello.
Hello, World¶
#include <stdio.h>pulls in declarations for standard I/O functions such asprintf. The#includeis handled by the preprocessor before compilation.int main(void)is the entry point. Execution starts here.printf("Hello, World!\n")prints text;\nis a newline.return 0;reports success to the shell (non-zero conventionally indicates an error).
4. C Constructs¶
C's core is intentionally small. Almost everything you write falls into one of four categories:
flowchart TD
C["C Constructs"]
C --> F["Functions (code)"]
C --> D["Data / Variables"]
C --> S["Statements"]
C --> E["Expressions"]
D --> G["globals"]
D --> ST["stack (local vars)"]
D --> H["heap (malloc / free)"]
Functions Hold the Code¶
In C, all code lives inside functions - there is no top-level statement execution as in Python. This is similar to Java, where everything is inside a method. The main function is where the program starts.
Data Lives in Three Places¶
At run time, the variables and data your program uses reside in one of three storage regions:
| Region | What lives there | Lifetime | How it is created |
|---|---|---|---|
| Globals | Variables declared outside any function | Whole program run | Declared at file scope |
| Stack | Local variables, function parameters | While the function call is active | Automatically on each call |
| Heap | Dynamically allocated memory | Until you free it | malloc / free |
The stack grows and shrinks as functions are called and return; each call gets its own stack frame holding its locals. The heap is for memory whose size or lifetime is not known at compile time. In this course we mostly use globals and the stack; malloc/free (the heap) are not used in this class, but you should know the region exists.
High addresses
+---------------------+
| Stack | <- local variables, grows downward
| | |
| v |
| |
| ^ |
| | |
| Heap | <- malloc/free, grows upward
+---------------------+
| Globals / Data | <- global variables
+---------------------+
| Code | <- the functions themselves
+---------------------+
Low addresses
Statements and Expressions¶
A statement is a complete unit of execution. Statements end with a semicolon ; or are blocks grouped by braces { }. An expression is a piece of a statement that evaluates to a value. Not every statement is an expression, but many statements contain expressions.
The classic example from class is an assignment:
This whole line is a statement (it ends in ;). The part x = 3 is an expression - in C an assignment evaluates to the value assigned, so x = 3 has the value 3. That is why chained assignments like a = b = 0; work.
5. Statements and Control Flow¶
Statements are the building blocks of a function body. Here are the common ones reviewed in class.
Assignment¶
if¶
The condition in parentheses is evaluated; if it is non-zero (true), the block runs. C has no separate boolean type historically - any non-zero value is "true," and 0 is "false."
Function Call¶
A function call is itself a statement when followed by a semicolon. printf is a call into the standard library.
for¶
The for header has three parts, separated by semicolons:
- init -
i = 0runs once before the loop (an assignment). - condition -
i < 10is tested before each iteration; the loop continues while it is true. - update -
i++runs after each iteration (here, incrementiby 1).
%d in the format string is a placeholder for a decimal integer; the value of i is substituted.
while and switch¶
while (condition) {
// repeat while condition is true
}
switch (value) {
case 1:
// ...
break;
case 2:
// ...
break;
default:
// ...
break;
}
while repeats as long as its condition holds. switch branches on the value of an integer-like expression; remember the break statements, or control "falls through" to the next case.
Control-Flow at a Glance¶
flowchart TD
A[if] --> A1["one-time conditional branch"]
B[for] --> B1["counted loop (init; cond; update)"]
C[while] --> C1["loop while condition true"]
D[switch] --> D1["multi-way branch on a value"]
6. Functions¶
A function definition has three parts: a return value type, a list of parameters, and a body.
Annotated as in the class notes:
- Return value type (
int): the type of the value the function hands back to its caller. - Name (
add_2): how callers refer to the function. - Parameters (
int x): typed inputs, available as local variables inside the body. - Body (
{ ... }): the statements that run when the function is called. return x + 2;evaluates the expressionx + 2and sends it back to the caller.
Calling a Function¶
#include <stdio.h>
int add_2(int x) {
return x + 2;
}
int main(void) {
int result = add_2(40); // result is 42
printf("result = %d\n", result);
return 0;
}
void Functions¶
A function that returns nothing has the return type void and does not need a return statement (though a bare return; is allowed to exit early):
Worked Example: Summing an Array¶
A common task reviewed in class is summing the elements of an array. Because C does not store an array's length with the array, you must pass the size explicitly.
#include <stdio.h>
// Sum n integers starting at array a.
int sum(int a[], int n) {
int total = 0; // accumulator (local, on the stack)
for (int i = 0; i < n; i++) { // index declared in the for header
total = total + a[i];
}
return total;
}
int main(void) {
int nums[5] = {1, 2, 3, 4, 5};
int s = sum(nums, 5); // must pass the size: 5
printf("sum = %d\n", s); // sum = 15
return 0;
}
Notes from the discussion:
- The accumulator
totalstarts at0and adds each element. - The loop index can be declared inside the
forheader (int i = 0) or before the loop. Declaring it in the header keeps its scope limited to the loop. a[]as a parameter is really a pointer to the first element; the function has no idea how long the array is, which is exactly whynis required.
7. Static vs. Dynamic Typing¶
C is statically typed. Python is dynamically typed.
- Static typing (C, Java): every variable has a type that is fixed at compile time. The compiler knows the type of
xbefore the program runs and checks your operations against it. Type errors are caught during compilation. - Dynamic typing (Python): a name can refer to a value of any type, and the type is associated with the value at run time. Type errors surface only when the offending line executes.
Static typing is central to this course: the type of a variable determines how many bytes it occupies and how those bytes are interpreted, which connects directly to data representation in computer architecture.
8. Data Types¶
C types divide into primitive/scalar types (a single value) and derived types (collections built from other types, such as arrays and structs).
Primitive / Scalar Types¶
| Type | Typical size | Holds | Example values |
|---|---|---|---|
int |
4 bytes | whole numbers | -2, -1, 0, 1, 2 |
char |
1 byte | a single character / small integer | 'a' |
float |
4 bytes | single-precision real | 3.14 |
double |
8 bytes | double-precision real | 3.14 |
A few clarifications from class:
- An
intis 4 bytes on the machines we use, which is why it can represent a limited range of whole numbers (we will look at exact ranges and two's complement later). - A
charis 1 byte and is written with single quotes:'a'. It is really a small integer holding a character code (ASCII). float(4 bytes) anddouble(8 bytes) both store real numbers;doublehas more precision. Usedoubleunless you have a reason not to.- Sizes can be confirmed at run time with the
sizeofoperator.
#include <stdio.h>
int main(void) {
printf("int = %zu bytes\n", sizeof(int)); // 4
printf("char = %zu bytes\n", sizeof(char)); // 1
printf("float = %zu bytes\n", sizeof(float)); // 4
printf("double = %zu bytes\n", sizeof(double)); // 8
return 0;
}
Derived Types: Arrays¶
An array is a fixed-size, contiguous sequence of elements of the same type. As noted in the array-sum example, the size is not carried with the array, so you track it yourself.
int nums[5] = {1, 2, 3, 4, 5}; // five ints, indices 0..4
nums[0] = 10; // first element
// nums[5] would be out of bounds - undefined behavior!
9. Strings Are Character Arrays¶
C has no built-in string type. A string is an array of
charterminated by a special NUL byte ('\0', value 0).
When you write a string literal in double quotes, the compiler automatically appends the terminating NUL for you:
The annotation from class points out that "hello" is a string literal, and the compiler will auto add the NUL terminator. So str actually occupies six bytes:
index: 0 1 2 3 4 5
+----+----+----+----+----+----+
str: | h | e | l | l | o | \0 |
+----+----+----+----+----+----+
^
NUL terminator (added by the compiler)
The NUL terminator is how library functions know where the string ends, since the length is not stored separately. strlen counts characters up to (but not including) the NUL:
#include <stdio.h>
#include <string.h>
int main(void) {
char str[] = "hello";
printf("%s\n", str); // prints: hello
printf("length = %zu\n", strlen(str)); // length = 5 (NUL not counted)
return 0;
}
%sinprintfprints characters until it hits the NUL.strlenis declared in<string.h>; you must include that header to use it. This is exactly the function needed for theargslensprogram in Lab 01 (see/assignments/lab01/).
This NUL-terminated convention has real consequences: forgetting the terminator, or writing past it, causes the classic C bugs we will study when we look at memory and security later in the course.
10. Looking Ahead: Structs and Pointers¶
The notes close by naming two topics we will return to in depth:
- Structs - a derived type that groups several named fields of possibly different types into one value. Structs let you model records (for example, a point with
xandyfields).
- Pointers - variables that hold the address of another value rather than the value itself. Pointers are how C refers to memory directly, how arrays decay into addresses, and how functions can modify their caller's data. They are essential for understanding the stack, the heap, and assembly later in the course.
int x = 42;
int *p = &x; // p holds the address of x
printf("%d\n", *p); // dereference: prints 42
*p = 7; // writes through the pointer
printf("%d\n", x); // x is now 7
We will treat both of these carefully in upcoming sessions, connecting them to how data is laid out in memory on a RISC-V machine.
Key Concepts¶
| Concept | Definition | Example |
|---|---|---|
| Compiled language | Source is translated to a machine-code executable before running | gcc -o hello hello.c |
| Executable | The runnable binary produced by the compiler | ./hello |
./ prefix |
"File in the current directory," needed because . is not on PATH |
./hello |
| Function | Named code unit with return type, parameters, and a body | int add_2(int x) { return x + 2; } |
| Globals / stack / heap | The three run-time storage regions for data | locals on the stack, malloc on the heap |
| Statement | A complete unit of execution ending in ; or a { } block |
x = 3; |
| Expression | A piece of a statement that evaluates to a value | x = 3 evaluates to 3 |
| Static typing | Variable types fixed and checked at compile time | int x = 3; |
int |
4-byte whole-number type | -2, -1, 0, 1, 2 |
char |
1-byte character/small-integer type | 'a' |
| String | NUL-terminated array of char (no built-in type) |
char str[] = "hello"; |
| NUL terminator | The '\0' byte marking the end of a string |
added automatically to literals |
| SSH key pair | Private key (laptop) + public key (server) for passwordless login | id_ed25519 / id_ed25519.pub |
Practice Problems¶
Problem 1: Compile and Run¶
You have a file greet.c. Write the two shell commands to compile it into an executable named greet and then run it. Explain why a bare greet would fail.
Click to reveal solution
A bare `greet` fails because the shell searches the directories on `PATH` (like `/usr/bin`) for commands, and the current directory `.` is not on `PATH` by default. The `./` prefix explicitly tells the shell to run the `greet` file in the current directory.Problem 2: Statement vs. Expression¶
For the line y = x + 1;, identify the statement and the expression(s), and state the value the assignment expression produces if x is 4.
Click to reveal solution
- The **statement** is the entire line `y = x + 1;` (it ends in a semicolon). - The **expression** is `y = x + 1` (an assignment expression), which contains the sub-expression `x + 1`. - If `x` is `4`, then `x + 1` is `5`, `y` is set to `5`, and the assignment expression itself evaluates to `5`. This is why `z = (y = x + 1);` would also set `z` to `5`.Problem 3: Sum a Larger Array¶
Modify the sum function so main correctly sums this array of seven elements: {2, 4, 6, 8, 10, 12, 14}. What value is printed, and what must change at the call site?
Click to reveal solution
The `sum` function does not change - it already takes the size as a parameter. Only `main` changes: The printed value is `56`. The key change is passing `7` as the size, because C does not store the array length with the array - the function relies entirely on the size argument.Problem 4: String Length and Bytes¶
For char str[] = "CS315";, how many bytes does str occupy, and what does strlen(str) return? Why are they different?
Click to reveal solution
- `str` occupies **6 bytes**: the five characters `C`, `S`, `3`, `1`, `5`, plus the automatically added NUL terminator `'\0'`. - `strlen(str)` returns **5**, because `strlen` counts characters up to but not including the NUL. They differ by one because the storage must include room for the terminator, but `strlen` reports only the visible character count.Problem 5: Type Sizes¶
Using sizeof, predict the output of this program on the course machines:
#include <stdio.h>
int main(void) {
printf("%zu %zu %zu %zu\n",
sizeof(char), sizeof(int), sizeof(float), sizeof(double));
return 0;
}
Click to reveal solution
- `char` = 1 byte - `int` = 4 bytes - `float` = 4 bytes - `double` = 8 bytes These sizes are why type matters so much in C: the type determines exactly how much memory a value uses and how the bytes are interpreted.Problem 6: Where Does It Live?¶
Classify each variable below by storage region (global, stack, or heap):
Click to reveal solution
- **(a) `g`** - a **global**. It is declared outside any function, so it lives in the global/data region for the whole program run. - **(b) `p`** - on the **stack**. Function parameters are local to the call and live in the call's stack frame. - **(c) `local`** - on the **stack**. Local variables are allocated automatically in the current function's stack frame and disappear when the function returns. None of these use the **heap**; heap memory would require `malloc` (not used in this class).Further Reading¶
- Stanford Essential C
- Beej's Guide to C Programming
- learn-c.org
- The C Programming Handbook for Beginners (freeCodeCamp)
- GNU
gccDocumentation - OpenSSH Manual
- Course C programming guide:
/guides/c-programming/ - Lab 01 specification:
/assignments/lab01/ - Source notes (PDF): "/notes/CS315-01 2025-08-21 C Review.pdf"
Summary¶
-
SSH key-based login uses a private key on your laptop and a matching public key (
authorized_keys) on the server; correct file permissions are required or SSH falls back to a password. Reach the BeagleV boards through Stargate. -
C is compiled, not interpreted:
gcc -o hello hello.cproduces an executable you run with./hello. The./is needed because the current directory is not onPATH. -
C's core is small and built from four constructs: functions (code), data/variables, statements, and expressions.
-
Data lives in three regions at run time - globals, the stack (local variables and parameters), and the heap (
malloc/free, not used in this class). -
All code lives in functions, each with a return type, parameters, and a body;
voidfunctions need noreturn. Control flow usesif,for,while, andswitch. -
C is statically typed: types are fixed and checked at compile time, and the type determines how many bytes a value occupies and how they are interpreted -
int(4),char(1),float(4),double(8). -
Strings are NUL-terminated
chararrays; there is no built-in string type. String literals get their'\0'terminator added automatically, so"hello"occupies six bytes whilestrlenreports five. -
Structs and pointers are coming next - they let us group fields into records and refer to memory by address, connecting C directly to the architecture topics ahead.