Every C program starts at main, and main can receive arguments from the shell. argc is the count, argv is the array of strings. Most tutorials stop there. This one covers validation, flag parsing, and the mistakes that let users crash your program by passing unexpected input.

The Basics

#include <stdio.h>

int main(int argc, char *argv[]) {
    printf("argc = %dn", argc);

    for (int i = 0; i < argc; i++) {
        printf("argv[%d] = "%s"n", i, argv[i]);
    }

    return 0;
}
$ ./program hello world 42
argc = 4
argv[0] = "./program"
argv[1] = "hello"
argv[2] = "world"
argv[3] = "42"

argv[0] is always the program name (or path). argv[argc] is guaranteed to be NULL — you can iterate the argv array by checking for null instead of using argc.

Converting argv Strings to Numbers

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <number>n", argv[0]);
        return 1;
    }

    /* WRONG: atoi returns 0 for invalid input, no error detection */
    int bad = atoi(argv[1]);

    /* RIGHT: strtol detects errors */
    char *endptr;
    errno = 0;
    long good = strtol(argv[1], &endptr, 10);

    if (errno != 0) {
        perror("strtol");
        return 1;
    }
    if (endptr == argv[1]) {
        fprintf(stderr, "Not a number: %sn", argv[1]);
        return 1;
    }
    if (*endptr != '') {
        fprintf(stderr, "Trailing garbage: %sn", endptr);
        return 1;
    }

    printf("You provided: %ldn", good);
    return 0;
}

Never use atoi on user input — it silently returns 0 for invalid strings, can’t distinguish “0” from “abc”, and has no overflow detection. Use strtol and check all three error conditions: errno, empty parse (endptr == argv[i]), and trailing garbage (*endptr != '').

Parsing Flags — Manual Approach

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {
    int   verbose = 0;
    int   count   = 1;
    char *output  = "out.txt";

    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-v") == 0) {
            verbose = 1;

        } else if (strcmp(argv[i], "-n") == 0) {
            if (i + 1 >= argc) {
                fprintf(stderr, "-n requires a valuen");
                return 1;
            }
            count = (int)strtol(argv[++i], NULL, 10);

        } else if (strncmp(argv[i], "-o=", 3) == 0) {
            output = argv[i] + 3;   /* pointer arithmetic: skip "-o=" */

        } else if (argv[i][0] == '-') {
            fprintf(stderr, "Unknown flag: %sn", argv[i]);
            return 1;
        } else {
            /* positional argument */
            printf("Positional: %sn", argv[i]);
        }
    }

    if (verbose) printf("verbose=%d count=%d output=%sn", verbose, count, output);
    return 0;
}
$ ./prog -v -n 5 -o=result.txt input.c
verbose=1 count=5 output=result.txt
Positional: input.c

Using getopt for Standard POSIX Flag Parsing

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>   /* getopt — POSIX only, not available on Windows */

int main(int argc, char *argv[]) {
    int verbose = 0;
    int count   = 1;
    int opt;

    /* "v" = flag, "n:" = option with required argument */
    while ((opt = getopt(argc, argv, "vn:")) != -1) {
        switch (opt) {
            case 'v': verbose = 1; break;
            case 'n': count = atoi(optarg); break;
            default:
                fprintf(stderr, "Usage: %s [-v] [-n count]n", argv[0]);
                return 1;
        }
    }

    printf("verbose=%d count=%dn", verbose, count);

    /* Remaining non-flag arguments start at argv[optind] */
    for (int i = optind; i < argc; i++) {
        printf("arg: %sn", argv[i]);
    }

    return 0;
}

getopt handles the POSIX flag conventions: combined flags (-vn 5), argument after flag (-n 5 or -n5), and the -- end-of-flags separator. It is not available on Windows without a compatibility layer.

Validating Bounds on Numeric Arguments

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

int parse_port(const char *str) {
    char *end;
    long port = strtol(str, &end, 10);

    if (end == str || *end != '') {
        fprintf(stderr, "Invalid port: %sn", str);
        return -1;
    }
    if (port < 1 || port > 65535) {
        fprintf(stderr, "Port out of range: %ldn", port);
        return -1;
    }
    return (int)port;
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <port>n", argv[0]);
        return 1;
    }

    int port = parse_port(argv[1]);
    if (port == -1) return 1;

    printf("Listening on port %dn", port);
    return 0;
}

You can test all argv parsing patterns in our run c online compiler — hardcode the argv values in main for quick iteration, then switch to real args when you deploy locally. See also our guide on string functions in Cstrcmp, strncmp, and strlen are the core tools for argv parsing.

TL;DR

  • argv[0] is the program name; user arguments start at argv[1]
  • argv[argc] is guaranteed to be NULL
  • Never use atoi on user input — use strtol and check errno, endptr, and trailing characters
  • Always validate bounds on numeric arguments before using them
  • Use getopt on POSIX systems for standard flag handling
  • Print a usage message to stderr and return 1 on invalid arguments
  • The -- argument signals the end of flags — everything after is a positional argument