How to write a C main function

I know that now the children write their crazy "Applications" in Python and JavaScript. But do not be so quick to deny the C language - it can provide a lot of things, and concise. If you need speed, write your answer is probably in C language. If you are looking for a stable job or want to learn how to capture a null pointer dereference, C language may be your answer! In this article, I will explain how to construct a C file and write a C main function to successfully process the command line arguments.

I: a stubborn Unix systems programmer.

You: an editor, C compiler, and have time to kill the man.

Let's start now.

A boring but the correct C program

 

How to write a C main function

 

 

Parody O'Reilly book cover, "Hating Other People's Code"

C program beginning with main () function, usually stored in a file named main.c in.

/* main.c */
int main(int argc, char *argv[]) {
}

This program can compile but not do anything.

$ gcc main.c
$ ./a.out -o foo -vv
$

Correct but boring.

main function is unique.

main () function is the first function of the program at the beginning of the implementation of the execution, but not the first execution of a function. The first function is _start (), which is usually provided by the C runtime library, automatically links when compiling the program. This insight is highly dependent on the operating system and compiler tool chain, so I pretended not to mention it.

main () function takes two parameters, commonly referred to as the argv and argc, and returns a signed integer. Most Unix environments want the program to return on success and 0 (zero), return -1 (minus one) failure.

Parameter Name Parameter Description argc number vector number parameter vector argv array of character pointers

Argv parameter vector is to call the mark of your program command line representation. In the above example, argv is a list of the following strings:

argv = [ "/path/to/a.out", "-o", "foo", "-vv" ];

Parameter vector will ensure at least a first index string in its argv [0], which is the full path of program execution.

Main.c file analysis

When I started writing from scratch main.c, its structure is generally as follows:

/ * Main.c * / 
/ * 0 Rights / license * /
/ * contains 1 * /
/ * 2 Definitions * /
/ * 3 external declarations * /
/ * 4 Type Definitions * /
/ * global variables declared 5 * /
/ * 6 function prototypes * /
int main (int argc, char * the argv []) {
/ * command line parsing. 7 * /
}
/ * * function declarations 8 /

Now I will talk through these numbers, in addition to the part number is zero. If you have the copyright or license text in the source code, it is placed there.

Another thing I do not want to discuss is a comment.

"Commented a lie." 
- a cynical but clever and good-looking programmer.

Instead of using comments, it is better to use meaningful function names and variable names.

Given the inherent inertia programmer, once added comments, the maintenance burden will be doubled. If you change or refactoring code, you need to update or expand the comment. Over time, the code will look completely different, and content annotation described completely different.

If you have to write a comment, do not write about what the code is doing, on the contrary, why should write down the code written. Write something you will want to read the comments in five years, when you have forgotten the code. Fate of the world depends on you. Do not have pressure.

1, contains

I added to the main.c file is a file containing the first thing, they provide a lot of standard C standard library functions and variables for the program. C standard library to do a lot of things. Browse the header file / usr / include in, you can learn what they can do.

#include string is the C preprocessor (CPP) command, it will completely contained documents cited in the current file. C header file names are usually named .h extension, and should not contain any executable code. It only macro definitions, type definitions, external variables and function prototypes. String <header.h> tell cpp find a file named header.h in the header file system path defined, it is usually in / usr / include directory.

/* main.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <libgen.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#include <sys/types.h>

This is my default global contain a minimum contains a collection, it will be introduced:

Something stdio #include file provided to provide FILE, stdin, stdout, stderr and fprint () family of functions stdlib provide malloc (), calloc () and realloc () unistd provide EXIT_FAILURE, EXIT_SUCCESSlibgen provide basename () function errno errno variable and defined external all string values ​​that may be acceptable provided memcpy (), memset () and strlen () function to provide an external series getopt optarg, opterr, optind and getopt () function sys / types shortcut type definitions, such as uint32_t and uint64_t

2, the definition of

/* main.c */
<...>
#define OPTSTR "vi:o:f:h"
#define USAGE_FMT "%s [-v] [-f hexflag] [-i inputfile] [-o outputfile] [-h]"
#define ERR_FOPEN_INPUT "fopen(input, r)"
#define ERR_FOPEN_OUTPUT "fopen(output, w)"
#define ERR_DO_THE_NEEDFUL "do_the_needful blew up"
#define DEFAULT_PROGNAME "george"

It does not make much sense now, but I OPTSTR defined here will explain, it is suggested by the program command line switch. Reference getopt (3) man page to learn how OPTSTR will affect getopt () behavior.

USAGE_FMT defines a printf () style format string, it is used in Usage () function.

I would also like #define string constants in this part of the file. If necessary, they can be collected together more easily correct spelling, reuse, news and international news.

Finally, when naming #define all capital letters to distinguish between variable and function names. If needed, you can put words together or separated by an underscore, just make sure they are capitalized on the line.

3, the external statement

/* main.c */
<...>
extern int errno;
extern char *optarg;
extern int opterr, optind;

extern statement the name into the current namespace (ie "File") compilation unit, and allow the program to access the variable. Here we introduce three integer variables and a character pointer definition. Several variable opt prefix by the getopt () function is used, C standard library errno possible causes of the failure as a function of band communication channel to communicate.

4. Type Definition

/* main.c */
<...>
typedef struct {
int verbose;
uint32_t flags;
FILE *input;
FILE *output;
} options_t;

After the external declaration, I like to structure, union, and enumeration declarations typedef. Naming a typedef is a traditional habit. I like to use _t suffix to indicate that the name is a type. In this example, I will declare a struct 4 options_t members include. C is a space-independent programming language, so I will use the space field names are arranged in the same column. I just like the way it looks. For pointers declare that I am an asterisk in front of the name to make it clear that it is a pointer.

5, global variable declaration

/* main.c */
<...>
int dumb_global_variable = -11;

Global variables are a bad idea, you should never use them. But if you must use a global variable, in a statement here, and make sure to give them a default value. Really, do not use global variables.

6, the function prototype

/* main.c */
<...>
void usage(char *progname, int opt);
int do_the_needful(options_t *options);

When writing functions, add them to the main () function instead of before and after, here put the function prototype. Early C compilers use a single pass strategy, which means that each symbol (variable or function name) you use in your program must be declared before use. Modern compilers are almost always multi-pass compiler, they build a complete symbol table before generating code, so not strictly required function prototypes. Sometimes, however, you can not choose to use the code compiler, so please write the function prototype and continue to do so.

Of course, I always include a usage () function, when the main () function does not understand your incoming content from the command line, it will call this function.

7, command line parsing

/* main.c */
<...>
int main(int argc, char *argv[]) {
int opt;
options_t options = { 0, 0x0, stdin, stdout };
opterr = 0;
while ((opt = getopt(argc, argv, OPTSTR)) != EOF)
switch(opt) {
case 'i':
if (!(options.input = fopen(optarg, "r")) ){
perror(ERR_FOPEN_INPUT);
exit(EXIT_FAILURE);
/* NOTREACHED */
}
break;
case 'o':
if (!(options.output = fopen(optarg, "w")) ){
perror(ERR_FOPEN_OUTPUT);
exit(EXIT_FAILURE);
/* NOTREACHED */
}
break;

case 'f':
options.flags = (uint32_t )strtoul(optarg, NULL, 16);
break;
case 'v':
options.verbose += 1;
break;
case 'h':
default:
usage(basename(argv[0]), opt);
/* NOTREACHED */
break;
}
if (do_the_needful(&options) != EXIT_SUCCESS) {
perror(ERR_DO_THE_NEEDFUL);
exit(EXIT_FAILURE);
/* NOTREACHED */
}
return EXIT_SUCCESS;
}

Well, the code a bit more. The main () function's purpose is to collect the parameters provided by the user, performs basic input validation, and the collected parameters passed to the function using them. This example declares a default value is used to initialize the variable options, and parse command line options to update as needed.

The core main () function is a while loop that uses the getopt () to traverse argv, look for command-line option and its arguments (if any). OPTSTR front of the file is defined template-driven getopt () behavior. The value of any character command-line options opt to accept variable getopt () to find the program in response to detecting the occurrence of command-line options in a switch statement.

If you notice might ask, Why opt is declared as 32 int, but is expected to be 8 char? In fact getopt () returns an int, argv time when it reaches the end of a negative value, I would use the EOF (end of file marker) match. char is signed, but I would like to match their variable function return values.

Upon detection of a known command-line options, specific behavior occurs. In OPTSTR specify a parameter to the end of the colon, these options can have an argument. When there is an option a parameter, argv the next string can be provided to the program by optarg externally defined variables. I use optarg to open a file for reading and writing, or the command-line arguments converted from a string to an integer value.

Here are a few key points about the code style:

  • The opterr initialized to 0, prohibit getopt trigger?.
  • In the middle of main () exit (EXIT_FAILURE); or exit (EXIT_SUCCESS) ;.
  • / * NOTREACHED * / I like a lint command.
  • Return EXIT_SUCCESS used at the end of the function returns an int ;.
  • Display implicit type cast.

This program command line format, compiled as follows:

$ ./a.out -h
a.out [-v] [-f hexflag] [-i inputfile] [-o outputfile] [-h]

In fact, usage after the build () will send such content to stderr.

8, function declarations

/* main.c */
<...>
void usage(char *progname, int opt) {
fprintf(stderr, USAGE_FMT, progname?progname:DEFAULT_PROGNAME);
exit(EXIT_FAILURE);
/* NOTREACHED */
}
int do_the_needful(options_t *options) {
if (!options) {
errno = EINVAL;
return EXIT_FAILURE;
}
if (!options->input || !options->output) {
errno = ENOENT;
return EXIT_FAILURE;
}
/* XXX do needful stuff */
return EXIT_SUCCESS;
}

I write the last function is not a template function. In the present embodiment, the function do_the_needful () accepts a pointer to options_t structure. I verified options pointer is not NULL, then proceed to validate the input and structure of the members of the output. If a test fails, return EXIT_FAILURE, and by external global variable errno to the conventional error code, I can tell the caller routine cause of the error. The caller can use the convenience functions perror () to issue an error message is easy to read based on the value of errno.

Function is almost always in some way validate their input. If fully verified at great cost, then try to perform data validation once and regarded as immutable. Usage () function using the conditions fprintf () call assignment progname authentication parameters. Next usage () function to quit, so I will not bother to set errno, you do not have to worry about whether to use the correct name of the program.

Here, the biggest mistake I want to avoid is a NULL pointer dereference. This will cause the operating system to send a special signal called SYSSEGV to my process, leading to inevitable death. Most users do not want to see is caused by the collapse of SYSSEGV. Preferably NULL pointer capture more appropriate to issue an error message and gracefully close the program.

Some people complain that there are multiple return statements in the function body, they talked a lot about something "continuous flow of control" and the like. Honestly, if the intermediate functions errors, it should return the error condition. Write a lot of nested if statements that only a return is by no means a "good idea" ™.

Finally, if you write a function to accept more than four parameters, consider bind them into a structure, and pass a pointer to the structure. This makes the function signature simpler and easier to remember, and when you call after not go wrong. It also enables the calling function slightly faster, because of the need to replicate what the function stack less. In practice, only when the function is invoked millions or billions of times, we will consider this issue. If you think it does not make sense, it does not matter.

Wait, you do not say no comment! ? ! !

In do_the_needful () function, I wrote a special type of comment, it was designed as a placeholder, rather than to illustrate the code:

/* XXX do needful stuff */

When you write to you, sometimes you do not want to stop and write some particularly complex code, you will write later, but not now. That's what I left to their own place to come back again. I insert a comment with XXX prefix and a description of what needs to be done in a short note. After that, when I have more time, I will look for XXX in the source code. Prefix does not matter what, just make sure it is unlikely to appear in the library of your code in another context (such as the function name or variable) in.

Put them together

Well, when you compile this program, it is still almost no effect. But now you have a solid framework to build your own command line parsing C program.

/* main.c - the complete listing */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <libgen.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#define OPTSTR "vi:o:f:h"
#define USAGE_FMT "%s [-v] [-f hexflag] [-i inputfile] [-o outputfile] [-h]"
#define ERR_FOPEN_INPUT "fopen(input, r)"
#define ERR_FOPEN_OUTPUT "fopen(output, w)"
#define ERR_DO_THE_NEEDFUL "do_the_needful blew up"
#define DEFAULT_PROGNAME "george"
extern int errno;
extern char *optarg;
extern int opterr, optind;
typedef struct {
int verbose;
uint32_t flags;
FILE *input;
FILE *output;
} options_t;
int dumb_global_variable = -11;
void usage(char *progname, int opt);
int do_the_needful(options_t *options);
int main(int argc, char *argv[]) {
int opt;
options_t options = { 0, 0x0, stdin, stdout };
opterr = 0;
while ((opt = getopt(argc, argv, OPTSTR)) != EOF)
switch(opt) {
case 'i':
if (!(options.input = fopen(optarg, "r")) ){
perror(ERR_FOPEN_INPUT);
exit(EXIT_FAILURE);
/* NOTREACHED */
}
break;
case 'o':
if (!(options.output = fopen(optarg, "w")) ){
perror(ERR_FOPEN_OUTPUT);
exit(EXIT_FAILURE);
/* NOTREACHED */
}
break;

case 'f':
options.flags = (uint32_t )strtoul(optarg, NULL, 16);
break;
case 'v':
options.verbose += 1;
break;
case 'h':
default:
usage(basename(argv[0]), opt);
/* NOTREACHED */
break;
}
if (do_the_needful(&options) != EXIT_SUCCESS) {
perror(ERR_DO_THE_NEEDFUL);
exit(EXIT_FAILURE);
/* NOTREACHED */
}
return EXIT_SUCCESS;
}
void usage(char *progname, int opt) {
fprintf(stderr, USAGE_FMT, progname?progname:DEFAULT_PROGNAME);
exit(EXIT_FAILURE);
/* NOTREACHED */
}
int do_the_needful(options_t *options) {
if (!options) {
errno = EINVAL;
return EXIT_FAILURE;
}
if (!options->input || !options->output) {
errno = ENOENT;
return EXIT_FAILURE;
}
/* XXX do needful stuff */
return EXIT_SUCCESS;
}

Now, you're ready to write more maintainable C language.

Guess you like

Origin www.cnblogs.com/dtdg777/p/10994853.html