How Do I Use Command-Line Arguments in C?
argc, argv, and the connection between your C programs and every Unix command
After this lesson, you will be able to:
- Write
mainwithint argc, char *argv[]to accept command-line arguments - Explain that
argcincludes the program name, so user arguments start atargv[1] - Validate
argcand print a usage message when wrong number of arguments is provided - Convert string arguments to numeric values using
atoiandatof - Explain why
argventries are always strings
Every Unix Command Does This
Think about every command you’ve used this quarter:
ls -la /home
grep -rn "printf" *.c
chmod 755 myscript.sh
gcc -Wall -o hello hello.c
Each one takes arguments — flags, filenames, patterns. How does the program access them? Through argc and argv — the same mechanism you’ll use in your own programs.
argc and argv
The Full main Signature
Until now, we’ve written:
int main(void) { ... }
The full version accepts command-line arguments:
int main(int argc, char *argv[]) { ... }
argc— argument count (number of arguments, including the program name)argv— argument vector (array of strings, each argument as achar*)
How It Works
If you run:
./calculator 10 + 5
Then:
argc= 4argv[0]="./calculator"(program name)argv[1]="10"argv[2]="+"argv[3]="5"argv[4]=NULL(always NULL-terminated)
Key Insight: Every Unix command you’ve used —
ls -l,grep pattern file,chmod 755 file— is a C program that reads its flags and arguments fromargcandargv. When you write programs that accept command-line arguments, you’re building tools that work exactly like the Unix commands you’ve been using.
Printing All Arguments
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("You passed %d arguments:\n", argc);
for (int i = 0; i < argc; i++)
{
printf(" argv[%d] = \"%s\"\n", i, argv[i]);
}
return 0;
}
./args hello world 42
You passed 4 arguments:
argv[0] = "./args"
argv[1] = "hello"
argv[2] = "world"
argv[3] = "42"
Validating Arguments
Always check that the user provided the right number of arguments:
if (argc != 3)
{
printf("Usage: %s <filename> <count>\n", argv[0]);
return 1;
}
Using argv[0] in the usage message is a nice touch — it shows the actual program name the user typed.
Common Pitfall:
argcincludes the program name, so./program file1 file2hasargc = 3, not 2. The actual arguments start atargv[1]. This off-by-one trips up everyone at least once.
./greet Alice Bob. What is the value of argc?argc counts all arguments including the program name (argv[0]). So ./greet is argument 0, Alice is argument 1, Bob is argument 2 — three total. The trailing NULL in argv is not counted by argc. This off-by-one is the most common argc/argv mistake.
Converting String Arguments to Numbers
Command-line arguments are always strings. To use them as numbers:
#include <stdlib.h>
int count = atoi(argv[1]); // String to int
double rate = atof(argv[2]); // String to double
Common Pitfall:
atoihas no error checking —atoi("hello")returns 0 without any indication that the conversion failed. For production code, usestrtolorstrtodwhich report errors. For labs,atoi/atofare fine.
./calc 42 and use argv[1] directly in arithmetic: int result = argv[1] + 10;. What happens?argv[1] is a char* (a pointer to a string), not a number. Adding 10 to it performs pointer arithmetic — it moves the pointer 10 bytes forward in memory, which is not what you want. C doesn't auto-convert strings to numbers. You must explicitly call atoi(argv[1]) or atof(argv[1]) to get a numeric value.
Why does this matter?
This is a common mistake when you’re used to languages like Python or JavaScript that auto-convert types. C never converts a string to a number implicitly. Every command-line argument is a string — you must convert it yourself. Getting comfortable with atoi/atof (and eventually strtol/strtod) is essential for any program that takes numeric input from the command line.
Building a Calculator
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
if (argc != 4)
{
printf("Usage: %s <num1> <op> <num2>\n", argv[0]);
printf(" ops: + - x /\n");
return 1;
}
double num1 = atof(argv[1]);
char op = argv[2][0];
double num2 = atof(argv[3]);
double result;
switch (op)
{
case '+':
result = num1 + num2;
break;
case '-':
result = num1 - num2;
break;
case 'x':
result = num1 * num2;
break;
case '/':
if (num2 == 0)
{
printf("Error: division by zero\n");
return 1;
}
result = num1 / num2;
break;
default:
printf("Unknown operator: %c\n", op);
return 1;
}
printf("%.2f %c %.2f = %.2f\n", num1, op, num2, result);
return 0;
}
./calc 10 + 5
# 10.00 + 5.00 = 15.00
./calc 100 / 3
# 100.00 / 3.00 = 33.33
The Trick: We use
'x'for multiplication instead of'*'because the shell interprets*as a wildcard (glob). Running./calc 10 * 5would expand*to filenames in the current directory. Using'x'avoids the issue.
Connecting to Unix Philosophy
Think about it: you’ve now built a command-line tool. It takes arguments, validates them, does a computation, and prints results. That’s exactly what grep, chmod, and wc do. Your programs are Unix commands.
From Java: In Java,
public static void main(String[] args)serves the same purpose.args[0]is the first argument (not the class name — Java strips that). In C,argv[0]is the program name andargv[1]is the first user argument. The shift by one is important.
'x' for multiplication instead of '*'?./calc 10 * 5, the shell sees * and expands it to all filenames in the current directory before your program runs. Your program would receive argv full of filenames instead of the multiplication operator. Using x sidesteps the problem. (You could also quote it: ./calc 10 '*' 5.)
Quick Check: If you run ./myprog file.txt 42, what are argc, argv[0], and argv[2]?
argc = 3, argv[0] = "./myprog", argv[2] = "42" (as a string, not an integer — use atoi to convert).
Quick Check: Why is argv[2] a string even when the user typed a number?
The shell passes all arguments as strings. argv is an array of char* (string pointers). To use an argument as a number, you must convert it explicitly with atoi() (for int) or atof() (for double).
Quick Check: Why use argv[0] in usage messages?
It shows the actual command name the user typed. If they rename the executable or run it from a different path, the usage message still shows the correct name. This is standard Unix convention.
Your Programs Are Unix Commands Now
With argc and argv, your C programs fit right into the Unix ecosystem. They can take file names, flags, and parameters just like ls, grep, and gcc. Combined with stdin/stdout (from Lesson 1.5), your programs can participate in pipelines too.
Series 2 is complete. You’ve gone from your first #include <stdio.h> through arrays, strings, sorting, and command-line tools. You can write real C programs — the kind that compile, run, and do useful work.
But there’s a fundamental limitation you’ve been bumping into: functions can’t modify their caller’s variables. swap(x, y) doesn’t work. You can’t return two values from a function. You can’t dynamically allocate memory.
In Series 3: Pointers & Memory, all of that changes. Pointers are C’s most powerful feature — and the key to understanding how computers actually work.
The CTF Challenges for Weeks 3–5 cover C foundations — programming basics, loops, functions, arrays, and strings. Good way to stress-test what you just learned.
Big Picture: You now understand C’s approach to data (raw arrays with no safety net), text (null-terminated character arrays), and code organization (header files and Makefiles). Next week, you tackle the most powerful and dangerous feature of C: pointers. Everything from here builds on what you’ve learned in Series 2 — especially arrays, since pointer arithmetic is just array indexing in disguise.