Why this matters
Many programs need a value before they start. A file converter needs a file name. A tool that adds numbers needs the numbers. You can type these values on the same line as the program's name, so the program receives them at once, with no prompt and no waiting. Tools such as ls, git and the C++ compiler itself work this way.
In a coding test you will usually read input with cin while the program runs. This lesson covers the other route, where the values arrive on the command line before the program starts. The checks you learn here, which turn text into numbers and reject bad text, also make cin input safer.
What the shell hands to a program
When you type a line in a terminal, a program called the shell reads it and starts the program you named. Here is a line you can type:
./args1 12 abc -xThe shell splits this line at spaces. The first word, ./args1, names the program to run. The other three words, 12, abc and -x, are called arguments. An argument is one word typed after the program's name. The shell passes all of them to the program when it starts, and the program reads them from main.
A word can contain spaces if you wrap it in double quotes. The line ./args2 "1 2" passes one argument, the text 1 2, which contains a space. The program sees it as one piece. This matters in the mistakes section below.
C++ gives the program two values through the parameters of main:
argcis the count of words, and it includes the program name. The name stands for "argument count".argvis the list of words. The name stands for "argument vector", and a vector here means a list.
The line int main(int argc, char* argv[]) declares both. Read char* argv[] as "a list of pieces of text". Each piece is one word from the command line, and the list has argc entries. Pointers are covered in a later module, so for now treat each entry as one piece of text. Each piece ends with a hidden zero character, which tells the program where the word stops.
The first entry, argv[0], holds the program name. Usually it is the name as you typed it. The real program can be started in other ways, so do not base any logic on this name. The first real argument is argv[1]. On normal systems argc is never 0, because argv[0] is always there. When the user types nothing after the name, argc is 1.
Example 1: print every argument
This first program prints argc and each entry of argv. It does not use the words for anything yet. Its job is to show you what arrives.
#include <iostream>
#include <span>
#include <string_view>
int main(int argc, char* argv[]) {
std::span<char*> args(argv, static_cast<std::size_t>(argc));
std::cout << "argc = " << argc << '\n';
for (std::size_t i = 0; i < args.size(); ++i) {
std::string_view text = args[i];
std::cout << "argv[" << i << "] = \"" << text << "\"\n";
}
return 0;
}Two lines need explaining. The line that creates args uses std::span, a standard tool from C++20 that holds a start point and a length. Its length must be an unsigned count, std::size_t, and argc is a signed int. The static_cast says that the conversion is intended. The line std::string_view text = args[i]; wraps one piece of text in a std::string_view. A string view does not copy the text. It records where the text starts and how long it is, so you can compare it, measure it and print it.
Compile and run it with the commands below. Use -std=c++20 to select the C++20 standard, and -Wall to ask the compiler to report common mistakes.
g++ -std=c++20 -Wall -o args1 args1.cpp
./args1
./args1 12 abc -xThe output of the first run is:
argc = 1
argv[0] = "./args1"The output of the second run is:
argc = 4
argv[0] = "./args1"
argv[1] = "12"
argv[2] = "abc"
argv[3] = "-x"Each word is text, even 12. The program has not turned 12 into a number. That conversion is the next step.
Turning text into numbers
The text 12 is two characters: the digit 1 and the digit 2. The number 12 is a value that you can add and compare. A program must convert the text, and it must decide what to do when the text is not a number at all, such as abc.
The C++ standard library has several tools for this. Two of them matter here.
| Tool | On bad input | Notes |
|---|---|---|
std::stoi | throws an exception | skips spaces at the start and stops at the first letter without an error |
std::from_chars | returns an error code | does not skip spaces, and tells you where it stopped |
An exception is an error signal. If the program does not catch it, the program stops. The next program shows what std::stoi does with bad input. Its output is shown after the code.
#include <iostream>
#include <stdexcept>
#include <string>
int main() {
std::cout << "stoi(\"12abc\") = " << std::stoi("12abc") << '\n';
std::cout << "stoi(\" 7\") = " << std::stoi(" 7") << '\n';
try {
std::stoi("abc");
} catch (const std::invalid_argument& e) {
std::cout << "invalid_argument: " << e.what() << '\n';
}
try {
std::stoi("99999999999");
} catch (const std::out_of_range& e) {
std::cout << "out_of_range: " << e.what() << '\n';
}
return 0;
}stoi("12abc") = 12
stoi(" 7") = 7
invalid_argument: stoi
out_of_range: stoiThe first line shows the trap. The text 12abc becomes 12, and the letters are dropped without any message. If a user types 12abc as a count, the program quietly uses 12. The std::stoi tool is fine when you control the text, but it is weak for checking user input.
std::from_chars does the checking for you. It reads a range of characters and returns two things. The first is an error code, which says whether the conversion worked. The second is a pointer, which says where the reading stopped. If the stop point is not the end of the text, then some characters were left over and the whole text was not a number. You will see this in the next example.
std::from_chars needs the start and end of the text. A std::string_view gives both: text.data() is the start, and text.data() + text.size() is the end. Because from_chars lives in the <charconv> header, you must include it.
Example 2: add the whole numbers you were given
This program adds every argument after the program name. It checks each one, and if one is bad it prints a message and stops. The function parse_int does the checking, and it returns a std::optional<int>. An optional is a value that either holds an int or holds nothing. The nothing case means the text was not a whole number.
#include <charconv>
#include <iostream>
#include <optional>
#include <span>
#include <string_view>
// Returns the whole text as an int, or nothing if the text is not a whole number.
std::optional<int> parse_int(std::string_view text) {
int value = 0;
const char* first = text.data();
const char* last = text.data() + text.size();
auto [ptr, ec] = std::from_chars(first, last, value);
if (ec != std::errc{} || ptr != last) {
return std::nullopt;
}
return value;
}
int main(int argc, char* argv[]) {
if (argc < 2) {
std::cerr << "usage: " << argv[0] << " NUMBER [NUMBER ...]\n";
return 1;
}
std::span<char*> args(argv, static_cast<std::size_t>(argc));
long long total = 0;
for (std::size_t i = 1; i < args.size(); ++i) {
std::optional<int> value = parse_int(args[i]);
if (!value) {
std::cerr << "error: argument " << i << " (\"" << args[i] << "\") is not a whole number\n";
return 2;
}
total += *value;
}
std::cout << "sum = " << total << '\n';
return 0;
}The line auto [ptr, ec] = ... is a structured binding. It unpacks the two results of from_chars into two names, ptr and ec. The condition ec != std::errc{} is true when there was an error. std::errc{} is the code for "no error", written with empty braces so that it has the default value. The condition ptr != last is true when characters remain after the number. For the text 12abc, from_chars reads 12 and stops at the a, so ptr points at a, and the check rejects the text. That is the difference from std::stoi.
The loop starts at index 1, not 0, because index 0 is the program name. The variable total is a long long, which can hold larger sums than an int.
Three runs show the three outcomes:
./args2 12 30 -4
./args2
./args2 12 abcsum = 38
usage: ./args2 NUMBER [NUMBER ...]
error: argument 2 ("abc") is not a whole numberThe usage message uses argv[0] so that it shows the name the user typed. The error message gives the position of the bad argument, which helps the user find it.
Example 3: a count with a range check
Many tools take a count and then some text. The next program repeats the text the given number of times. It must check two things: the count is a whole number, and the count is in a sensible range. Here the range is 1 to 5.
#include <charconv>
#include <cstddef>
#include <iostream>
#include <optional>
#include <span>
#include <string_view>
std::optional<int> parse_int(std::string_view text) {
int value = 0;
const char* first = text.data();
const char* last = text.data() + text.size();
auto [ptr, ec] = std::from_chars(first, last, value);
if (ec != std::errc{} || ptr != last) {
return std::nullopt;
}
return value;
}
int main(int argc, char* argv[]) {
if (argc < 3) {
std::cerr << "usage: " << argv[0] << " COUNT TEXT [TEXT ...]\n";
return 1;
}
std::span<char*> args(argv, static_cast<std::size_t>(argc));
std::optional<int> count = parse_int(args[1]);
if (!count) {
std::cerr << "error: COUNT must be a whole number, got \"" << args[1] << "\"\n";
return 2;
}
if (*count < 1 || *count > 5) {
std::cerr << "error: COUNT must be from 1 to 5, got " << *count << '\n';
return 2;
}
for (int round = 0; round < *count; ++round) {
for (std::size_t i = 2; i < args.size(); ++i) {
std::cout << args[i] << (i + 1 < args.size() ? " " : "\n");
}
}
return 0;
}The first check needs argc to be at least 3, because the program needs a count and at least one word. The count is then checked twice: once to see that it is a number, and once to see that it is in range. Keeping the two checks separate gives two different messages, so the user knows which part of the input is wrong.
The inner loop prints each word and puts a space after it, except for the last word, where it prints a line break. The conditional (i + 1 < args.size() ? " " : "\n") picks one of two values. This is the same pattern as choosing between two prints in an if, written in one line.
./repeat 2 hi there
./repeat 9 x
./repeat abc xhi there
hi there
error: COUNT must be from 1 to 5, got 9
error: COUNT must be a whole number, got "abc"Example 4: decimal numbers
Whole numbers are not always enough. An average, a price or a measurement can have a decimal point. The standard library has std::from_chars for double too, and it works the same way. The text must match a number format, and the whole text must be used.
#include <charconv>
#include <cstddef>
#include <iomanip>
#include <iostream>
#include <optional>
#include <span>
#include <string_view>
std::optional<double> parse_double(std::string_view text) {
double value = 0.0;
const char* first = text.data();
const char* last = text.data() + text.size();
auto [ptr, ec] = std::from_chars(first, last, value);
if (ec != std::errc{} || ptr != last) {
return std::nullopt;
}
return value;
}
int main(int argc, char* argv[]) {
if (argc < 2) {
std::cerr << "usage: " << argv[0] << " NUMBER [NUMBER ...]\n";
return 1;
}
std::span<char*> args(argv, static_cast<std::size_t>(argc));
double sum = 0.0;
std::size_t count = 0;
for (std::size_t i = 1; i < args.size(); ++i) {
std::optional<double> value = parse_double(args[i]);
if (!value) {
std::cerr << "error: \"" << args[i] << "\" is not a number\n";
return 2;
}
sum += *value;
++count;
}
std::cout << std::fixed << std::setprecision(2);
std::cout << "average of " << count << " numbers = " << sum / static_cast<double>(count) << '\n';
return 0;
}std::fixed and std::setprecision(2) come from <iomanip>. They make the output show exactly two digits after the decimal point. Without them, the average would print as 2.66667, with six significant digits.
./average 1.5 2.5 4
./average -2e1 10
./average 1.5 xaverage of 3 numbers = 2.67
average of 2 numbers = -5.00
error: "x" is not a numberThe e in -2e1 is the exponent form, meaning minus 2 times 10 to the power 1, which is minus 20. The from_chars function accepts this form. Like the integer version, it does not accept a leading plus sign, so +5 is rejected. Running ./average +5 1 prints error: "+5" is not a number.
Exit codes
When main returns a number, that number goes to the shell as the exit code. By convention 0 means success, and any other value means the program failed. The shell can read the last exit code through $?. The programs above use 1 for a usage error and 2 for bad input. The numbers are a convention chosen by the program's author, so write them down and keep them the same across a program.
./args2 12 abc
echo "exit code $?"error: argument 2 ("abc") is not a whole number
exit code 2Scripts and other programs use this number to decide what to do next, so a message on the screen is not enough. Return a non-zero code whenever the program could not do its work.
Common mistakes
Reading argv[1] without checking argc. When the user types nothing after the program name, argc is 1, and argv[1] is past the last word. In C++ this is undefined behaviour, which means the program may do anything. The next program reads argv[1] without a check:
#include <iostream>
#include <string_view>
int main(int argc, char* argv[]) {
std::cout << "argc = " << argc << '\n';
std::string_view first = argv[1];
std::cout << "first argument: " << first << '\n';
return 0;
}Run it with no arguments:
./argv_crash
echo "exit code $?"exit code 139On Linux and macOS, the exit code 139 means the program was stopped by a segmentation fault. A segmentation fault is the operating system stopping a program that touched memory it is not allowed to use. The line argc = 1 may not reach the screen when the output goes to a pipe or a file, because the program stopped while the text was still in a holding area that had not been sent out yet. The fix is the check at the start of each example: if (argc < 2), with a usage message.
Comparing an argument with == to a string literal. The text "add" in the code is a piece of memory, and argv[1] is another piece of memory. The == operator compares addresses here (the memory locations where each text starts), not letters. The next program makes the mistake:
#include <iostream>
int main(int argc, char* argv[]) {
if (argv[1] == "add") {
std::cout << "pointer comparison said: add\n";
} else {
std::cout << "pointer comparison said: not add\n";
}
return 0;
}The compiler warns about it:
warning: comparison with string literal results in unspecified behavior [-Waddress]When you run it with ./argv_pitfalls add, the output is pointer comparison said: not add, even though the argument is add. Compare with a std::string_view instead, as in std::string_view(argv[1]) == "add". This compares the letters.
Trusting std::stoi with user text. As shown earlier, std::stoi turns 12abc into 12 with no error. Use std::from_chars and check the stop point, as in parse_int.
Forgetting the <charconv> include. Without #include <charconv>, the compiler stops with an error. The real message is:
error: ‘from_chars’ is not a member of ‘std’The fix is the include line at the top of the file.
Expecting a quoted argument to be split. The shell keeps "5 6" as one argument. The program receives the text 5 6, and from_chars stops at the space, so the check rejects it:
./args2 "5 6"
echo "exit code $?"error: argument 1 ("5 6") is not a whole number
exit code 2If you want two numbers, the user must type them as two words, with no quotes: ./args2 5 6.
Practice
Exercise 1. Write a program that prints how many arguments the user typed, not counting the program name. Running ./count 4 9 x should print typed 3 arguments.
Show the answer
#include <iostream>
int main(int argc, char* argv[]) {
std::cout << "typed " << argc - 1 << " arguments\n";
return 0;
}The count is argc minus 1, because argv[0] is the program name. Running ./count 4 9 x prints typed 3 arguments.
Exercise 2. The program calls std::stoi(argv[1]) and prints the result. What does ./predict 7x print?
Show the answer
It prints 7. The function std::stoi reads the digit 7, stops at the x, and returns 7 without an error. The same behaviour was shown with 12abc earlier.
Exercise 3. Write a program that prints the largest whole number among the arguments. It must print a usage message and exit with code 1 when no argument is given. It must print an error and exit with code 2 when an argument is not a whole number.
Show the answer
#include <charconv>
#include <cstddef>
#include <iostream>
#include <optional>
#include <span>
#include <string_view>
std::optional<int> parse_int(std::string_view text) {
int value = 0;
const char* first = text.data();
const char* last = text.data() + text.size();
auto [ptr, ec] = std::from_chars(first, last, value);
if (ec != std::errc{} || ptr != last) {
return std::nullopt;
}
return value;
}
int main(int argc, char* argv[]) {
if (argc < 2) {
std::cerr << "usage: " << argv[0] << " NUMBER [NUMBER ...]\n";
return 1;
}
std::span<char*> args(argv, static_cast<std::size_t>(argc));
std::optional<int> largest = parse_int(args[1]);
if (!largest) {
std::cerr << "error: argument 1 is not a whole number\n";
return 2;
}
for (std::size_t i = 2; i < args.size(); ++i) {
std::optional<int> value = parse_int(args[i]);
if (!value) {
std::cerr << "error: argument " << i << " is not a whole number\n";
return 2;
}
if (*value > *largest) {
largest = value;
}
}
std::cout << "largest = " << *largest << '\n';
return 0;
}Running ./ex_largest 4 19 -3 7 prints largest = 19. Running ./ex_largest 4 x prints error: argument 2 is not a whole number and exits with 2.
Exercise 4. A program compares argv[1] with the text start using if (argv[1] == "start"). The compiler warns, and the comparison is wrong. Fix it.
Show the answer
Use a std::string_view for the comparison, and check argc first:
#include <iostream>
#include <string_view>
int main(int argc, char* argv[]) {
if (argc < 2) {
std::cout << "no command given\n";
return 1;
}
if (std::string_view(argv[1]) == "start") {
std::cout << "starting\n";
} else {
std::cout << "unknown command: " << argv[1] << '\n';
}
return 0;
}Running ./start_check start prints starting. Running ./start_check stop prints unknown command: stop. Running it with no argument prints no command given and exits with 1.
Exercise 5. Predict the output of ./args2 "5 6" and explain why.
Show the answer
It prints error: argument 1 ("5 6") is not a whole number and exits with 2. The quotes make the shell pass one argument, 5 6, with a space in it. The from_chars function reads 5, stops at the space, and the check sees characters left over, so the text is rejected.
Summary
- The shell splits the command line at spaces, and the program receives the words as
argcandargv. argv[0]is the program name, so the first real argument isargv[1], andargcis 1 when nothing was typed.- Check
argcbefore readingargv[k], and compare text withstd::string_view, not==onargventries. - Use
std::from_charsto convert text to a number, becausestd::stoiaccepts12abcas 12. - Check that the whole text was used, and return a non-zero exit code when the input is wrong.