Dr. Roger Ianjamasimanana

Practical guide to the C main function

By Dr. Roger Ianjamasimanana

1. The role of the main function

The main function serves as the entry point of a C program. When you execute a C program, the operating system invokes the main function, and begins the execution of of your code there. Without a main function, the compiler wouldn’t know where to start executing your program, resulting in errors during the compilation process. In this practical guide, we will look at the structure and use of the main function in C.

2. Is the main function mandatory?

The main function is mandatory in virtually all C programs intended to produce an executable. However, there are specific scenarios, particularly in specialized environments, where main might not be required or used differently. For hosted environment like Windows, Linux, and macOS, the main function is mandatory. For freestanding environment (e.g., microcontrollers, firmware) the entry point of the program is implementation-defined, meaning it can vary based on the system or compiler. These systems might use different startup routines or require specific initialization functions instead of main.

3. Variations of the main function

The recommended forms of the main function are:

  • int main()
  • int main(int argc, char *argv[])

Using void main() is non-standard and should be avoided as it leads to undefined behavior and portability issues.

3.1. Basic structure (no arguments)


int main() {
    // Your code here
    return 0;
}

Let’s comment our code above. Our defined main function takes no parameters. It returns an integer to the operating system upon termination. A return value of 0 typically means that the program executed successfully without any errors. Returning 0 is a widely accepted convention across different operating systems and environments. In Unix-like systems, shell scripts and other programs interpret an exit status of 0 as "no error."

A non-zero return value, such as 1, typically indicates that the program encountered an error or did not execute as intended. While 1 is commonly used to indicate a general error, other non-zero values can represent specific error types. In Unix-like systems, any non-zero exit status is interpreted as an error by shell scripts and other programs.

3.2. With command-line arguments


int main(int argc, char *argv[]) {
    // Your code here
    return 0;
}

Parameters:

  • int argc: argc stands for "argument count"; it represents the number of command-line arguments passed to the program when it is executed. It is always of type int.
  • char *argv[]: argv stands for "argument vector". It is an array of strings representing the command-line arguments. For example argv[0] access the name of the program being executed. The asterisk before argv '*' is necessary to allow each string (argument) to be of varying lengths, as each pointer can point to a string of any size. It also avoids allocating a fixed size for each string, which can be wasteful if the arguments vary in length.

When you type the following command on the terminal,


./program Hello world

here we have two command line arguments, which are "Hello" and "world". Thus argc is equal to two, argv[0] is equal to "./program", argv[1] is equal to "Hello", and argv[2] is equal to world. This kind of code is ideal for programs that need to process input provided via the command line. Let’s study the example below.

#include <stdio.h>
int main(int argc, char *argv[]) {
    printf("Number of arguments: %d\n", argc);
    for(int i = 0; i < argc; i++) {
        printf("Argument %d: %s\n", i, argv[i]);
    }
    return 0;
}

Save the above program in a file as program.c and run as follow:


    gcc program.c -o program
    ./program one two

After you run the above program, you should get the following output:


Number of arguments: 3
Argument 0: ./program
Argument 1: one
Argument 2: two

3.3. Using void as a parameter list

When you declare main as int main(void), it tells the compiler and anyone reading the code that main does not expect any input parameters from the environment or the command line.


int main(void) {
// Your code here
return 0;
}

Example:

#include <stdio.h>

int main(void) {
    printf("Hello, World!\n");
    return 0;
}

While both int main() and int main(void) are commonly used, there are subtle differences, especially concerning older C standards:

C89/C90 standard:

  • int main(): implies that main can accept an unspecified number of parameters.
  • int main(void): explicitly declares that main takes no parameters.

C99 and later standards:

  • Both int main() and int main(void) are treated similarly, indicating that main takes no parameters.
  • The compiler enforces that no arguments are passed to main when declared as int main(void).

To ensure clarity and adherence to modern C standards, I recommend using int main(void) when your program does not require any command-line arguments.

3.4. Alternative return types (non-standard)

The C standard specifies that main should return an int. Despite this, some compilers allow alternative return types, such as void. I do not recommend using alternative return types as it may lead to undefined behavior or portability issues.

4. Example codes

4.1. Simple main function without parameters

#include <stdio.h>

int main() {
    printf("Hello, World!\n");
    return 0;
}

The program above takes no command-line arguments. It prints a greeting message and exits successfully.

4.2. The main function with command-line arguments

#include <stdio.h>

int main(int argc, char *argv[]) {
    if(argc > 1) {
        printf("First argument: %s\n", argv[1]);
    } else {
        printf("No arguments provided.\n");
    }
    return 0;
}

The above code checks if any command-line arguments are provided. If yes, it prints the first argument; otherwise, it notifies the user that no arguments were provided.

Let’s run the program with a command line argument:


./program Hello

Output:


First argument: Hello

4.3. Error signaling via return value

#include <stdio.h>

int main() {
    int division = 10 / 0; // This will cause a runtime error
    printf("Result: %d\n", division);
    return 0;
}

Explanation: we attempted to divide by zero, which is an undefined behavior. Depending on the system, this may cause a runtime error, and the program may terminate abnormally. To handle such errors, you can return a non-zero value to indicate failure.

Let’s modify the above code to handle the error more efficienctly.

#include <stdio.h>

int main() {
    int divisor = 0;
    if(divisor == 0) {
        printf("Error: Division by zero.\n");
        return 1; // Non-zero return value indicates an error
    }
    int division = 10 / divisor;
    printf("Result: %d\n", division);
    return 0;
}

Output:

    
Error: Division by zero.
    
Here, we have used an exit status of 1 to indicate an error and 0 for a successful execution.

5. Best practices for the main function

✍️
Always return an integer

Use int as the return type for main. Ensure that all code paths in main return an integer value.

✍️
Use void main() only in freestanding environments.

Stick to int main() for hosted environments to ensure portability and standard compliance.

✍️
Handle command-line arguments appropriately

Use argc and argv when your program needs to process input from the command line. Validate the number and type of arguments to prevent errors.

✍️
Return meaningful exit codes

Return 0 for successful execution and non-zero values for different error conditions. This allows other programs or scripts to interpret the result.

✍️
Keep the main function concise

Delegate tasks to other functions to maintain readability and modularity. Avoid cluttering the main function with too much logic.

Below is an example of a well-structured main function

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

// Function prototypes
void greetuser();
int performcalculation(int a, int b);

int main(int argc, char *argv[]) {
    greetuser();
    
    if(argc != 3) {
        fprintf(stderr, "Input error. Usage example: %s  \n", argv[0]);
        return 1;
    }
    
    int num1 = atoi(argv[1]);
    int num2 = atoi(argv[2]);
    
    int result = performcalculation(num1, num2);
    printf("Result: %d\n", result);
    
    return 0;
}

void greetuser() {
    printf("Welcome to the Calculator Program!\n");
}

int performcalculation(int a, int b) {
    return a + b; // Simple addition
}

Strength of the code

  • Modularity: functions like greetuser and performcalculation handle specific tasks.
  • Error handling: Checks if the correct number of command-line arguments are provided.
  • Exit codes: returns 1 if usage is incorrect, 0 otherwise.
  • Clarity: main is easy to read and understand.

6. Final notes

Keep in mind the following recommendations while writing your C main function.

  • Mandatory nature: The main function is essential for any C program that produces an executable. It acts as the starting point for program execution.
  • Standard signature: While int main() is the most common form, other signatures like int main(int argc, char *argv[]) are also valid and useful for handling command-line arguments.
  • Return type: Always use int as the return type for main to comply with the C standard and ensure consistent behavior across different systems and compilers.
  • Best practices: Keep main concise, handle errors gracefully, and structure your program by delegating tasks to other functions for better readability and maintainability.

By adhering to these guidelines, you ensure that your C programs are robust, portable, and maintainable. These are your tickets to a solid foundation for more advanced programming concepts and practices.

feature-top
Readers’ comment
feature-top
Log in to add a comment
🔐 Access