Arguments and environment

main can take the arguments the shell passed: argc counts them and argv holds them as C strings. The first is the program's own name, so the user's arguments start at index 1 and the array is valid up to argc - 1.

Lesson 010 printed argc and argv. This lesson turns them into a program: parse a number, accept --name=value options and read the environment, with a check for every argument that is not there.

The arguments arrive as text, so every one of them needs a decision: a default, a conversion or an error message. A program that prints its usage when the arguments do not fit is easier to use than one that silently picks a value.

A program that takes a file name should say which file it could not open, and one that takes a number should print the usage line when the text is not a number. The two messages are the difference between a tool and a puzzle.

argc and argv

The loop prints every argument with its index. The number is the count, argv[0] is the program as it was typed, and argv[argc] is a null pointer that marks the end of the array.

The program is run with two arguments, so argc is 3. The output shows the program name as it was invoked and then the two words.

The strings are owned by the runtime for the whole program. They must not be freed, and they can be copied into std::string when the program needs to keep one.

#include <iostream> int main(int argc, char** argv) { std::cout << "count " << argc << '\n'; for (int i = 0; i < argc; ++i) { std::cout << i << ": " << argv[i] << '\n'; } }

g++ -std=c++23 -Wall -Wextra argv.cpp -o args
./args one two

count 3
0: ./args
1: one
2: two

Parsing a number

std::from_chars converts text to a number without allocating and without a locale. It returns a pointer to the first character it did not use and an error code, so the caller can check both.

The program requires one argument, parses it and prints whether the conversion succeeded and the value. The first line of main is the usage message for the case with no argument.

from_chars is the tool for arguments because it does not throw. Lesson 070 used std::stoi with a position check for the same job on file text.

#include <charconv> #include <iostream> #include <string_view> int main(int argc, char** argv) { if (argc < 2) { std::cout << "usage: parse NUMBER\n"; return 1; } int value = 0; std::string_view text{argv[1]}; auto [end, error] = std::from_chars(text.data(), text.data() + text.size(), value); std::cout << (error == std::errc{}) << ' ' << value << ' ' << (end - text.data()) << '\n'; }

g++ -std=c++23 -Wall -Wextra parse.cpp -o parse
./parse 42
./parse 4x2

1 42 2
1 4 1

A small option parser

Options of the form --name=value are a prefix and a value. The program uses rfind(prefix, 0) to test the prefix at the start of the argument and substr to take the rest.

The program accepts --name= and --count= and keeps defaults for the ones it does not see. The run passes both, and the output shows the values that were parsed.

A parser with many options should report the unknown ones and a usage line. The shape is the same: loop over argv, compare prefixes, convert, and remember what was set.

#include <iostream> #include <string> #include <string_view> int main(int argc, char** argv) { std::string name = "anonymous"; int count = 1; for (int i = 1; i < argc; ++i) { std::string_view argument{argv[i]}; if (argument.rfind("--name=", 0) == 0) { name = std::string{argument.substr(7)}; } else if (argument.rfind("--count=", 0) == 0) { count = std::stoi(std::string{argument.substr(8)}); } } std::cout << name << ' ' << count << '\n'; }

g++ -std=c++23 -Wall -Wextra options.cpp -o options
./options --name=ada --count=3

ada 3

The environment

std::getenv reads an environment variable and returns a null pointer when it is not set. The program checks two names and prints whether each one is present, without printing their values.

The first variable is set in the container that built the page, and the second is invented for the test, so the output is 1 and 1. A program must always test the pointer before using it.

The returned pointer belongs to the environment and must not be freed or modified. Copy it into a std::string when the program needs to keep it.

#include <cstdlib> #include <iostream> int main() { const char* home = std::getenv("HOME"); const char* missing = std::getenv("NO_SUCH_VARIABLE_FOR_THE_COURSE"); std::cout << (home != nullptr) << ' ' << (missing == nullptr) << '\n'; }

g++ -std=c++23 -Wall -Wextra env.cpp -o env
./env

1 1

The argument that is not there

argv has argc entries, and reading argv[1] when argc is 1 reads past the array. The program below checks argc first, which is the only safe way to use an argument.

The page runs it without arguments, so argc is 1 and the program prints its usage line instead of dereferencing a pointer that does not exist. Run with an argument, the same code prints it.

A program that reads argv[1] unconditionally crashes on the first user who forgets the argument. The check costs one comparison and gives the user a message instead of a segmentation fault.

#include <iostream> int main(int argc, char** argv) { std::cout << "argc " << argc << '\n'; if (argc > 1) { std::cout << argv[1] << '\n'; } else { std::cout << "no argument given\n"; } }

g++ -std=c++23 -Wall -Wextra guard.cpp -o guard
./guard

argc 1
no argument given

Exercises

  1. Add a --verbose flag that takes no value and prints the parsed values when it is set.
  2. Parse two numbers from the command line and print their sum, with a usage line when either is missing.
  3. Read the PATH variable and print how many entries it has, split on the colon.

Questions

A new set of questions in a new order every time. Choose one answer for each, then check.

  1. What is argv[0]?

    Answer

    The program's own name

    The user's words start at argv[1], and argc counts the program name too.

  2. What type are the elements of argv?

    Answer

    C strings (char*)

    The array comes from the operating system as pointers to null-terminated text.

  3. What does std::stoi do with 4x2?

    Answer

    It returns 4 and reports that two characters were used

    The parser stops at the first non-digit, and the used count tells how far it got.

  4. What does getenv return for a variable that is not set?

    Answer

    nullptr

    The caller tests the pointer before reading the value.

  5. This program is run as ./args one two. What does it print? #include <iostream> int main(int argc, char** argv) { std::cout << "count " << argc << '\n'; for (int i = 0; i < argc; ++i) std::cout << i << ": " << argv[i] << '\n'; }

    Answer

    count 3, then the program name and the two words

    argc includes the program name, and argv holds it at index zero.

  6. What does this program print when run as ./parse 42? #include <iostream> #include <string> int main(int argc, char** argv) { std::size_t used = 0; std::string text = argv[1]; int value = std::stoi(text, &used); std::cout << (argc > 1) << ' ' << value << ' ' << used; }

    Answer

    1 42 2

    The argument is present, the number is 42, and both characters were consumed.

  7. What does this program print when run as ./parse 4x2? #include <iostream> #include <string> int main(int argc, char** argv) { std::size_t used = 0; std::string text = argv[1]; int value = std::stoi(text, &used); std::cout << (used == text.size()) << ' ' << value << ' ' << used; }

    Answer

    0 4 1

    stoi stops at the x, so only one character was used and the whole field was not consumed.

  8. What does this program print when run as ./options --name=ada --count=3? #include <iostream> #include <string> int main(int argc, char** argv) { std::string name; int count = 0; for (int i = 1; i < argc; ++i) { std::string arg = argv[i]; if (arg.rfind("--name=", 0) == 0) name = arg.substr(7); if (arg.rfind("--count=", 0) == 0) count = std::stoi(arg.substr(8)); } std::cout << name << ' ' << count; }

    Answer

    ada 3

    Each argument is matched by its prefix, and the values after the equals sign are stored.

  9. What does the compiler report for this line? #include <string> int main(int argc, char** argv) { int value = std::stoi(argv[1], 1); return value + argc; }

    Answer

    invalid conversion from 'int' to 'std::size_t*'

    The second parameter is a size_t pointer, and a null pointer literal has no type for the overload.

  10. Why test the argument count before reading an argument?

    Answer

    argv has no element for a missing argument

    Reading past argc - 1 is undefined, so the count has to be checked first.

The code on this page was built with g++ 15.2.0 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container.

Article author: Arthur Isaev