C — Command-line Arguments
Command-line arguments let your C program receive input from the user at invocation time. Instead of hardcoding values or reading from stdin, you can pass flags, options, and data directly when you run the program. This is the foundation of every useful CLI tool — from ls and grep to custom build scripts and data processors.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | int main(int argc, char *argv[]) { |
| 4 | printf("You passed %d argument(s)\n", argc); |
| 5 | for (int i = 0; i < argc; i++) { |
| 6 | printf(" argv[%d] = \"%s\"\n", i, argv[i]); |
| 7 | } |
| 8 | return 0; |
| 9 | } |
Running ./hello_args foo bar produces three entries: the program name, "foo", and "bar". The program name itself is always the first argument.
The main function can accept two parameters: argc (argument count) and argv (argument vector). argc is an int that holds the number of arguments passed, always at least 1 because the program name counts as the first argument. argv is an array of character pointers (strings), where each element points to one argument.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | int main(int argc, char *argv[]) { |
| 4 | /* argc is always >= 1 */ |
| 5 | printf("argc = %d\n", argc); |
| 6 | |
| 7 | /* argv[0] is always the program name */ |
| 8 | printf("Program name: %s\n", argv[0]); |
| 9 | |
| 10 | /* argv[argc] is NULL on most systems (C99/C11 guarantee) */ |
| 11 | if (argv[argc] == NULL) { |
| 12 | printf("argv[argc] is NULL (standard behavior)\n"); |
| 13 | } |
| 14 | |
| 15 | /* Access each argument */ |
| 16 | for (int i = 1; i < argc; i++) { |
| 17 | printf("Argument %d: %s\n", i, argv[i]); |
| 18 | } |
| 19 | return 0; |
| 20 | } |
note
argv[argc] is a null pointer. This lets you iterate without checking argc — you can just loop while argv[i] != NULL.Understanding how argv is laid out in memory helps you manipulate arguments correctly. The argv array is a contiguous block of pointers, each pointing to a null-terminated string. The OS loads these strings into your process's memory space before main is called.
| 1 | #include <stdio.h> |
| 2 | #include <string.h> |
| 3 | |
| 4 | int main(int argc, char *argv[]) { |
| 5 | /* Show the address of each argv pointer */ |
| 6 | printf("argv is at: %p\n", (void *)argv); |
| 7 | printf("sizeof(char*): %zu\n", sizeof(char *)); |
| 8 | |
| 9 | /* Each string's address and length */ |
| 10 | for (int i = 0; i < argc; i++) { |
| 11 | printf("argv[%d] addr=%p len=%zu str=\"%s\"\n", |
| 12 | i, (void *)argv[i], strlen(argv[i]), argv[i]); |
| 13 | } |
| 14 | |
| 15 | /* You can also modify individual characters */ |
| 16 | if (argc > 1) { |
| 17 | argv[1][0] = 'X'; /* changes first char of argv[1] */ |
| 18 | printf("Modified argv[1]: %s\n", argv[1]); |
| 19 | } |
| 20 | return 0; |
| 21 | } |
warning
argv strings is technically allowed by the standard, but modifying the argv pointers themselves (e.g., argv[1] = "new") is undefined behavior on many platforms.There are three standard forms of the main function. All are valid C, and the choice depends on whether your program needs command-line arguments.
| Form | Use Case |
|---|---|
int main(void) | No arguments needed |
int main(int argc, char *argv[]) | With arguments (array syntax) |
int main(int argc, char **argv) | With arguments (pointer-to-pointer) |
The second and third forms are equivalent — char *argv[] and char **argv decay to the same thing in function parameters. Use whichever you find more readable.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | /* Form 1: no arguments */ |
| 4 | int main(void) { |
| 5 | printf("No arguments accepted.\n"); |
| 6 | return 0; |
| 7 | } |
| 8 | |
| 9 | /* Form 2: argc + argv (array form) */ |
| 10 | /* int main(int argc, char *argv[]) { ... } */ |
| 11 | |
| 12 | /* Form 3: argc + argv (pointer form) */ |
| 13 | /* int main(int argc, char **argv) { ... } */ |
All command-line arguments arrive as strings. When you need numeric values — like port numbers, file sizes, or coordinates — you must convert them. C provides several functions for this, each with different safety and capability trade-offs.
| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | #include <errno.h> |
| 4 | |
| 5 | int main(int argc, char *argv[]) { |
| 6 | if (argc < 3) { |
| 7 | fprintf(stderr, "Usage: %s <int> <float>\n", argv[0]); |
| 8 | return 1; |
| 9 | } |
| 10 | |
| 11 | /* atoi: simple but no error detection */ |
| 12 | int a = atoi(argv[1]); |
| 13 | printf("atoi: %d\n", a); |
| 14 | |
| 15 | /* atof: simple float conversion */ |
| 16 | double b = atof(argv[2]); |
| 17 | printf("atof: %f\n", b); |
| 18 | |
| 19 | /* strtol: proper integer conversion with error checking */ |
| 20 | char *endptr; |
| 21 | errno = 0; |
| 22 | long val = strtol(argv[1], &endptr, 10); |
| 23 | if (errno != 0 || *endptr != '\0' || endptr == argv[1]) { |
| 24 | fprintf(stderr, "Invalid integer: %s\n", argv[1]); |
| 25 | return 1; |
| 26 | } |
| 27 | printf("strtol: %ld\n", val); |
| 28 | |
| 29 | /* strtod: proper float conversion with error checking */ |
| 30 | errno = 0; |
| 31 | double d = strtod(argv[2], &endptr); |
| 32 | if (errno != 0 || *endptr != '\0' || endptr == argv[2]) { |
| 33 | fprintf(stderr, "Invalid float: %s\n", argv[2]); |
| 34 | return 1; |
| 35 | } |
| 36 | printf("strtod: %f\n", d); |
| 37 | |
| 38 | return 0; |
| 39 | } |
| Function | Converts To | Error Detection | Recommended? |
|---|---|---|---|
atoi() | int | None — returns 0 on failure | No |
atof() | double | None — returns 0.0 on failure | No |
strtol() | long | errno + endptr check | Yes |
strtod() | double | errno + endptr check | Yes |
strtoul() | unsigned long | errno + endptr check | Yes |
best practice
strtol() or strtod() over atoi() and atof(). The latter cannot distinguish between the input "0" and an invalid input — both return 0.Most CLI programs accept flags and options. A simple convention is to check for arguments starting with - (single dash for short flags) or -- (double dash for long options). Here's how to build a basic parser by hand.
| 1 | #include <stdio.h> |
| 2 | #include <string.h> |
| 3 | |
| 4 | int main(int argc, char *argv[]) { |
| 5 | int verbose = 0; |
| 6 | int count = 1; |
| 7 | const char *output = "output.txt"; |
| 8 | |
| 9 | for (int i = 1; i < argc; i++) { |
| 10 | if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) { |
| 11 | verbose = 1; |
| 12 | } else if (strcmp(argv[i], "-n") == 0 && i + 1 < argc) { |
| 13 | count = atoi(argv[++i]); |
| 14 | } else if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) { |
| 15 | output = argv[++i]; |
| 16 | } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { |
| 17 | printf("Usage: %s [-v] [-n count] [-o file]\n", argv[0]); |
| 18 | printf(" -v, --verbose Enable verbose output\n"); |
| 19 | printf(" -n <count> Number of iterations (default: 1)\n"); |
| 20 | printf(" -o <file> Output file (default: output.txt)\n"); |
| 21 | return 0; |
| 22 | } else { |
| 23 | fprintf(stderr, "Unknown option: %s\n", argv[i]); |
| 24 | fprintf(stderr, "Try '%s --help' for usage.\n", argv[0]); |
| 25 | return 1; |
| 26 | } |
| 27 | } |
| 28 | |
| 29 | printf("verbose=%d count=%d output=%s\n", verbose, count, output); |
| 30 | return 0; |
| 31 | } |
info
argv[i+1]. The check i + 1 < argc prevents buffer overread when an option expects a value but none was provided.On POSIX systems (Linux, macOS, BSD), the getopt() function handles short option parsing automatically. It supports combined flags (-abc is equivalent to -a -b -c), options with required arguments, and the special -- separator.
| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | #include <unistd.h> |
| 4 | |
| 5 | int main(int argc, char *argv[]) { |
| 6 | int verbose = 0; |
| 7 | int count = 1; |
| 8 | char *output = NULL; |
| 9 | int opt; |
| 10 | |
| 11 | /* optstring: letters = flags, letter: = requires arg */ |
| 12 | while ((opt = getopt(argc, argv, "vn:o:h")) != -1) { |
| 13 | switch (opt) { |
| 14 | case 'v': |
| 15 | verbose = 1; |
| 16 | break; |
| 17 | case 'n': |
| 18 | count = atoi(optarg); /* optarg = argument to -n */ |
| 19 | break; |
| 20 | case 'o': |
| 21 | output = optarg; |
| 22 | break; |
| 23 | case 'h': |
| 24 | printf("Usage: %s [-v] [-n count] [-o file]\n", argv[0]); |
| 25 | exit(0); |
| 26 | default: |
| 27 | fprintf(stderr, "Unknown option: -%c\n", optopt); |
| 28 | exit(1); |
| 29 | } |
| 30 | } |
| 31 | |
| 32 | /* optind = index of first non-option argument */ |
| 33 | printf("verbose=%d count=%d output=%s\n", verbose, count, output); |
| 34 | printf("Remaining args: "); |
| 35 | for (int i = optind; i < argc; i++) { |
| 36 | printf("%s ", argv[i]); |
| 37 | } |
| 38 | printf("\n"); |
| 39 | return 0; |
| 40 | } |
The optstring controls parsing: a letter alone means a boolean flag, a letter followed by : means it requires an argument. The global optarg holds the argument value, and optind tracks the current index.
GNU systems provide getopt_long() for parsing long options like --verbose and --output=file. You define options in a struct option array.
| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | #include <getopt.h> |
| 4 | |
| 5 | int main(int argc, char *argv[]) { |
| 6 | int verbose = 0; |
| 7 | int count = 1; |
| 8 | char *output = NULL; |
| 9 | |
| 10 | static struct option long_opts[] = { |
| 11 | {"verbose", no_argument, 0, 'v'}, |
| 12 | {"count", required_argument, 0, 'n'}, |
| 13 | {"output", required_argument, 0, 'o'}, |
| 14 | {"help", no_argument, 0, 'h'}, |
| 15 | {0, 0, 0, 0} |
| 16 | }; |
| 17 | |
| 18 | int opt; |
| 19 | while ((opt = getopt_long(argc, argv, "vn:o:h", long_opts, NULL)) != -1) { |
| 20 | switch (opt) { |
| 21 | case 'v': verbose = 1; break; |
| 22 | case 'n': count = atoi(optarg); break; |
| 23 | case 'o': output = optarg; break; |
| 24 | case 'h': |
| 25 | printf("Usage: %s [OPTIONS] [FILES...]\n", argv[0]); |
| 26 | printf(" -v, --verbose Verbose output\n"); |
| 27 | printf(" -n, --count=N Iteration count\n"); |
| 28 | printf(" -o, --output=FILE Output file\n"); |
| 29 | exit(0); |
| 30 | default: |
| 31 | exit(1); |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | printf("verbose=%d count=%d output=%s\n", verbose, count, output); |
| 36 | return 0; |
| 37 | } |
pro tip
{0, 0, 0, 0} sentinel at the end is mandatory. Use no_argument, required_argument, or optional_argument for the third field.Beyond command-line arguments, your program can read environment variables using getenv(). This is useful for configuration that persists across runs — like PATH, HOME, or custom variables like MY_APP_CONFIG.
| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | |
| 4 | int main(int argc, char *argv[]) { |
| 5 | /* Read individual environment variables */ |
| 6 | const char *home = getenv("HOME"); |
| 7 | const char *path = getenv("PATH"); |
| 8 | const char *shell = getenv("SHELL"); |
| 9 | |
| 10 | printf("HOME = %s\n", home ? home : "(not set)"); |
| 11 | printf("PATH = %s\n", path ? path : "(not set)"); |
| 12 | printf("SHELL = %s\n", shell ? shell : "(not set)"); |
| 13 | |
| 14 | /* Set a new environment variable (affects child processes) */ |
| 15 | setenv("MY_APP_VERSION", "1.0.0", 1); |
| 16 | printf("MY_APP_VERSION = %s\n", getenv("MY_APP_VERSION")); |
| 17 | |
| 18 | /* Use env var as default, override with argument */ |
| 19 | const char *config = getenv("MY_APP_CONFIG"); |
| 20 | if (argc > 1) { |
| 21 | config = argv[1]; /* command-line takes precedence */ |
| 22 | } |
| 23 | printf("Config: %s\n", config ? config : "(none)"); |
| 24 | |
| 25 | return 0; |
| 26 | } |
warning
getenv() returns a pointer to the environment string. Do not free it or modify it. If you call setenv() or putenv() afterward, the old pointer may become invalid.On POSIX systems, you can also access the entire environment via the global environ variable (declared in <unistd.h>).
| 1 | #include <stdio.h> |
| 2 | #include <unistd.h> |
| 3 | |
| 4 | extern char **environ; |
| 5 | |
| 6 | int main(void) { |
| 7 | for (char **env = environ; *env != NULL; env++) { |
| 8 | printf("%s\n", *env); |
| 9 | } |
| 10 | return 0; |
| 11 | } |
Let's build a practical example — a calculator that takes an operator and two numbers from the command line. This demonstrates argument validation, numeric conversion, and error handling.
| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | #include <string.h> |
| 4 | #include <errno.h> |
| 5 | |
| 6 | static double parse_number(const char *str, const char *name) { |
| 7 | char *end; |
| 8 | errno = 0; |
| 9 | double val = strtod(str, &end); |
| 10 | if (errno != 0 || *end != '\0' || end == str) { |
| 11 | fprintf(stderr, "Error: invalid %s \"%s\"\n", name, str); |
| 12 | exit(1); |
| 13 | } |
| 14 | return val; |
| 15 | } |
| 16 | |
| 17 | int main(int argc, char *argv[]) { |
| 18 | if (argc != 4) { |
| 19 | fprintf(stderr, "Usage: %s <num> <op> <num>\n", argv[0]); |
| 20 | fprintf(stderr, " Operations: + - * /\n"); |
| 21 | return 1; |
| 22 | } |
| 23 | |
| 24 | double a = parse_number(argv[1], "first operand"); |
| 25 | const char *op = argv[2]; |
| 26 | double b = parse_number(argv[3], "second operand"); |
| 27 | |
| 28 | double result; |
| 29 | if (strcmp(op, "+") == 0) { |
| 30 | result = a + b; |
| 31 | } else if (strcmp(op, "-") == 0) { |
| 32 | result = a - b; |
| 33 | } else if (strcmp(op, "*") == 0) { |
| 34 | result = a * b; |
| 35 | } else if (strcmp(op, "/") == 0) { |
| 36 | if (b == 0.0) { |
| 37 | fprintf(stderr, "Error: division by zero\n"); |
| 38 | return 1; |
| 39 | } |
| 40 | result = a / b; |
| 41 | } else { |
| 42 | fprintf(stderr, "Error: unknown operator \"%s\"\n", op); |
| 43 | return 1; |
| 44 | } |
| 45 | |
| 46 | printf("%g %s %g = %g\n", a, op, b, result); |
| 47 | return 0; |
| 48 | } |
A custom echo tool that supports flags for newlines, escaping, and separators. This pattern mirrors how real Unix utilities handle options.
| 1 | #include <stdio.h> |
| 2 | #include <string.h> |
| 3 | |
| 4 | int main(int argc, char *argv[]) { |
| 5 | int newline = 1; |
| 6 | int escape = 0; |
| 7 | const char *sep = " "; |
| 8 | int start = 1; |
| 9 | |
| 10 | /* Parse options */ |
| 11 | for (int i = 1; i < argc; i++) { |
| 12 | if (strcmp(argv[i], "-n") == 0) { |
| 13 | newline = 0; |
| 14 | } else if (strcmp(argv[i], "-e") == 0) { |
| 15 | escape = 1; |
| 16 | } else if (strcmp(argv[i], "-s") == 0 && i + 1 < argc) { |
| 17 | sep = argv[++i]; |
| 18 | } else if (strcmp(argv[i], "--") == 0) { |
| 19 | start = i + 1; |
| 20 | break; |
| 21 | } else { |
| 22 | break; /* first non-option argument */ |
| 23 | } |
| 24 | } |
| 25 | |
| 26 | /* Print remaining arguments with separator */ |
| 27 | for (int i = start; i < argc; i++) { |
| 28 | if (i > start) { |
| 29 | fputs(sep, stdout); |
| 30 | } |
| 31 | if (escape) { |
| 32 | /* Simple escape: \n -> newline, \t -> tab */ |
| 33 | for (const char *p = argv[i]; *p; p++) { |
| 34 | if (*p == '\\' && *(p + 1)) { |
| 35 | p++; |
| 36 | switch (*p) { |
| 37 | case 'n': putchar('\n'); break; |
| 38 | case 't': putchar('\t'); break; |
| 39 | case '\\': putchar('\\'); break; |
| 40 | default: putchar('\\'); putchar(*p); break; |
| 41 | } |
| 42 | } else { |
| 43 | putchar(*p); |
| 44 | } |
| 45 | } |
| 46 | } else { |
| 47 | fputs(argv[i], stdout); |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | if (newline) { |
| 52 | putchar('\n'); |
| 53 | } |
| 54 | return 0; |
| 55 | } |
A grep-like tool that reads stdin and filters lines matching a pattern. This shows how arguments and stdin work together — a very common Unix pattern.
| 1 | #include <stdio.h> |
| 2 | #include <string.h> |
| 3 | #include <stdlib.h> |
| 4 | |
| 5 | #define MAX_LINE 4096 |
| 6 | |
| 7 | int main(int argc, char *argv[]) { |
| 8 | int invert = 0; |
| 9 | int ignore_case = 0; |
| 10 | const char *pattern = NULL; |
| 11 | |
| 12 | for (int i = 1; i < argc; i++) { |
| 13 | if (strcmp(argv[i], "-v") == 0) { |
| 14 | invert = 1; |
| 15 | } else if (strcmp(argv[i], "-i") == 0) { |
| 16 | ignore_case = 1; |
| 17 | } else if (argv[i][0] == '-') { |
| 18 | fprintf(stderr, "Unknown option: %s\n", argv[i]); |
| 19 | return 1; |
| 20 | } else { |
| 21 | pattern = argv[i]; |
| 22 | } |
| 23 | } |
| 24 | |
| 25 | if (!pattern) { |
| 26 | fprintf(stderr, "Usage: %s [-v] [-i] PATTERN\n", argv[0]); |
| 27 | return 1; |
| 28 | } |
| 29 | |
| 30 | char line[MAX_LINE]; |
| 31 | while (fgets(line, sizeof(line), stdin)) { |
| 32 | /* Remove trailing newline */ |
| 33 | line[strcspn(line, "\n")] = '\0'; |
| 34 | |
| 35 | int match; |
| 36 | if (ignore_case) { |
| 37 | match = (strstr(line, pattern) != NULL); |
| 38 | } else { |
| 39 | /* Simple case-insensitive: use strcasestr if available */ |
| 40 | match = (strstr(line, pattern) != NULL); |
| 41 | } |
| 42 | |
| 43 | if (match != invert) { |
| 44 | printf("%s\n", line); |
| 45 | } |
| 46 | } |
| 47 | |
| 48 | return 0; |
| 49 | } |
The value of argv[0] depends on how the program was invoked. If you create a symlink or alias, argv[0] reflects that name. This is how tools like busybox and vim (which symlinks to vi, nvim, etc.) detect their own name and change behavior accordingly.
| 1 | #include <stdio.h> |
| 2 | #include <string.h> |
| 3 | #include <libgen.h> /* basename() */ |
| 4 | |
| 5 | int main(int argc, char *argv[]) { |
| 6 | printf("argv[0] = %s\n", argv[0]); |
| 7 | |
| 8 | /* Extract just the filename (POSIX) */ |
| 9 | char *prog = basename(argv[0]); |
| 10 | printf("basename = %s\n", prog); |
| 11 | |
| 12 | /* Behavior changes based on program name */ |
| 13 | if (strcmp(prog, "list") == 0) { |
| 14 | printf("Running in LIST mode\n"); |
| 15 | } else if (strcmp(prog, "count") == 0) { |
| 16 | printf("Running in COUNT mode\n"); |
| 17 | } else { |
| 18 | printf("Running in default mode\n"); |
| 19 | } |
| 20 | return 0; |
| 21 | } |
Command-line handling works similarly across Unix, Linux, and Windows, but there are differences. On Windows, you may need to handle argument quoting differently, and getopt() is not natively available — you must use a third-party implementation or handle parsing manually.
| Feature | Unix/Linux | Windows (MSVC) |
|---|---|---|
| Argument splitting | Shell splits before calling main() | GetCommandLine + CommandLineToArgvW |
| getopt() | Native (POSIX) | Not available — use a library |
| Wildcard expansion | Shell expands globs | Program receives literal * (must expand) |
| argv[0] | Full path or invoked name | Full path to executable |
note
wmain(int argc, wchar_t *argv[]) for Unicode argument support. The standard main function receives arguments encoded in the system's active code page.A more complete example that combines numeric arguments, flags, file operations, and comprehensive error handling. This mirrors the structure of real-world CLI tools.
| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | #include <string.h> |
| 4 | #include <errno.h> |
| 5 | #include <sys/stat.h> |
| 6 | |
| 7 | static void usage(const char *prog) { |
| 8 | fprintf(stderr, "Usage: %s [OPTIONS] FILE...\n", prog); |
| 9 | fprintf(stderr, "Options:\n"); |
| 10 | fprintf(stderr, " -s Show file size in bytes\n"); |
| 11 | fprintf(stderr, " -l Show long format (size + permissions)\n"); |
| 12 | fprintf(stderr, " -h Show this help message\n"); |
| 13 | } |
| 14 | |
| 15 | int main(int argc, char *argv[]) { |
| 16 | int show_size = 0; |
| 17 | int long_format = 0; |
| 18 | int file_start = 1; |
| 19 | |
| 20 | /* Parse options */ |
| 21 | for (int i = 1; i < argc; i++) { |
| 22 | if (strcmp(argv[i], "-s") == 0) { |
| 23 | show_size = 1; |
| 24 | } else if (strcmp(argv[i], "-l") == 0) { |
| 25 | long_format = 1; |
| 26 | } else if (strcmp(argv[i], "-h") == 0) { |
| 27 | usage(argv[0]); |
| 28 | return 0; |
| 29 | } else if (argv[i][0] == '-') { |
| 30 | fprintf(stderr, "Unknown option: %s\n", argv[i]); |
| 31 | usage(argv[0]); |
| 32 | return 1; |
| 33 | } else { |
| 34 | file_start = i; |
| 35 | break; |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | if (file_start >= argc) { |
| 40 | fprintf(stderr, "Error: no files specified\n"); |
| 41 | usage(argv[0]); |
| 42 | return 1; |
| 43 | } |
| 44 | |
| 45 | int exit_code = 0; |
| 46 | for (int i = file_start; i < argc; i++) { |
| 47 | struct stat st; |
| 48 | if (stat(argv[i], &st) != 0) { |
| 49 | fprintf(stderr, "%s: %s\n", argv[i], strerror(errno)); |
| 50 | exit_code = 1; |
| 51 | continue; |
| 52 | } |
| 53 | |
| 54 | if (long_format) { |
| 55 | printf("%s: size=%ld bytes\n", argv[i], (long)st.st_size); |
| 56 | } else if (show_size) { |
| 57 | printf("%ld\n", (long)st.st_size); |
| 58 | } else { |
| 59 | printf("%s\n", argv[i]); |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | return exit_code; |
| 64 | } |
Good error handling in argument parsing is critical. Users will pass invalid input, missing arguments, and unknown flags. Your program should provide clear, helpful error messages and exit with appropriate codes.
| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | #include <string.h> |
| 4 | #include <errno.h> |
| 5 | #include <limits.h> |
| 6 | |
| 7 | /* Safe integer parsing with range checking */ |
| 8 | int parse_int(const char *str, const char *name, int min, int max) { |
| 9 | char *end; |
| 10 | errno = 0; |
| 11 | long val = strtol(str, &end, 10); |
| 12 | |
| 13 | if (errno != 0) { |
| 14 | fprintf(stderr, "Error: %s overflow/underflow\n", name); |
| 15 | exit(1); |
| 16 | } |
| 17 | if (*end != '\0' || end == str) { |
| 18 | fprintf(stderr, "Error: %s is not a valid integer (\"%s\")\n", |
| 19 | name, str); |
| 20 | exit(1); |
| 21 | } |
| 22 | if (val < min || val > max) { |
| 23 | fprintf(stderr, "Error: %s must be between %d and %d\n", |
| 24 | name, min, max); |
| 25 | exit(1); |
| 26 | } |
| 27 | return (int)val; |
| 28 | } |
| 29 | |
| 30 | int main(int argc, char *argv[]) { |
| 31 | if (argc < 3) { |
| 32 | fprintf(stderr, "Usage: %s <port> <threads>\n", argv[0]); |
| 33 | fprintf(stderr, " port: 1-65535\n"); |
| 34 | fprintf(stderr, " threads: 1-256\n"); |
| 35 | return 1; |
| 36 | } |
| 37 | |
| 38 | int port = parse_int(argv[1], "port", 1, 65535); |
| 39 | int threads = parse_int(argv[2], "threads", 1, 256); |
| 40 | |
| 41 | printf("port=%d threads=%d\n", port, threads); |
| 42 | return 0; |
| 43 | } |
best practice
argv[0]. When users pipe output or run programs from scripts, seeing mytool: error is much more helpful than just error.