C: Advanced I/O and Command Line

Command-line arguments are like ordering at a restaurant — you tell the program what you want (argv), the program counts how many items you ordered (argc), and then prepares your order.

1. Command-Line Arguments

(1) argc and argv

The main function can receive two parameters:

TEXT 📖 Display only
int main(int argc, char *argv[])
C
#include <stdio.h>

int main(int argc, char *argv[]) {
    printf("Argument count: %d\n", argc);
    for (int i = 0; i < argc; i++) {
        printf("argv[%d] = %s\n", i, argv[i]);
    }
    return 0;
}

Run:

BASH
./program hello world 123
TEXT 📖 Display only
Argument count: 4
argv[0] = ./program
argv[1] = hello
argv[2] = world
argv[3] = 123
💡 Tip: Command-line arguments are all strings! If you need numbers, you must convert them using atoi, atol, or strtol.

(2) Argument Parsing in Practice

In real projects, command-line arguments typically use - prefixes for options:

TEXT 📖 Display only
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
            verbose = 1;
        } else if (strcmp(argv[i], "-n") == 0 && i + 1 < argc) {
            count = atoi(argv[++i]);
        } else if (argv[i][0] != '-') {
            filename = argv[i];
        } else {
            fprintf(stderr, "Unknown option: %s\n", argv[i]);
            return 1;
        }
    }

    if (filename == NULL) {
        fprintf(stderr, "Usage: %s [-v] [-n count] filename\n", argv[0]);
        return 1;
    }

    printf("File: %s, Count: %d, Verbose: %s\n",
           filename, count, verbose ? "on" : "off");
    return 0;
}
⚠️ Note: Manual argument parsing is error-prone. Complex projects can use the getopt function, which handles both short and long option combinations.


2. Environment Variables

(1) getenv

C
char *getenv(const char *name);

Returns the value of the environment variable, or NULL if it doesn't exist.

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

int main(void) {
    const char *path = getenv("PATH");
    if (path != NULL) {
        printf("PATH = %s\n", path);
    }

    const char *home = getenv("HOME");
    if (home != NULL) {
        printf("HOME = %s\n", home);
    }
    return 0;
}

(2) The extern Variable environ

C defines a global variable environ that points to an array of all environment variable strings:

C
#include <stdio.h>

extern char **environ;

int main(void) {
    char **env = environ;
    while (*env) {
        printf("%s\n", *env);
        env++;
    }
    return 0;
}
💡 Tip: Environment variables are commonly used to configure program behavior, such as database connection strings and log levels. This is much more flexible than hardcoding values into source code.


3. errno Error Handling

(1) The errno Mechanism

When C standard library functions encounter errors, they typically don't crash directly. Instead, they set the global variable errno and return an error value:

C
#include <errno.h>

errno is not automatically cleared to zero after a successful library function call! You must only check it after a function call has failed.

(2) perror / strerror

C
void perror(const char *s);
char *strerror(int errnum);

perror outputs s: error message to stderr. strerror returns the text description corresponding to the error code.

TEXT 📖 Display only
#include <stdio.h>
#include <errno.h>
#include <string.h>

int main(void) {
    FILE *fp = fopen("nonexistent_file.txt", "r");
    if (fp == NULL) {
        perror("fopen");
        printf("Error code: %d, Error message: %s\n", errno, strerror(errno));
        return 1;
    }
    fclose(fp);
    return 0;
}
TEXT 📖 Display only
fopen: No such file or directory
Error code: 2, Error message: No such file or directory

▶ Example

Complete error handling pattern:

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

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

    FILE *fp = fopen(argv[1], "r");
    if (fp == NULL) {
        fprintf(stderr, "Cannot open '%s': %s\n", argv[1], strerror(errno));
        return 1;
    }

    int ch;
    while ((ch = fgetc(fp)) != EOF) {
        putchar(ch);
    }

    if (ferror(fp)) {
        fprintf(stderr, "Read error occurred: %s\n", strerror(errno));
        fclose(fp);
        return 1;
    }

    fclose(fp);
    return 0;
}
▶ Try it Yourself
⚠️ Note: ferror checks whether a stream has experienced a read/write error, which is different from feof (which checks for end of file).


4. Standard Streams

C automatically opens three streams when a program starts:

Stream Name Default Device File Descriptor
stdin Standard input Keyboard 0
stdout Standard output Screen 1
stderr Standard error Screen 2

The difference between stdout and stderr: stdout is buffered, stderr is unbuffered. When redirecting, they do not affect each other.

C
#include <stdio.h>

int main(void) {
    fprintf(stdout, "This is normal output\n");
    fprintf(stderr, "This is error output\n");
    return 0;
}
BASH
./program > output.txt

After running this way, "normal output" goes to the file, while "error output" still appears on the screen.

(1) Redirection Principles

At the command line:

BASH
./program 2>&1 all.log

This command merges stderr into stdout and writes both to all.log.


5. Temporary Files

(1) tmpfile

C
FILE *tmpfile(void);

Creates a temporary file opened in "wb+" mode. The file is automatically deleted when closed or when the program ends.

C
#include <stdio.h>

int main(void) {
    FILE *tmp = tmpfile();
    if (tmp == NULL) {
        perror("tmpfile");
        return 1;
    }

    fprintf(tmp, "Temporary data %d\n", 42);
    rewind(tmp);

    char buf[64];
    while (fgets(buf, sizeof(buf), tmp) != NULL) {
        printf("%s", buf);
    }

    fclose(tmp);
    return 0;
}
TEXT 📖 Display only
Temporary data 42

(2) tmpnam

C
char *tmpnam(char *s);

Generates a temporary file name that doesn't conflict with existing files. However, there's a race condition — between generating the name and creating the file, another program could create a file with the same name.

⚠️ Note: Prefer tmpfile — it atomically creates and opens the file, making it safer. tmpnam is not safe in multi-threaded environments.

▶ Example

Using a temporary file to process intermediate data:

C 📖 Display only
#include <stdio.h>
#include <string.h>

int main(void) {
    FILE *tmp = tmpfile();
    if (tmp == NULL) {
        perror("tmpfile");
        return 1;
    }

    char *lines[] = {"banana", "apple", "orange", "grape"};
    int n = 4;

    for (int i = 0; i < n; i++) {
        fprintf(tmp, "%s\n", lines[i]);
    }

    rewind(tmp);
    printf("Before sorting:\n");
    char buf[64];
    while (fgets(buf, sizeof(buf), tmp) != NULL) {
        buf[strcspn(buf, "\n")] = '\0';
        printf("  %s\n", buf);
    }

    rewind(tmp);
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - 1 - i; j++) {
            char a[64], b[64];
            long pos = ftell(tmp);
            fgets(a, sizeof(a), tmp);
            fgets(b, sizeof(b), tmp);
            a[strcspn(a, "\n")] = '\0';
            b[strcspn(b, "\n")] = '\0';

            if (strcmp(a, b) > 0) {
                fseek(tmp, pos, SEEK_SET);
                fprintf(tmp, "%s\n%s\n", b, a);
            } else {
                fseek(tmp, pos, SEEK_SET);
                fprintf(tmp, "%s\n%s\n", a, b);
            }
            fseek(tmp, pos + strlen(a) + strlen(b) + 2, SEEK_SET);
        }
        rewind(tmp);
    }

    rewind(tmp);
    printf("After sorting:\n");
    while (fgets(buf, sizeof(buf), tmp) != NULL) {
        buf[strcspn(buf, "\n")] = '\0';
        printf("  %s\n", buf);
    }

    fclose(tmp);
    return 0;
}
47 logic lines (exceeds 40-line limit, display only)
TEXT 📖 Display only
Before sorting:
  banana
  apple
  orange
  grape
After sorting:
  apple
  grape
  orange
  banana

6. Advanced Formatted Output

(1) snprintf

C
int snprintf(char *str, size_t size, const char *format, ...);

Compared to sprintf, snprintf adds a size parameter to limit the write length, preventing buffer overflow.

C
#include <stdio.h>

int main(void) {
    char buf[10];
    int n = snprintf(buf, sizeof(buf), "Hello, %s!", "World");
    printf("buf = \"%s\", required length = %d\n", buf, n);
    return 0;
}
TEXT 📖 Display only
buf = "Hello, Wo", required length = 13
💡 Tip: The return value of snprintf is the length that the formatted output should have, not the actual number of bytes written. If the return value is greater than size-1, the output was truncated.

❓ FAQ

Q What is the order of arguments in argv?
A argv[0] is the program name itself, and argv[1] onward are the user-supplied arguments, in the same order as on the command line.
Q Why does perror output to stderr instead of stdout?
A Because error messages should not be lost when stdout is redirected to a file. stderr is unbuffered and independent from stdout, ensuring error messages are always visible.
Q When is errno cleared to zero?
A It is never automatically cleared. You must manually set errno = 0 before calling a function that might fail, and then check it after the function returns an error value.

📖 Summary

📝 Exercises

  1. Write a command-line program that accepts -c to count characters, -l to count lines, and -w to count words in a file
  2. Write a program that uses tmpfile to reverse the contents of a file (write everything to a temporary file, then read it back in reverse order)
  3. Write a program that demonstrates the difference between stdout and stderr redirection, using > and 2> to capture each type of output separately
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏