Why this matters
Almost every tool you use in a terminal takes arguments: ls -l /tmp, gcc -o prog main.c, grep -i error log.txt. Arguments are how a program learns what to work on without asking interactively, which is what makes programs scriptable and composable. In C they arrive as the parameters of main, and they are your first real encounter with an array of strings, a structure you will see again and again.
The other form of main
So far main took no parameters. It can instead be written as
int main(int argc, char *argv[])
argc("argument count") is the number of strings.argv("argument vector") is an array of those strings.
The char * part means "address of a char"; argv is an array whose elements are each the address of the first character of a string. Pointers are the next module, which explains that notation fully. Until then, all you need is: argv[i] is a string, usable anywhere a string is, with %s, strcmp, strlen and the rest.
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("argc = %d\n", argc);
for (int i = 0; i < argc; i++) {
printf("argv[%d] = \"%s\"\n", i, argv[i]);
}
if (argv[argc] == NULL) {
printf("argv[%d] is NULL\n", argc);
}
return 0;
}
$ ls *.txt
notes.txt todo.txt
$ ./prog hello "two words" 42 *.txt
argc = 6
argv[0] = "./prog"
argv[1] = "hello"
argv[2] = "two words"
argv[3] = "42"
argv[4] = "notes.txt"
argv[5] = "todo.txt"
argv[6] is NULL
Several things to notice:
argv[0]is the program name, as it was typed. The real arguments start atargv[1], so a program run with no arguments hasargc == 1.argv[argc]isNULL, a special "no address" value, marking the end of the list. Some code loops until it finds it instead of usingargc.- Everything is a string.
42arrives as the two characters'4'and'2'plus a terminator, not as a number. Converting it is your job. - The shell did the splitting, quoting and wildcard expansion. Your program never saw the quotes around
two wordsor the*: the shell expanded*.txtinto matching file names before starting the program. C itself knows nothing about any of this.
Where the strings live
The kernel copies the argument strings into the new process's memory, near the top of the stack, and the C startup code builds the argv array pointing at them before calling main:
argv (array of addresses) the strings themselves
+---------+
| argv[0] | -------------------------> "./prog\0"
+---------+
| argv[1] | -------------------------> "hello\0"
+---------+
| argv[2] | -------------------------> "two words\0"
+---------+
| argv[3] | -------------------------> "42\0"
+---------+
| NULL | end marker (argv[argc])
+---------+
Each string is an ordinary terminated char array. The arrows are pointers; you will draw many diagrams like this in the next module. The strings stay valid for the whole run of the program.
Converting arguments to numbers
To turn "42" into the integer 42, you can use sscanf, which works exactly like scanf but reads from a string instead of from input. It returns how many values it converted. A useful trick detects trailing junk: ask for one more character after the number. If the whole string was a number, there is no character to read and sscanf returns 1; if anything follows, it returns 2.
#include <stdio.h>
int main(int argc, char *argv[])
{
if (argc != 3) {
fprintf(stderr, "usage: %s WIDTH HEIGHT\n", argv[0]);
return 2;
}
int w, h;
char extra;
if (sscanf(argv[1], "%d%c", &w, &extra) != 1 || sscanf(argv[2], "%d%c", &h, &extra) != 1) {
fprintf(stderr, "%s: WIDTH and HEIGHT must be whole numbers\n", argv[0]);
return 1;
}
printf("area = %d\n", w * h);
return 0;
}
$ ./prog 3 4
area = 12
$ ./prog 3
usage: ./prog WIDTH HEIGHT
$ echo $?
2
$ ./prog 3 4x
./prog: WIDTH and HEIGHT must be whole numbers
$ echo $?
1
You will also see atoi(argv[1]) from <stdlib.h> in a lot of code. It is shorter but cannot report errors: atoi("hello") is simply 0, and out-of-range input is undefined behavior. The input/output module introduces strtol, the robust standard choice, which also detects overflow. sscanf with %d does not detect overflow either, so treat it as a stepping stone.
Usage messages, stderr and exit status
Well-behaved command-line programs follow conventions that scripts depend on:
- Check
argcbefore touchingargv[i]. Readingargv[1]whenargcis 1 givesNULL; passing that toprintf("%s")orstrcmpis undefined behavior and usually a crash. - On misuse, print a usage line that shows the expected form:
usage: prog [-u] COUNT WORD. Square brackets mean optional, capitals mean "put a value here",...means "one or more". - Error messages go to standard error, with
fprintf(stderr, "..."). Standard error is a second output stream that is not redirected by>or swallowed by a pipe, so the user sees the message even when output goes to a file. Streams are explained in the I/O module. - Exit nonzero on failure. A common convention, used by many GNU tools, is 2 for "you used the program wrong" and 1 for "the work itself failed".
- Prefixing messages with the program name (
sum: not a number: x) helps users of pipelines tell which program complained.
Options
Arguments starting with - are conventionally options: -v, -n 5, --help. A program recognizes them by comparing strings:
if (argc > 1 && strcmp(argv[1], "-v") == 0) {
verbose = 1;
}
Real programs with many options use the POSIX function getopt (man 3 getopt), which handles combined flags like -lh and option arguments. Hand-parsing one or two options is fine.
Common mistakes
- Starting at
argv[0]when you meant the first real argument. - Indexing without checking
argc:./progwith no arguments makesargv[1]NULL; aprintf("%s", argv[1])then prints(null)with glibc or crashes elsewhere, andstrcmpcrashes withSegmentation fault. - Comparing with
==:if (argv[1] == "-v")compares addresses, never the text, and gcc warnscomparison with string literal results in unspecified behavior. Usestrcmp. - Treating arguments as numbers:
argv[1] * 2does not compile (invalid operands to binary *). Convert first. - Modifying argument strings: it is allowed by the standard but surprising to readers; copy into your own buffer (
snprintf) before changing the text.
Recap
int main(int argc, char *argv[])receives the command line:argcstrings inargv.argv[0]is the program name;argv[1]toargv[argc - 1]are the arguments;argv[argc]isNULL.- The shell splits words, removes quotes and expands wildcards before your program starts.
- Arguments are strings; convert with
sscanf(and soonstrtol), checking for bad input. - Check
argc, print a usage line tostderr, and return a nonzero status on misuse.
Going further: run ./prog * in a directory with many files to see how big argc can get. Then read the start of man 3 getopt and its EXAMPLES section.