C Command-Line Arguments and Lab 01¶
Overview¶
This hands-on lab session walks through the full development workflow you will use all semester: writing C programs on the BeagleV machines, compiling them with gcc, organizing builds with a Makefile, and checking your work with the class autograder. The technical heart of the session is understanding how a C program receives command-line arguments through argc and argv, and how the compiler turns a .c source file into a runnable executable. By the end you will have written and submitted the two Lab 01 programs, hello and argslens, and demonstrated that your development environment is fully set up.
Learning Objectives¶
- Compile and run C programs on the BeagleV machines using
gcc - Explain the difference between one-step compilation and the two-step compile-then-link pipeline (
.c→.o→ executable) - Read and accept command-line arguments in a C program using
argcandargv - Convert string arguments to integers with
atoi()and measure string length withstrlen() - Write, build, and test the
helloandargslensprograms for Lab 01 - Read a basic
Makefileand add a new program target to it - Install, configure, and run the autograder (
grade) on the BeagleV machines - Use
gitto add, commit, and push your code to your Lab 01 GitHub repository
Prerequisites¶
- Passwordless SSH access to the BeagleV machines from your laptop
- A terminal-based editor other than
nano(micro,vim, oremacs) - A GitHub account whose ID has been added to the class spreadsheet
- Basic command-line navigation (
cd,ls,mkdir,pwd) - Familiarity with the contents of Lab 01
1. The Lab Workflow at a Glance¶
Lab 01 is the first time you put the entire toolchain together. Nothing here is conceptually hard, but each step has to work for the next one to succeed. The whole loop looks like this:
flowchart LR
A[SSH to BeagleV] --> B[Clone Lab01 repo]
B --> C[Edit .c files<br/>with micro/vim]
C --> D[make]
D --> E[Run program<br/>./hello World]
E --> F{Output correct?}
F -- no --> C
F -- yes --> G[grade test]
G --> H{Tests pass?}
H -- no --> C
H -- yes --> I[git add / commit / push]
I --> J[Demo dev setup to TA]
There are two graded pieces in Lab 01:
| Piece | How it is graded | What it checks |
|---|---|---|
| Lab01 Tests | Autograder (grade test) |
hello and argslens produce exactly the expected output |
| Lab01 Dev Setup | Interactive demo to Greg or a TA | You can SSH, edit, compile, run, autograde, and use git |
The rest of these notes follow the workflow above, in order.
2. Compiling a C Program with gcc¶
The first thing demonstrated in class was compiling the classic "Hello, World" program. The single most important command of the day is:
This reads as: "compile the source file hello.c and write the output executable to a file named hello." The -o hello part names the output. If you leave off -o, gcc writes the executable to the default name a.out, which is rarely what you want.
Notice the leading ./ when you run the program. The ./ tells the shell to look for the executable in the current directory. Without it, the shell only searches the directories listed in your PATH, and your freshly built hello is not on that list.
A complete hello.c¶
Here is a minimal program that satisfies the Lab 01 hello requirement (we will revisit the argument handling in detail in Section 5):
#include <stdio.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("usage: ./hello <str>\n");
return 1;
}
printf("Hello, %s!\n", argv[1]);
return 0;
}
3. One Step vs. Two Steps: The Compile-and-Link Pipeline¶
The handwritten notes contrast two ways of getting from source code to an executable. Understanding the difference is essential for reading the Lab 01 Makefile.
The one-step view¶
When you run gcc -o hello hello.c, it looks like one operation, but gcc is actually doing several things behind the scenes: preprocessing, compiling to assembly, assembling to machine code, and linking. For a single source file, you never see the intermediate results.
The two-step view (compile, then link)¶
You can ask gcc to stop after producing an object file (.o) and then link separately. This is the model make uses. The -c flag means "compile only, do not link."
hello.c
|
| gcc -c hello.c (compile only)
v
hello.o <-- object file (machine code, not yet runnable)
|
| gcc -o hello hello.o (link)
v
hello <-- runnable executable
$ gcc -c hello.c # produces hello.o (note: no -o needed, defaults to hello.o)
$ gcc -o hello hello.o # links hello.o into the executable hello
$ ./hello World
Hello, World!
Why bother with two steps?¶
A single program with one source file does not benefit much. The payoff arrives in Project 01 and beyond, when programs share code across multiple files:
- Faster rebuilds: if you change only
argslens.c, only that file needs to be recompiled intoargslens.o. Unchanged object files are reused. - Code sharing: a helper module compiled once into a
.ocan be linked into several different programs.
The object file (.o) is real machine code, but it is not runnable on its own. It is missing the C runtime startup code and, for multi-file programs, the definitions of functions it calls. Linking resolves those references and produces the final executable.
flowchart TD
subgraph compile["Compile (per source file)"]
A[hello.c] -->|gcc -c| B[hello.o]
end
subgraph link["Link (combine objects)"]
B -->|gcc -o hello| C[hello executable]
D[C runtime startup] --> C
end
4. The Lab 01 Makefile¶
Typing the compile and link commands by hand gets tedious fast, and it is easy to forget a step. make automates it. You write a Makefile that declares what to build and how, and make figures out the when by comparing file timestamps.
Mental model¶
- A target is a file you want to build (e.g.,
hello). - A target has prerequisites (the files it depends on, e.g.,
hello.o). - A target has a recipe (the shell commands that build it).
makerebuilds a target only if a prerequisite is newer than the target, or the target is missing.
The Makefile for Lab 01¶
.PHONY: all clean
CC := gcc
CFLAGS := -g -O2 -Wall -Wextra
LDFLAGS := -g
PROGS := hello argslens
HELLO_OBJS := hello.o
ARGSLENS_OBJS := argslens.o
OBJS := ${HELLO_OBJS} ${ARGSLENS_OBJS}
%.o: %.c
${CC} -c ${CFLAGS} -o $@ $<
all: ${PROGS}
hello: ${HELLO_OBJS}
${CC} ${LDFLAGS} -o $@ $^
argslens: ${ARGSLENS_OBJS}
${CC} ${LDFLAGS} -o $@ $^
clean:
rm -rf ${PROGS} ${OBJS}
Reading it piece by piece¶
| Line / block | What it does |
|---|---|
.PHONY: all clean |
Declares all and clean as targets that are not real files, so make always runs them when asked |
CC := gcc |
The C compiler to use |
CFLAGS := -g -O2 -Wall -Wextra |
Compile flags: debug info, optimization, and lots of warnings |
LDFLAGS := -g |
Link flags: keep debug symbols in the executable |
PROGS := hello argslens |
The two executables to build |
%.o: %.c |
A pattern rule: how to turn any .c into the matching .o |
all: ${PROGS} |
The default goal: build everything |
The recipe lines use automatic variables. These are the most confusing part of make for newcomers, so commit them to memory:
| Variable | Meaning | In hello: hello.o |
|---|---|---|
$@ |
The target being built | hello |
$< |
The first prerequisite | hello.o |
$^ |
All prerequisites | hello.o |
So the pattern rule ${CC} -c ${CFLAGS} -o $@ $< expands, when building hello.o, to:
And the link rule ${CC} ${LDFLAGS} -o $@ $^ expands to:
Tabs, not spaces. Every recipe line must begin with a real tab character. If your editor inserts spaces,
makereports the cryptic errorMakefile:NN: *** missing separator. Stop.This is the single most common Makefile mistake.
What make actually runs¶
$ make
gcc -c -g -O2 -Wall -Wextra -o hello.o hello.c
gcc -g -o hello hello.o
gcc -c -g -O2 -Wall -Wextra -o argslens.o argslens.c
gcc -g -o argslens argslens.o
Run make again immediately and nothing happens, because every target is up to date:
Edit argslens.c, and only that program rebuilds:
Adding a new program¶
This is the modification you will make most often. To add a program foo:
- Write
foo.c. - Add
footoPROGS. - Add
FOO_OBJS := foo.o. - Add a link rule:
The pattern rule already knows how to compile foo.c into foo.o, so you do not need a compile rule.
Handy make commands¶
make # build the default goal (all)
make clean # delete executables and object files
make clean && make # full rebuild from scratch
make -n # dry run: print commands without running them
For more depth see the make guide.
5. Command-Line Arguments: argc and argv¶
This is the conceptual core of the session. Every C program's main can receive the words you type on the command line. The signature is:
argc("argument count") is the number of command-line words, including the program name itself.argv("argument vector") is an array of strings holding those words.argv[0]is always the program name;argv[1],argv[2], ... are the user's arguments.
You may also see argv written as char **argv; for our purposes char *argv[] and char **argv mean the same thing.
A concrete example¶
Suppose you run:
The shell splits that line on whitespace and hands the pieces to your program like this:
argc = 5
argv[0] ──> "./args"
argv[1] ──> "foo"
argv[2] ──> "cs315"
argv[3] ──> "computer"
argv[4] ──> "architecture"
argv[5] ──> NULL (the array is NULL-terminated)
A useful mental picture: argv is an array of pointers, each pointing at a NUL-terminated string somewhere in memory.
argv
+------+ +-------------------+
| [0] | -----> | '.' '/' 'a' 'r' 'g' 's' '\0' |
+------+ +-------------------+
| [1] | -----> | 'f' 'o' 'o' '\0' |
+------+ +-------------------+
| [2] | -----> | 'c' 's' '3' '1' '5' '\0' |
+------+ +-------------------+
| [3] | -----> | 'c' 'o' 'm' 'p' 'u' 't' 'e' 'r' '\0' |
+------+ +-------------------+
| [4] | -----> | 'a' 'r' 'c' 'h' ... '\0' |
+------+ +-------------------+
| [5] | -----> NULL
+------+
The args.c program¶
This is the program developed in class (available in the inclass repo). It prints each argument on its own line:
#include <stdio.h>
int main(int argc, char *argv[]) {
for (int i = 0; i < argc; i++) {
printf("argv[%d] = %s\n", i, argv[i]);
}
return 0;
}
$ gcc -o args args.c
$ ./args foo cs315 computer architecture
argv[0] = ./args
argv[1] = foo
argv[2] = cs315
argv[3] = computer
argv[4] = architecture
The loop runs from 0 to argc - 1. Using i < argc (not i <= argc) is critical: index argc holds the terminating NULL, and printing it as a %s string is undefined behavior.
Checking the argument count¶
Most real programs require a specific number of arguments and should fail gracefully otherwise. The hello program needs exactly one user argument (so argc == 2, counting the program name):
#include <stdio.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("usage: ./hello <str>\n");
return 1; // non-zero return signals an error
}
printf("Hello, %s!\n", argv[1]);
return 0; // zero return signals success
}
Returning 0 from main means success; any non-zero value means an error. The autograder and shell scripts rely on this convention.
6. Converting Argument Strings to Numbers¶
Everything in argv is a string, even when it looks like a number. If a user types ./repeat 3, then argv[1] is the two-byte string "3" (the character '3' followed by '\0'), not the integer 3.
To use it as a number, convert it with atoi() ("ASCII to integer") from <stdlib.h>:
#include <stdio.h>
#include <stdlib.h> // for atoi
int main(int argc, char *argv[]) {
if (argc != 3) {
printf("usage: ./echorepeat <str> <count>\n");
return 1;
}
int count = atoi(argv[2]); // convert the string "3" to the int 3
for (int i = 0; i < count; i++) {
printf("%s\n", argv[1]);
}
return 0;
}
This echorepeat example was demonstrated in class. The instructor noted that the current version hard-codes some assumptions and will be improved in a later session, but it illustrates the essential idea: read a string argument, convert it, and use it.
Gotcha:
atoireturns0for input it cannot parse (e.g.,atoi("abc")is0), and it does not report errors. For robust input validation, programs usestrtolinstead, butatoiis fine for the labs.
7. Measuring String Length with strlen¶
The argslens program extends args.c by also printing the length in characters of each argument. The length is computed with strlen() from <string.h>.
strlen counts the characters in a string up to, but not including, the terminating '\0'. So strlen("foo") is 3.
Building argslens.c¶
Start from args.c, add #include <string.h>, and append the length to each line:
#include <stdio.h>
#include <string.h> // for strlen
int main(int argc, char *argv[]) {
for (int i = 0; i < argc; i++) {
printf("argv[%d] = %s (%zu)\n", i, argv[i], strlen(argv[i]));
}
return 0;
}
$ gcc -o argslens argslens.c
$ ./argslens foo cs315 computer architecture
argv[0] = ./argslens (10)
argv[1] = foo (3)
argv[2] = cs315 (5)
argv[3] = computer (8)
argv[4] = architecture (12)
A note on the format specifier¶
strlen returns a value of type size_t (an unsigned integer type). The matching printf conversion is %zu. Using %d (for int) instead will compile, but with -Wall -Wextra the compiler warns you, and on some systems it prints the wrong value. Two acceptable approaches:
// Option A: use the correct specifier
printf("argv[%d] = %s (%zu)\n", i, argv[i], strlen(argv[i]));
// Option B: cast to int and use %d
printf("argv[%d] = %s (%d)\n", i, argv[i], (int)strlen(argv[i]));
Either matches the required output exactly. The point is to make the compiler warning go away, because the autograder expects clean, exact output.
8. Running the Autograder¶
The autograder, invoked as grade, runs your programs against a set of saved test cases and reports a score. It is the same tool that determines your Lab01 Tests grade, so running it yourself before submitting tells you exactly where you stand.
Installing the autograder on BeagleV¶
The autograder works out of the box on the BeagleV machines. You only need to clone it and put it on your PATH:
Then add it to your PATH by editing ~/.bash_profile. If the file does not exist, create it with your editor (micro ~/.bash_profile):
Log out and back in (or source ~/.bash_profile) so the change takes effect. Confirm it worked:
Pointing the autograder at the tests repo¶
You also need to clone the class tests repo and configure the autograder to find it. The tests live at https://github.com/USF-CS315-F25/tests.
Running the tests¶
From inside your lab01 directory (where your built hello and argslens live), run:
A perfect score is 100/100. Each test compares your program's output byte for byte against the expected output. This is why exact formatting matters: a missing exclamation point, a wrong space, or a trailing blank line will fail a test even though the output "looks right."
flowchart LR
A[grade test] --> B[Build your programs]
B --> C[Run with test inputs]
C --> D[Compare output<br/>to expected]
D --> E{Match exactly?}
E -- yes --> F[Test passes]
E -- no --> G[Test fails<br/>shows diff]
9. Submitting with Git¶
Once your programs pass, commit and push them to your Lab 01 GitHub repo. The basic cycle:
What to submit (and what NOT to submit)¶
| Submit | Do NOT submit |
|---|---|
Makefile |
executables (hello, argslens) |
hello.c |
object files (*.o) |
argslens.c |
anything make generates |
README.md (optional) |
a.out |
In short: submit source, never build products. A make clean before committing is a good habit so you do not accidentally git add a binary. If you already committed binaries, run make clean, then git add -A, commit, and push.
10. Common Mistakes and How to Fix Them¶
These are the issues that come up most often in lab. Scan this list before asking for help; the fix is usually here.
| Symptom | Likely cause | Fix |
|---|---|---|
command not found: hello |
Ran hello instead of ./hello |
Prefix with ./ to run from current directory |
Makefile:5: *** missing separator. Stop. |
Recipe indented with spaces | Replace the leading spaces with a real tab |
undefined reference to 'main' |
Compiled with -c but tried to run the .o |
Link the object: gcc -o hello hello.o |
implicit declaration of 'strlen' |
Missing #include <string.h> |
Add the header |
implicit declaration of 'atoi' |
Missing #include <stdlib.h> |
Add the header |
| Autograder fails a passing-looking test | Output not byte-exact (spacing, !, newline) |
Match the spec's output character for character |
| Segfault printing arguments | Loop ran to i <= argc, hit NULL |
Use i < argc |
which grade finds nothing |
PATH not updated |
Edit ~/.bash_profile, then re-login |
Wrong length printed in argslens |
Used %d for size_t |
Use %zu or cast (int)strlen(...) |
Committed hello/*.o to GitHub |
Forgot make clean |
make clean, then commit the deletions |
Key Concepts¶
| Concept | Definition | Example |
|---|---|---|
argc |
Count of command-line arguments, including the program name | ./args a b → argc == 3 |
argv |
Array of argument strings; argv[0] is the program name |
argv[1] == "a" |
| Object file | Compiled machine code for one source file, not yet runnable | hello.o |
| Linking | Combining object files + runtime into an executable | gcc -o hello hello.o |
-c flag |
Compile only; produce a .o, do not link |
gcc -c hello.c |
-o flag |
Name the output file | gcc -o hello hello.c |
strlen |
Returns the number of characters before '\0' |
strlen("foo") == 3 |
atoi |
Converts a numeric string to an int |
atoi("42") == 42 |
| Pattern rule | Generic Makefile rule for a class of files | %.o: %.c |
$@ / $< / $^ |
Make automatic vars: target / first prereq / all prereqs | In hello: hello.o, $@=hello, $<=hello.o |
| Phony target | A Make target that is not a file | .PHONY: all clean |
| Exit status | Return value of main; 0 = success |
return 1; on error |
Practice Problems¶
Problem 1: Predict argc and argv¶
A user runs:
What is the value of argc, and what does each element of argv hold?
Click to reveal solution
The shell splits the line on whitespace. `argv[0]` is always the program name, so four words means `argc == 4`. Index `4` holds the terminating `NULL` and must not be printed as a string.Problem 2: Spot the off-by-one bug¶
This loop is supposed to print every argument but crashes. Why?
Click to reveal solution
The condition should be `i < argc`, not `i <= argc`. When `i == argc`, the loop accesses `argv[argc]`, which is the terminating `NULL` pointer. Passing `NULL` to `printf` with `%s` dereferences a null pointer, which is undefined behavior and typically causes a segmentation fault. **Fix:**Problem 3: Convert one-step into two steps¶
You currently build with one command:
Rewrite this as the two-step compile-then-link process, and explain what intermediate file is produced.
Click to reveal solution
The intermediate file is the **object file** `argslens.o`. It contains compiled machine code but is not runnable on its own because it still needs the C runtime startup code, which is added during the link step. This two-step model is exactly what the Makefile automates: a pattern rule for compiling and an explicit link rule per program.Problem 4: Add a program to the Makefile¶
You wrote a new program wordcount.c. Show the lines you must add to the Lab 01 Makefile so that make wordcount works.
Click to reveal solution
You do **not** need a compile rule. The existing pattern rule `%.o: %.c` already knows how to build `wordcount.o` from `wordcount.c`. Remember that the recipe line under `wordcount:` must start with a real tab.Problem 5: Fix the argslens length output¶
A student's argslens prints the right strings but the lengths come out as garbage numbers, and the compiler warns about a format mismatch. Their line is:
What is wrong, and how do you fix it two different ways?
Click to reveal solution
`strlen` returns a `size_t` (unsigned), but `%d` expects an `int`. The mismatch causes the warning and can produce wrong output. **Fix A — use the correct specifier:** **Fix B — cast to int:** Both compile cleanly under `-Wall -Wextra` and produce the exact output the autograder expects.Problem 6: Why does the autograder fail a "correct" program?¶
Your hello prints Hello, World and you think that is right, but grade test fails the test. The spec shows Hello, World!. What general lesson does this teach about the autograder?
Click to reveal solution
The output is missing the trailing `!`. The autograder compares your program's output to the expected output **byte for byte**. Any difference fails the test, even one that looks trivial to a human: - a missing or extra `!` - a wrong number of spaces - a missing or extra newline (`\n`) - printing to `stderr` instead of `stdout` **Lesson:** match the spec's sample output exactly, including punctuation and whitespace. When a test fails, compare your output against the expected output character by character (the autograder shows the diff). **Fix:**Further Reading¶
- Lab 01 assignment
- C programming guide
- Make guide
- Source notes PDF
- Autograder repository
- Class tests repository
- Beej's Guide to C Programming
- GNU Make Manual
- learn-c.org
Summary¶
-
gcc -o hello hello.ccompiles and links in one command;./helloruns the result from the current directory. -
Compilation is really two steps: a source file compiles to an object file (
gcc -c→.o), and object files link into a runnable executable. A.ois machine code but is not runnable by itself. -
makeautomates the compile-and-link pipeline; it rebuilds only what changed, using pattern rules and the automatic variables$@,$<, and$^. Recipe lines must start with a tab. -
main(int argc, char *argv[])receives command-line arguments:argcis the count (including the program name), andargvis the array of argument strings, withargv[0]being the program name. -
Arguments are always strings. Use
atoi()(from<stdlib.h>) to convert a numeric argument to anint, andstrlen()(from<string.h>) to measure a string's length, printed with%zu. -
The two Lab 01 programs are
hello(printsHello, <str>!) andargslens(prints each argument with its length), both built from the provided Makefile. -
The autograder (
grade test) scores your programs against byte-exact expected output; install it on yourPATHand run it before submitting. -
Submit source only (
Makefile,hello.c,argslens.c, optionalREADME.md) to your Lab 01 GitHub repo, never executables or object files, and demonstrate your dev setup to a TA.