Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

C++  Module 4 – Functions, files and scope

C++ command-line arguments and exit codes

Read C++ command-line arguments through argc and argv, report success or failure with exit codes, and read environment variables without crashing.

  • Beginner
  • 20 minutes
  • Examples run with Clang 21.0.0, GCC 16.2.0 and Python 3.14.8
  • By MySmartCoPilot
Free: 20 minutes

What you will learn

  • Read command-line arguments through argc and argv, and predict what the shell passes
  • Convert an argument to a number with std::from_chars and reject text that is not one
  • Choose exit statuses from main that scripts can act on, within the 8 bits that survive
  • Explain which clean-up std::exit skips that a return from main performs
  • Read an environment variable with std::getenv without dereferencing a null pointer

Before you start

What you’ll learn · Before you start

What you will learn

  • Read command-line arguments through argc and argv, and predict what the shell passes
  • Convert an argument to a number with std::from_chars and reject text that is not one
  • Choose exit statuses from main that scripts can act on, within the 8 bits that survive
  • Explain which clean-up std::exit skips that a return from main performs
  • Read an environment variable with std::getenv without dereferencing a null pointer

Before you start

On this page

Type ./show_args report.txt --top 3, and main receives an argc of 4 and four strings; return 256 from main, and the shell sees 0. A C++ program talks to whoever started it through three channels: the words of its command line, the number main returns, and the environment variables it inherits.

You will read arguments and convert them to numbers safely, return exit statuses that scripts can act on, see which clean-up std::exit skips, and read an environment variable without crashing: one unchecked std::getenv crashed in two different ways with the two standard libraries. Every program here ran on macOS with Apple clang 21 and GCC 16.

main receives its command line as argc and argv

C++ allows two forms of main: int main(), for programs that take no arguments, and int main(int argc, char* argv[]). The names argc (argument count) and argv (argument vector) are only conventions, but everyone uses them. This program prints what it receives, in brackets, so that spaces and empty arguments show:

show_args.cpp C++ · show_args.cpp
// show_args.cpp: prints what the program received on its command line.
#include <print>

int main(int argc, char* argv[]) {
    std::println("argc = {}", argc);
    for (int i = 0; i < argc; ++i) {
        // the brackets show spaces and empty arguments
        std::println("argv[{}] = [{}]", i, argv[i]);
    }
    std::println("argv[argc] is {}", argv[argc] == nullptr ? "a null pointer" : "not null");
}

For ./show_args report.txt --top 3, main receives an argc of 4 and an array of five pointers: four to the command-line argument: A word given to a program when it is started, such as report.txt in ./show_args report.txt. main receives the words, already split, expanded and unquoted by the shell, through argc and argv., the program name included, as C strings, and a last one that is a null pointer. Step through them:

Interactive · step by step What main receives for ./show_args report.txt --top 3 Step through argc, the array argv points to and the strings at the end of each pointer.
What main receives for ./show_args report.txt --top 3, step 6 of 6. Showing argc, argv, argv[0], argv[1], argv[2], argv[3], argv[4], ./show_args, report.txt, --top, 3, a null pointer. In focus: ./show_args, report.txt, --top, 3.points toargc4argvthe address of argv[0]argv[0]argv[1]argv[2]argv[3]argv[4]argv[argc]./show_argsthe name it was started byreport.txt--top3text, not a numbera null pointerpoints to nothing

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. The count. argc is 4: the program name and the three arguments the shell made of the line. It counts every entry of argv except the last.
  2. An array of pointers. argv holds the address of an array of five pointers, argv[0] to argv[4]: one per entry, and one more.
  3. The program name. argv[0] points to the name used to start the program, ./show_args, as a C string: the characters followed by a zero byte.
  4. The arguments. argv[1] to argv[3] point to report.txt, --top and 3. Every one is text, even 3, which must be converted before it can be used as a number.
  5. The end marker. argv[4], which is argv[argc], is a null pointer, so a loop can also stop when it meets it. The session printed argv[argc] is a null pointer.
  6. Valid all run long. The strings keep their values until the program ends, so views of them, such as a std::vector of std::string_view, never dangle.

Three command lines, and what main saw Python · args_session.py
from session import run

# What main receives: argc counts the program name too, and argv[argc] is a null pointer.
run(r"""
clang++ -std=c++23 -Wall -Wextra show_args.cpp -o show_args
./show_args
./show_args report.txt --top 3
cp show_args renamed && ./renamed
""")

Output

$ clang++ -std=c++23 -Wall -Wextra show_args.cpp -o show_args
$ ./show_args
argc = 1
argv[0] = [./show_args]
argv[argc] is a null pointer
$ ./show_args report.txt --top 3
argc = 4
argv[0] = [./show_args]
argv[1] = [report.txt]
argv[2] = [--top]
argv[3] = [3]
argv[argc] is a null pointer
$ cp show_args renamed && ./renamed
argc = 1
argv[0] = [./renamed]
argv[argc] is a null pointer

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 args_session.py

With no arguments, argc is 1: the program name alone. For ./show_args report.txt --top 3 it is 4, and argv[1] to argv[3] are the three arguments; --top and 3 are two separate words, because to the program an option is just an argument that starts with -. argv[argc] is always a null pointer, so code can also walk the array until it meets it.

The standard only promises that argv[0] represents the name used to start the program, or is an empty string. After cp show_args renamed, the same program printed ./renamed. Use argv[0] in messages, never to find the program’s own files.

Checkpoint

Check: argc, argv and argv[0]

  1. Question 1 of 2 What is argc when the program is started as ./show_args -n 5 report.txt?

    Type a number.

    A hint

    Count the program name too.

    Show the answer: Question 1 of 2

    Answer: 4

    The program name and three arguments: ./show_args, -n, 5 and report.txt. argv[1] to argv[3] hold the arguments, and argv[4], argv[argc], is a null pointer.

  2. Question 2 of 2 What does the C++ standard promise about argv[0]?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It represents the name used to start the program, or it is an empty string

    argv[0] is meant for messages such as a usage line. To find files that belong to a program, use a setting or a documented location, never argv[0].

The shell splits, expands and unquotes a line before main runs

Between the line you type and argv stands the shell. It splits the line into words, expands variables and wildcards and removes the quotes, so the program never sees much of what you typed:

Quotes, a wildcard and a variable Python · shell_session.py
from session import run

# The shell splits each line into words, expands variables and wildcards and removes the quotes before the program
# starts. Every line runs in a new sh, so a line that uses $name sets it first; the last two lines ask zsh instead.
run(r"""
clang++ -std=c++23 -Wall -Wextra show_args.cpp -o show_args
./show_args "two words" 'it'\''s' three\ four ""
touch june.csv july.csv
./show_args *.csv
./show_args *.txt
name="Asha Rao"; ./show_args $name "$name"
name="Asha Rao"; ./show_args "two words" *.csv $name
zsh -c 'name="Asha Rao"; ./show_args $name'
zsh -c './show_args *.txt'
""")

Output

$ clang++ -std=c++23 -Wall -Wextra show_args.cpp -o show_args
$ ./show_args "two words" 'it'\''s' three\ four ""
argc = 5
argv[0] = [./show_args]
argv[1] = [two words]
argv[2] = [it's]
argv[3] = [three four]
argv[4] = []
argv[argc] is a null pointer
$ touch june.csv july.csv
$ ./show_args *.csv
argc = 3
argv[0] = [./show_args]
argv[1] = [july.csv]
argv[2] = [june.csv]
argv[argc] is a null pointer
$ ./show_args *.txt
argc = 2
argv[0] = [./show_args]
argv[1] = [*.txt]
argv[argc] is a null pointer
$ name="Asha Rao"; ./show_args $name "$name"
argc = 4
argv[0] = [./show_args]
argv[1] = [Asha]
argv[2] = [Rao]
argv[3] = [Asha Rao]
argv[argc] is a null pointer
$ name="Asha Rao"; ./show_args "two words" *.csv $name
argc = 6
argv[0] = [./show_args]
argv[1] = [two words]
argv[2] = [july.csv]
argv[3] = [june.csv]
argv[4] = [Asha]
argv[5] = [Rao]
argv[argc] is a null pointer
$ zsh -c 'name="Asha Rao"; ./show_args $name'
argc = 2
argv[0] = [./show_args]
argv[1] = [Asha Rao]
argv[argc] is a null pointer
$ zsh -c './show_args *.txt'
zsh:1: no matches found: *.txt
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 shell_session.py

"two words" arrived as one argument with a space in it, three\ four too, and "" as an empty argument, which is different from no argument at all. *.csv never reached the program: the shell replaced it with the matching file names, sorted, while *.txt, which matched no file, was passed on unchanged. And $name without quotes became two arguments, Asha and Rao, while "$name" stayed one. Step through the last sh line, which does all of it at once:

Interactive · stage by stage One command line on its way from the shell to argv Step through ./show_args "two words" *.csv $name with name set to Asha Rao. The shell does every step before main runs.
One command line on its way from the shell to argv, step 8 of 8. Showing The line you type, Split into words (4 words, quotes kept for now), Expand the variable (now: Asha Rao), Split what it gave (zsh: Asha Rao stays one), Expand the wildcard (july.csv june.csv), Remove the quotes (two words, one argument), main receives argv. In focus: Split what it gave.The line you type"two words" *.csv $nameSplit into words4 words, quotes kept for nowExpand the variablenow: Asha RaoSplit what it gavezsh: Asha Rao stays oneExpand the wildcardjuly.csv june.csvRemove the quotestwo words, one argumentmain receives argvargc is 6

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. The line. name holds Asha Rao, and the folder holds june.csv and july.csv. The shell reads the line ./show_args "two words" *.csv $name before anything runs.
  2. Four words. The shell splits the line at the spaces that are outside quotes: ./show_args, "two words", *.csv and $name. The space inside the double quotes does not split.
  3. The variable. $name is replaced by its value, Asha Rao. Variables are expanded first, before any wildcard.
  4. Split again. The value of an unquoted variable is split at its spaces into fields: Asha and Rao become two words. Written as "$name", it would have stayed one.
  5. File names. .csv is replaced by the names that match it, sorted: july.csv and june.csv. In sh a pattern that matches no file is passed on as it is, as .txt was.
  6. No quotes left. Last, the quotes are removed. "two words" arrives as two words with a space in it; the program never sees a quote character.
  7. What main gets. The program starts with argc 6, and argv[0] to argv[5] are ./show_args, two words, july.csv, june.csv, Asha and Rao, as the session printed.
  8. zsh differs. zsh, the default shell on macOS, skips the split of an unquoted variable unless its SH_WORD_SPLIT option is set: the first zsh line of the session gave argc 2 and Asha Rao in one piece.

POSIX fixes this order for sh, and bash follows it: variables first, then the splitting of what they produced, then file names, and quote removal last. zsh, the default shell on macOS, differs in two details the last lines of the session show: it does not split an unquoted variable unless its SH_WORD_SPLIT option is set, and it refuses to run a command whose pattern matches no file. Quote variables in scripts, and every shell passes them on in one piece.

Windows works differently. There a program receives one command-line string, and the startup code of the Microsoft C runtime splits it into argv with its own rules for double quotes and backslashes; it does not expand * at all unless the program is linked with setargv.obj. A tool meant for both systems should not depend on wildcards having been expanded.

Key idea

Splitting, quoting and wildcards are the shell’s work, done before main runs: the program sees only the finished words, and the same line can give different words in another shell.

Checkpoint

Check: What the shell does to a command line

  1. Question 1 of 2 In sh, what is argc for ./show_args "a b" c\ d '' e ?

    Type a number.

    A hint

    Quotes and a backslash keep a space inside a word; an empty pair of quotes is still an argument.

    Show the answer: Question 1 of 2

    Answer: 5

    The words are ./show_args, a b, c d, an empty argument and e: five. The quotes and the backslash only protect the spaces and are removed before main runs, and '' passes an empty argument, which counts.

  2. Question 2 of 2 In a folder with no .txt file, what happens to ./show_args *.txt in sh, and in zsh?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: sh passes the text *.txt on unchanged; zsh refuses to run the command and reports no matches

    POSIX shells leave a pattern that matches nothing as it is, so a program may receive a literal *.txt. zsh's default is to report the failed match instead, one more reason not to rely on wildcards in scripts meant for every shell.

Copy the arguments into string views, then convert each number

argv is an array of char* from C. The convenient way to handle it in C++ is to copy the arguments once into a std::vector<std::string_view>, a growable list of string views (vectors get their own lesson in the next module). This program checks that every argument is a mark from 0 to 100:

check_marks.cpp C++ · check_marks.cpp
// check_marks.cpp: checks that every argument is a mark from 0 to 100.
// Exit status: 0 all marks valid, 1 at least one is not, 2 the command was used wrongly.
#include <charconv>
#include <cstdlib>
#include <optional>
#include <print>
#include <string_view>
#include <vector>

constexpr int exit_invalid_mark = 1;
constexpr int exit_usage = 2;

// The whole text as an int, or no value when it is not one (std::optional: a later lesson).
std::optional<int> to_int(std::string_view text) {
    int value = 0;
    auto [end, error] = std::from_chars(text.data(), text.data() + text.size(), value);
    if (error != std::errc{} || end != text.data() + text.size()) return std::nullopt;
    return value;
}

void print_usage(std::FILE* stream) {
    std::println(stream, "usage: check_marks MARK...   (each MARK a whole number from 0 to 100)");
}

int main(int argc, char* argv[]) {
    // the arguments after the program name
    std::vector<std::string_view> args(argv + 1, argv + argc);
    if (args.empty()) {
        print_usage(stderr);
        return exit_usage;
    }
    if (args[0] == "--help") {
        print_usage(stdout);
        return EXIT_SUCCESS;
    }
    int invalid = 0;
    for (std::string_view arg : args) {
        std::optional<int> mark = to_int(arg);
        if (!mark || *mark < 0 || *mark > 100) {
            std::println(stderr, "check_marks: '{}' is not a mark from 0 to 100", arg);
            ++invalid;
        }
    }
    if (invalid > 0) return exit_invalid_mark;
    std::println("all {} marks are valid", args.size());
    return EXIT_SUCCESS;
}

std::vector<std::string_view> args(argv + 1, argv + argc); makes one view for each argument after the program name. The strings that argv points to keep their values until the program ends, so these views never dangle, and they bring ==, starts_with and size() to plain C strings. Arguments are always text: "72" is two characters, not a number. to_int converts with std::from_chars from <charconv>, which reports an error code and where the number ended. This program shows what it reported for six arguments:

What from_chars reports, and what to_int accepts C++ · from_chars_demo.cpp
// from_chars_demo.cpp: what std::from_chars reports for six arguments, and what to_int makes of each.
#include <charconv>
#include <format>
#include <optional>
#include <print>
#include <string>
#include <string_view>
#include <system_error>

// The whole text as an int, or no value when it is not one (as in check_marks.cpp).
std::optional<int> to_int(std::string_view text) {
    int value = 0;
    auto [end, error] = std::from_chars(text.data(), text.data() + text.size(), value);
    if (error != std::errc{} || end != text.data() + text.size()) return std::nullopt;
    return value;
}

// What from_chars reported for the text, then what to_int returns.
void explain(std::string_view text) {
    int value = 0;
    auto [end, error] = std::from_chars(text.data(), text.data() + text.size(), value);
    std::optional<int> result = to_int(text);
    std::string verdict = result ? std::format("to_int gives {}", *result) : "to_int gives no value";
    if (error == std::errc::invalid_argument)
        std::println("[{}] invalid_argument: {}", text, verdict);
    else if (error == std::errc::result_out_of_range)
        std::println("[{}] result_out_of_range: {}", text, verdict);
    else
        std::println("[{}] value {}, read {} of {} characters: {}", text, value, end - text.data(), text.size(), verdict);
}

int main() {
    for (std::string_view text : {"72", "-4", "12abc", "abc", "", "99999999999"}) explain(text);
}

Output

[72] value 72, read 2 of 2 characters: to_int gives 72
[-4] value -4, read 2 of 2 characters: to_int gives -4
[12abc] value 12, read 2 of 5 characters: to_int gives no value
[abc] invalid_argument: to_int gives no value
[] invalid_argument: to_int gives no value
[99999999999] result_out_of_range: to_int gives no value

Recorded with Apple clang version 21.0.0 (clang-2100.3.34.2) on macOS 26 arm64. To run it yourself: clang++ -std=c++23 -O2 -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined from_chars_demo.cpp -o from_chars_demo && ./from_chars_demo

For 12abc, from_chars read the 12 and stopped at the a, which is why to_int also checks that the number used the whole text: half-read input is rejected, never taken as 12. An empty argument and abc gave invalid_argument, and 99,999,999,999, too large for an int, gave result_out_of_range. to_int returns a std::optional<int>, a value that holds either an int or nothing; a later module covers it. -4 is a number, so to_int accepts it, and check_marks rejects it afterwards with its range test.

Two more habits of check_marks are worth copying. Messages about problems go to standard streams: The three channels a console program starts with: standard input, which std::cin reads, and standard output and standard error, which std::cout and std::cerr write. The shell can connect each one to the terminal, a file or a pipe. More in “Console I/O in C++: cin, cout, print and println” with std::println(stderr, …), and only the real result goes to standard output, so check_marks 72 abc > results.txt still shows the complaint on the screen and keeps it out of the file. And the usage line goes to standard output when someone asks for it with --help, because then it is the requested output, and to standard error after a mistake.

Checkpoint

Check: Converting arguments to numbers

  1. Question 1 of 2 std::from_chars reads "12abc" into an int. What does it report, and what does to_int return?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The value 12, no error, and an end after 2 of the 5 characters, so to_int returns no value

    from_chars_demo.cpp printed [12abc] value 12, read 2 of 5 characters: to_int gives no value. Checking the end pointer is what turns from_chars into a whole-argument check.

  2. Question 2 of 2 You run ./check_marks 72 abc > results.txt. What is in results.txt, and what appears on the screen?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: results.txt stays empty, and the complaint about abc appears on the screen

    The session ran exactly this with > /dev/null: only the complaint about abc was left on the screen, and the exit status was 1. Keeping complaints on standard error keeps result files clean.

The value main returns is the exit status scripts read

A return statement in main leaves main and then calls std::exit with the returned value, and reaching the closing brace of main is the same as return 0;. The standard defines only three values: 0 and EXIT_SUCCESS mean success, and EXIT_FAILURE means failure (both macros are in <cstdlib>; on macOS, Linux and Windows they are 0 and 1). Every other value means whatever the program documents. The shell keeps the last exit status: The number a program hands back when it ends, the value main returns. 0 means success; a shell keeps the last status in $?, and && runs the next command only after a status of 0. More in “How C++ compiles: preprocess, compile and link” in $?, and &&, || and if choose what to run next from it:

save_marks.sh Shell · save_marks.sh
#!/bin/sh
# save_marks.sh: decides what to do from the exit status of check_marks.
if ./check_marks "$@"; then
    echo "save_marks: saved $# marks"
else
    status=$?
    echo "save_marks: check_marks failed with status $status, nothing saved"
    exit "$status"
fi
Exit statuses from a shell Python · check_session.py
from session import run

run(r"""
clang++ -std=c++23 -Wall -Wextra check_marks.cpp -o check_marks
./check_marks 72 91 58; echo "exit status: $?"
./check_marks 72 abc 101 -4; echo "exit status: $?"
./check_marks; echo "exit status: $?"
./check_marks --help; echo "exit status: $?"
./check_marks 72 abc > /dev/null; echo "only standard error was left on the screen, exit status: $?"
./check_marks 72 91 && echo "the && part runs after a success"
./check_marks 72 910 || echo "the || part runs after a failure"
sh save_marks.sh 64 77
sh save_marks.sh 64 seventy
""")

Output

$ clang++ -std=c++23 -Wall -Wextra check_marks.cpp -o check_marks
$ ./check_marks 72 91 58; echo "exit status: $?"
all 3 marks are valid
exit status: 0
$ ./check_marks 72 abc 101 -4; echo "exit status: $?"
check_marks: 'abc' is not a mark from 0 to 100
check_marks: '101' is not a mark from 0 to 100
check_marks: '-4' is not a mark from 0 to 100
exit status: 1
$ ./check_marks; echo "exit status: $?"
usage: check_marks MARK...   (each MARK a whole number from 0 to 100)
exit status: 2
$ ./check_marks --help; echo "exit status: $?"
usage: check_marks MARK...   (each MARK a whole number from 0 to 100)
exit status: 0
$ ./check_marks 72 abc > /dev/null; echo "only standard error was left on the screen, exit status: $?"
check_marks: 'abc' is not a mark from 0 to 100
only standard error was left on the screen, exit status: 1
$ ./check_marks 72 91 && echo "the && part runs after a success"
all 2 marks are valid
the && part runs after a success
$ ./check_marks 72 910 || echo "the || part runs after a failure"
check_marks: '910' is not a mark from 0 to 100
the || part runs after a failure
$ sh save_marks.sh 64 77
all 2 marks are valid
save_marks: saved 2 marks
$ sh save_marks.sh 64 seventy
check_marks: 'seventy' is not a mark from 0 to 100
save_marks: check_marks failed with status 1, nothing saved
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 check_session.py

check_marks documents three exit statuses: 0 when every mark is valid, 1 when at least one is not, and 2 when it was used wrongly. save_marks.sh never reads what check_marks prints, only its status. Step through its last run:

Interactive · message by message A script that decides by the exit status alone Step through sh save_marks.sh 64 seventy. Watch what travels back from check_marks to the script.
A script that decides by the exit status alone, step 6 of 6. Messages so far: save_marks.sh to check_marks: ./check_marks 64 seventy; check_marks to save_marks.sh: exit status 1.save_marks.shstatus 0: saved 2 markscheck_marksfinished: status 1./check_marks 64 seventyexit status 1

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. The script calls the tool. The if line runs ./check_marks "$@". The quoted "$@" passes on the script's own arguments unchanged: 64 and seventy.
  2. One bad argument. 64 is a whole number from 0 to 100. seventy is not a number at all, so check_marks prints its complaint on standard error and counts one invalid mark.
  3. Status 1 comes back. check_marks returns exit_invalid_mark, 1, from main. The script receives that number and nothing else: it never reads the output of check_marks.
  4. The else branch. if counts any status other than 0 as false, so the else branch runs. status=$? keeps the 1, and the script prints that nothing was saved.
  5. Passing it on. exit "$status" ends the script with the same status, so whatever ran the script learns of the failure too: the session shows [exit status 1].
  6. The good run. With 64 77, check_marks returned 0, the if branch ran and the script printed save_marks: saved 2 marks.

Build tools, CI jobs and test runners use your program in exactly this way. Some values mean the same thing to many programs:

Status What it usually means
0 Success: EXIT_SUCCESS
1 A general failure: EXIT_FAILURE here
2 Used wrongly, as Bash says of its builtins
126 and 127 The shell’s own: not executable, not found
128 + n Stopped by signal n, in bash and zsh

A status other than 0 is not always an error, though: GNU grep exits with 1 when no line matched and with 2 when something went wrong. BSD systems also define named codes from 64 up in <sysexits.h> (EX_USAGE is 64), but FreeBSD’s own manual calls that interface deprecated and discourages its use. So pick small values from 0 to 125, give a failure its own value only when a caller can do something different with it, and list the values in the program’s help.

Checkpoint

Check: Exit statuses that scripts read

  1. Question 1 of 2 What does ./check_marks 72 abc && echo saved print?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Only the complaint about abc; echo does not run

    && and || read the exit status only. The session showed ./check_marks 72 910 || echo … running the echo after a failure, the mirror image of this line.

  2. Question 2 of 2 GNU grep exits with status 1. What does that mean?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: No line matched; the search itself worked

    A status other than 0 means only what the program documents. A script that treats every status but 0 as a crash would misread grep, which is why tools should document their values.

Only the lowest 8 bits of the status reach the shell

main returns an int, but a parent process on macOS or Linux receives less. This program returns whatever number it is given:

return_code.cpp C++ · return_code.cpp
// return_code.cpp: returns the number it is given from main; what does the shell see?
#include <charconv>
#include <string_view>

int main(int argc, char* argv[]) {
    if (argc != 2) return 2;
    std::string_view text = argv[1];
    int code = 0;
    std::from_chars(text.data(), text.data() + text.size(), code);
    return code;
}
What the shell saw Python · return_code_session.py
from session import run

# The shell keeps only the lowest 8 bits of the value: what main returned, modulo 256.
run(r"""
clang++ -std=c++23 -Wall -Wextra return_code.cpp -o return_code
for code in 0 1 42 255 256 257 300 1000 -1; do ./return_code $code; echo "main returned $code, the shell saw $?"; done
""")

Output

$ clang++ -std=c++23 -Wall -Wextra return_code.cpp -o return_code
$ for code in 0 1 42 255 256 257 300 1000 -1; do ./return_code $code; echo "main returned $code, the shell saw $?"; done
main returned 0, the shell saw 0
main returned 1, the shell saw 1
main returned 42, the shell saw 42
main returned 255, the shell saw 255
main returned 256, the shell saw 0
main returned 257, the shell saw 1
main returned 300, the shell saw 44
main returned 1000, the shell saw 232
main returned -1, the shell saw 255

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 return_code_session.py

POSIX lets a parent read only the lowest 8 bits of the status through wait() and waitpid(), the calls a shell uses: the value modulo 256, the remainder after dividing by 256. A program that returns 256 to say “256 errors” therefore reports success, 300 arrives as 44, 1000 = 3 × 256 + 232 as 232, and -1 as 255. Windows keeps the whole 32-bit value, which GetExitCodeProcess reports, so the same program would show 256 there.

Values from 126 up have a second problem even when they fit: they collide with what the shell itself reports for a command that cannot run and for a program stopped by a signal. Keep exit codes between 0 and 125, and neither question comes up.

Programmer Calculator (Bitwise) Work out what the lowest 8 bits of a returned value are: 1000 is 0x3E8, so the shell sees 0xE8, which is 232.

Checkpoint

Check: The 8 bits of an exit status

  1. Question 1 of 2 main returns 600. Which exit status does a shell on macOS or Linux see?

    Type a number.

    A hint

    Only the lowest 8 bits survive, the value modulo 256.

    Show the answer: Question 1 of 2

    Answer: 88

    600 = 2 × 256 + 88, so the lowest 8 bits are 88. return_code.cpp showed the same rule: 300 arrived as 44 and 1000 as 232.

  2. Question 2 of 2 Which of these values returned from main reach a POSIX shell unchanged?

    Choose every answer that is right.

    Show the answer: Question 2 of 2

    Answer:

    • 255
    • 125

    Values from 0 to 255 survive; anything else arrives modulo 256. 126 to 255 also collide with the shell's own meanings, so tools keep to 0 to 125.

Returning from main destroys local objects; std::exit skips them

std::exit(status) from <cstdlib> ends the program from anywhere, even deep inside other functions. It does less clean-up than a return from main. In this program a Notice prints a line when it is destroyed:

exit_or_return.cpp C++ · exit_or_return.cpp
// exit_or_return.cpp: what is cleaned up when main returns, and what is not when
// std::exit ends the program.
#include <cstdlib>
#include <print>
#include <string_view>

// A Notice prints a line when it is destroyed, so the session shows which clean-up ran.
struct Notice {
    std::string_view name;
    ~Notice() { std::println("  {} cleaned up", name); }
};

Notice log_file{"global log file"};  // lives for the whole program

int finish(bool use_exit) {
    Notice temp_file{"local temp file"};  // lives until finish returns
    if (use_exit) std::exit(EXIT_FAILURE);
    return EXIT_FAILURE;
}

int main(int argc, char* argv[]) {
    bool use_exit = argc > 1 && std::string_view{argv[1]} == "--exit";
    std::println("ending with {}:", use_exit ? "std::exit" : "return");
    return finish(use_exit);
}
What each way of ending cleans up Python · exit_or_return_session.py
from session import run

run(r"""
clang++ -std=c++23 -Wall -Wextra exit_or_return.cpp -o exit_or_return
./exit_or_return
./exit_or_return --exit
g++-16 -std=c++23 -Wall -Wextra exit_or_return.cpp -o exit_or_return
./exit_or_return
./exit_or_return --exit
""")

Output

$ clang++ -std=c++23 -Wall -Wextra exit_or_return.cpp -o exit_or_return
$ ./exit_or_return
ending with return:
  local temp file cleaned up
  global log file cleaned up
[exit status 1]
$ ./exit_or_return --exit
ending with std::exit:
  global log file cleaned up
[exit status 1]
$ g++-16 -std=c++23 -Wall -Wextra exit_or_return.cpp -o exit_or_return
$ ./exit_or_return
ending with return:
  local temp file cleaned up
  global log file cleaned up
[exit status 1]
$ ./exit_or_return --exit
ending with std::exit:
  global log file cleaned up
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 exit_or_return_session.py

Step through the two runs and watch which objects are destroyed:

Interactive · step by step What is cleaned up after return, and after std::exit Step through the two runs of exit_or_return.cpp. Each Notice prints a line when it is destroyed.
What is cleaned up after return, and after std::exit, step 6 of 6. Showing main, finish (temp_file never destroyed), std::exit(1), Global objects (log_file destroyed), The shell. In focus: The shell.--exitmaincalls finish(use_exit)finishtemp_file never destroyedstd::exit(1)called inside finishGlobal objectslog_file destroyedThe shellsees status 1

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. A local object. Both runs print the line ending with …, then main calls finish, which creates the local object temp_file. It lives until finish is left.
  2. finish returns. Without --exit, finish returns EXIT_FAILURE. Leaving finish destroys temp_file, which prints local temp file cleaned up.
  3. main returns. main returns that 1. Returning from main leaves main and then calls std::exit with the value, which destroys the global log_file: global log file cleaned up.
  4. The other run. With --exit, finish calls std::exit(EXIT_FAILURE) while temp_file still exists. The program ends without leaving finish, and no local object is destroyed.
  5. Globals still go. std::exit still destroys the global log_file and flushes standard output, so the run printed only global log file cleaned up.
  6. The same status. Both ways the shell saw status 1, and the Apple clang and GCC builds printed the same lines. Only the clean-up differed.

When finish returns and main returns, the local object temp_file is destroyed on the way out, and then the global log_file. With std::exit, the global object is still destroyed, but temp_file is not: the standard says that ending a program without leaving the current block destroys no local objects. Both compilers behaved the same.

Here a skipped clean-up only means a missing line, but in real programs local objects close files, finish writes and remove temporary files. So return an error from function to function until main can return it; the error-handling module shows exceptions, which do the same job without the manual passing on. std::exit still flushes standard output, so text printed before it is not lost.

Checkpoint

Check: What std::exit cleans up

  1. Question 1 of 2 A function holds a local object whose destructor writes its last buffered lines to a file, then calls std::exit(1). What happens to those lines?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: They are lost: std::exit does not destroy the local object, so its destructor never runs

    exit_or_return.cpp printed local temp file cleaned up after a return and nothing for it after std::exit. Return the error to main instead, and every local object on the way is destroyed.

  2. Question 2 of 2 std::println prints a line to standard output, and the next statement is std::exit(EXIT_FAILURE). Is the line lost?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: No: std::exit flushes standard output before the program ends

    std::exit destroys static objects and flushes and closes the C streams. What it skips are the local objects of the functions still running.

std::getenv returns a null pointer for a variable that is not set

environment variable: A name and value pair, such as HOME or PATH, that a program inherits from the process that started it. std::getenv reads one by name and returns a null pointer when it is not set. are name and value pairs that a program inherits from whoever started it: HOME, PATH, or settings such as a folder for reports. std::getenv("REPORT_DIR") gives back a pointer to the variable’s text, and a null pointer if no such variable exists. Code that forgets the second case crashes, and the two standard libraries crash differently:

Using getenv without and with a check

C++ (getenv_wrong.cpp) · getenv_wrong.cpp

// getenv_wrong.cpp: assumes that REPORT_DIR is always set. It is not.
#include <cstdlib>
#include <print>
#include <string>

int main() {
    // a null pointer when the variable is not set
    std::string dir = std::getenv("REPORT_DIR");
    std::println("reports go to {}", dir);
}

C++ (getenv_right.cpp) · getenv_right.cpp

// getenv_right.cpp: checks the pointer before using it, and falls back to a default.
#include <cstdlib>
#include <print>
#include <string_view>

std::string_view report_dir() {
    const char* value = std::getenv("REPORT_DIR");
    // not set, or set to an empty string
    if (value == nullptr || *value == '\0') return ".";
    return value;
}

int main() {
    std::println("reports go to {}", report_dir());
}
With the variable set, empty and missing Python · getenv_session.py
from session import run

# env -u removes a variable for one command; NAME=value before a command sets it for that command only.
run(r"""
clang++ -std=c++23 -Wall -Wextra getenv_wrong.cpp -o wrong_clang
g++-16 -std=c++23 -Wall -Wextra getenv_wrong.cpp -o wrong_gcc
REPORT_DIR=/srv/reports ./wrong_clang
env -u REPORT_DIR ./wrong_clang
env -u REPORT_DIR ./wrong_gcc
clang++ -std=c++23 -Wall -Wextra getenv_right.cpp -o right
env -u REPORT_DIR ./right
REPORT_DIR= ./right
REPORT_DIR=/srv/reports ./right
""")

Output

$ clang++ -std=c++23 -Wall -Wextra getenv_wrong.cpp -o wrong_clang
$ g++-16 -std=c++23 -Wall -Wextra getenv_wrong.cpp -o wrong_gcc
$ REPORT_DIR=/srv/reports ./wrong_clang
reports go to /srv/reports
$ env -u REPORT_DIR ./wrong_clang
[stopped by signal 11 (SIGSEGV): a shell reports exit status 139]
$ env -u REPORT_DIR ./wrong_gcc
terminate called after throwing an instance of 'std::logic_error'
  what():  basic_string: construction from null is not valid
[stopped by signal 6 (SIGABRT): a shell reports exit status 134]
$ clang++ -std=c++23 -Wall -Wextra getenv_right.cpp -o right
$ env -u REPORT_DIR ./right
reports go to .
$ REPORT_DIR= ./right
reports go to .
$ REPORT_DIR=/srv/reports ./right
reports go to /srv/reports

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 getenv_session.py

REPORT_DIR getenv_wrong.cpp getenv_right.cpp
/srv/reports reports go to /srv/reports reports go to /srv/reports
Not set SIGSEGV with libc++, SIGABRT with libstdc++ reports go to .

While REPORT_DIR is set, the wrong version works, which is how this bug survives testing. Without it, std::string dir = std::getenv("REPORT_DIR"); builds a string from a null pointer, which is undefined behaviour: the Apple clang build with libc++ stopped with a segmentation fault (exit status 139), and the GCC build with libstdc++ noticed the null pointer, threw std::logic_error and stopped with SIGABRT (134). The right version checks the pointer, treats an empty value like a missing one, and falls back to a default, as its run with REPORT_DIR= shows.

Building a std::string straight from std::getenv

std::string dir = std::getenv("REPORT_DIR"); crashes whenever the variable is missing. Keep the pointer in a const char*, test it against nullptr (and for an empty value), and only then use it.

report_dir returns a std::string_view, which suits a value that is read and used at once; to keep a value for later, copy it into a std::string, because a later change to the environment can make the pointer invalid. Two more rules come from the standards: never modify the characters that getenv returns, and do not change the environment (with the POSIX function setenv) while other threads may call getenv, because the C++ standard promises no data race only as long as nothing modifies the environment. Reading the settings once at start-up, in main, avoids both problems.

Checkpoint

Check: Reading environment variables

  1. Question 1 of 2 REPORT_DIR is not set. What does std::string dir = std::getenv("REPORT_DIR"); do?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: It builds a string from a null pointer, which is undefined behaviour: libc++ crashed with SIGSEGV, libstdc++ threw std::logic_error

    The bug hides while the variable is set, which is how it survives testing. Check the pointer first, as report_dir in getenv_right.cpp does.

  2. Question 2 of 2 A long-running program needs the value of REPORT_DIR throughout. What should it keep?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: A std::string copied from the value once at start-up, after checking the pointer

    A view or a pointer suits a value that is read and used at once, as report_dir does. A value kept for the whole run is copied into a std::string, best in main, before other threads start.

Practise: a divide command

Write divide DIVIDEND DIVISOR, a command that prints the quotient of two whole numbers with two decimals, so ./divide 7 2 prints 3.50. It must report every problem on standard error, print nothing on standard output, and exit with its own status:

  • 0: success, the quotient printed.
  • 2: not exactly two arguments.
  • 3: an argument that is not a whole number that fits in an int, such as seven, 2x, an empty argument or 99999999999.
  • 4: a divisor of 0.
The exercise files

divide.cpp (starter) · exercise/divide.cpp

// divide.cpp: the starter. It divides correctly when given two whole numbers and a
// divisor that is not 0, and checks nothing else yet: add the checks and exit statuses.
#include <charconv>
#include <optional>
#include <print>
#include <string_view>
#include <vector>

// The whole text as an int, or no value when it is not one (as in check_marks.cpp).
std::optional<int> to_int(std::string_view text) {
    int value = 0;
    auto [end, error] = std::from_chars(text.data(), text.data() + text.size(), value);
    if (error != std::errc{} || end != text.data() + text.size()) return std::nullopt;
    return value;
}

int main(int argc, char* argv[]) {
    std::vector<std::string_view> args(argv + 1, argv + argc);
    int dividend = args.size() > 0 ? to_int(args[0]).value_or(0) : 0;
    int divisor = args.size() > 1 ? to_int(args[1]).value_or(0) : 0;
    std::println("{:.2f}", static_cast<double>(dividend) / divisor);
}

exercise/divide_tests.sh

#!/bin/sh
# divide_tests.sh: the public tests of the divide exercise. Run it with sh in the folder that holds ./divide.
# Each test gives the exit status, the standard output and whether a message on standard error is expected.
passed=0
failed=0

expect() {
    want_status=$1
    want_out=$2
    want_message=$3
    shift 3
    out=$(./divide "$@" 2> errors.txt)
    status=$?
    message=$(cat errors.txt)
    ok=yes
    [ "$status" = "$want_status" ] || ok=no
    [ "$out" = "$want_out" ] || ok=no
    if [ "$want_message" = message ] && [ -z "$message" ]; then ok=no; fi
    if [ "$want_message" = silent ] && [ -n "$message" ]; then ok=no; fi
    if [ "$ok" = yes ]; then
        passed=$((passed + 1))
    else
        failed=$((failed + 1))
        echo "FAIL ./divide $*: wanted status $want_status and '$want_out', got status $status and '$out'"
    fi
}

expect 0 "3.50" silent 7 2
expect 0 "-2.50" silent -5 2
expect 0 "0.00" silent 0 9
expect 2 "" message
expect 2 "" message 7
expect 2 "" message 7 2 1
expect 3 "" message seven 2
expect 3 "" message 7 2x
expect 3 "" message 7 ""
expect 3 "" message 99999999999 1
expect 4 "" message 7 0
echo "$passed passed, $failed failed"
[ "$failed" -eq 0 ]

Save both files in one folder, build divide from your divide.cpp, and run sh divide_tests.sh; it ends with 11 passed, 0 failed and exit status 0 when your program is right. The starter divides correctly but checks nothing, so it fails 8 of the 11 tests:

The tests with the starter file Python · divide_session.py
from session import run

# The public tests, run against the starter file.
run(r"""
cd exercise && clang++ -std=c++23 -Wall -Wextra divide.cpp -o divide && sh divide_tests.sh
""")

Output

$ cd exercise && clang++ -std=c++23 -Wall -Wextra divide.cpp -o divide && sh divide_tests.sh
FAIL ./divide : wanted status 2 and '', got status 0 and 'nan'
FAIL ./divide 7: wanted status 2 and '', got status 0 and 'inf'
FAIL ./divide 7 2 1: wanted status 2 and '', got status 0 and '3.50'
FAIL ./divide seven 2: wanted status 3 and '', got status 0 and '0.00'
FAIL ./divide 7 2x: wanted status 3 and '', got status 0 and 'inf'
FAIL ./divide 7 : wanted status 3 and '', got status 0 and 'inf'
FAIL ./divide 99999999999 1: wanted status 3 and '', got status 0 and '0.00'
FAIL ./divide 7 0: wanted status 4 and '', got status 0 and 'inf'
3 passed, 8 failed
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 divide_session.py

A first hint: check the number of arguments first, before you look at args[0] or args[1], then convert both with to_int, and test the divisor last. Give the exit statuses names, such as constexpr int exit_usage = 2;, as check_marks.cpp does.

When the tests pass, see how well the seven ideas of this lesson have stuck. The questions begin where your answers have been weakest, and every choice comes with its reason:

Practice that adapts to you

Practise this lesson

14 questions about 7 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 14 What is argv[argc]?

    Choose one answer.

    Show the answer: Question 1 of 14

    Answer: A null pointer, which marks the end of the array

    Because the last entry is a null pointer, a loop can walk argv until it meets nullptr instead of counting with argc.

  2. Question 2 of 14 This program is started as ./tool alpha beta. What does it print?

    Read the code, then choose one answer.

    #include <print>
    
    int main(int argc, char* argv[]) {
        for (int i = 1; i < argc; ++i)
            std::println("{}: {}", i, argv[i]);
    }
    Show the answer: Question 2 of 14

    Answer: 1: alpha and 2: beta, on two lines

    Starting at 1 and stopping before argc visits exactly the arguments. check_marks does the same with args(argv + 1, argv + argc).

  3. Question 3 of 14 On macOS, who turns *.csv into july.csv june.csv?

    Choose one answer.

    Show the answer: Question 3 of 14

    Answer: The shell, before the program starts

    Wildcards, variables and quotes are handled by the shell. That is why the same program behaves differently from sh, zsh and the Windows command prompt.

  4. Question 4 of 14 In sh, name="Asha Rao" is set. What is argc for ./show_args "$name" $name ?

    Type a number.

    Show the answer: Question 4 of 14

    Answer: 4

    "$name" stays one argument, Asha Rao, and the unquoted $name is split into Asha and Rao: with the program name, four entries, as the session printed for the same two words in the other order.

  5. Question 5 of 14 What does std::from_chars give back besides the number it stores?

    Choose one answer.

    Show the answer: Question 5 of 14

    Answer: A pointer to where it stopped reading and an error code

    Both parts matter: the error code says whether a number was read and whether it fitted, and the pointer says whether the whole argument was used.

  6. Question 6 of 14 A server takes its port as an argument, and 8080x must be rejected rather than read as 8080. Which check of to_int does that?

    Choose one answer.

    Show the answer: Question 6 of 14

    Answer: end != text.data() + text.size(), the test that the number used the whole text

    A port would also need a range test, 1 to 65535, after the conversion, in the way check_marks tests 0 to 100.

  7. Question 7 of 14 What exit status does a program report when main reaches its closing brace without a return?

    Choose one answer.

    Show the answer: Question 7 of 14

    Answer: 0, success, because reaching the end of main is the same as return 0;

    The standard makes the end of main equal to return 0;. Failures must be reported with an explicit return of a value other than 0.

  8. Question 8 of 14 A backup tool fails when the source file is missing and when the network is down. Its caller retries only network failures. Which statuses fit?

    Choose one answer.

    Show the answer: Question 8 of 14

    Answer: Two different small documented values, such as 3 for a missing file and 4 for the network, and 0 for success

    Give a failure its own value only when a caller can do something different with it, keep the values small, and list them in the help, as check_marks documents 0, 1 and 2.

  9. Question 9 of 14 main returns -2. Which exit status does a shell on macOS see?

    Type a number.

    A hint

    -1 arrived as 255.

    Show the answer: Question 9 of 14

    Answer: 254

    The lowest 8 bits of -2 in two's complement are 11111110, which is 254, one below what -1 gave.

  10. Question 10 of 14 return_code.cpp is built for Windows and returns 256. What does GetExitCodeProcess report to the parent?

    Choose one answer.

    Show the answer: Question 10 of 14

    Answer: 256, because Windows keeps the whole 32-bit value

    The same return value means success on macOS and failure on Windows, which is one more reason to keep exit statuses from 0 to 125.

  11. Question 11 of 14 In exit_or_return.cpp, which objects were destroyed in the run with --exit?

    Choose one answer.

    Show the answer: Question 11 of 14

    Answer: Only the global log_file

    The session printed only global log file cleaned up after std::exit, with both compilers.

  12. Question 12 of 14 A function three calls deep finds that a file it needs is missing. How should the program end so that every local object on the way is cleaned up?

    Choose one answer.

    Show the answer: Question 12 of 14

    Answer: Return an error from function to function until main returns a status

    Returning the error is the manual way; exceptions, in the error-handling module, do the same passing on automatically and destroy every local object they leave behind.

  13. Question 13 of 14 The program is started as REPORT_DIR= ./right. What does std::getenv("REPORT_DIR") return?

    Choose one answer.

    Show the answer: Question 13 of 14

    Answer: A pointer to an empty string, which report_dir treats like a missing value

    Empty and missing are different to getenv but usually mean the same to a program, so getenv_right.cpp handles both with one test.

  14. Question 14 of 14 May a program change the characters that std::getenv returns, for example to make them upper case?

    Choose one answer.

    Show the answer: Question 14 of 14

    Answer: No; copy the value into a std::string and change the copy

    Treat the result of getenv as read-only and short-lived: read it, check it, and copy it if it must be kept or changed.

Interview questions

Fresher · What do argc and argv hold, and what is argv[argc]?

argc is the number of entries in argv, counting the program name, so a program run with two arguments sees argc == 3. argv is an array of pointers to C strings: argv[0] represents the name used to start the program (or is an empty string), argv[1] to argv[argc - 1] are the arguments, already split and unquoted by the shell, and argv[argc] is guaranteed to be a null pointer, so code can also walk the array until it meets the null pointer. The strings stay valid for the whole run, and they are always text: a number has to be converted, for example with std::from_chars.

Interviewers tend to follow this one with questions about quoting and about Windows. Answer both, then read why each choice holds or fails:

Checkpoint

Interview round: two follow-up questions

  1. Question 1 of 2 A follow-up: a wrapper script calls ./check_marks $* instead of ./check_marks "$@". What changes for a caller who passes the single argument "75 80"?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: With $* unquoted, the shell splits it again, so check_marks receives 75 and 80 as two arguments; "$@" keeps each argument whole

    Splitting happens to every unquoted expansion. A script that passes its arguments on writes "$@", as save_marks.sh does, so a caller's quoting survives.

  2. Question 2 of 2 A second follow-up: the same program, built for Windows and run as show_args .csv in the command prompt, prints argv[1] = [.csv]. Why?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: On Windows the program splits its own command line, and the C runtime expands wildcards only when the program is linked with setargv.obj

    Expansion is not part of C++: it is done by the shell on POSIX systems and by optional startup code on Windows. A portable tool accepts file names, and expands a pattern itself only if it must.

Key takeaways

  • int main(int argc, char* argv[]): argc counts the program name and the arguments, argv[0] is the name used to start the program, and argv[argc] is a null pointer.
  • The shell splits words, expands variables and * and removes quotes before main runs; Windows splits the command line in the program’s startup code instead and expands no wildcards by default.
  • Copy the arguments into a std::vector<std::string_view> once, convert numbers with std::from_chars and check that the whole text was used, and send complaints to standard error.
  • The value main returns is the exit status: 0 for success and small documented values for each failure. POSIX systems keep only the lowest 8 bits, so 256 reads as 0.
  • Returning from main destroys local objects; std::exit skips them, though it still destroys globals and flushes the output.
  • std::getenv returns a null pointer for a variable that is not set: check it before using the value.

Terms in this lesson

command-line argument
A word given to a program when it is started, such as report.txt in ./show_args report.txt. main receives the words, already split, expanded and unquoted by the shell, through argc and argv.
environment variable
A name and value pair, such as HOME or PATH, that a program inherits from the process that started it. std::getenv reads one by name and returns a null pointer when it is not set.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.