C Programming Best Practices (writing style)

Source: Internet
Author: User
Tags define function

Brief introduction
This article is written to meet the needs of developers. We've summed up a set of guidelines that, as a developer or consultant, have guided us well over the years, and we've provided them as a recommendation to help you with your work. You may not agree with some of these guidelines, but we hope you will like some of them and use them in your programming or porting projects.

Style and guide
* Use a source style that makes your code readable and consistent. If you don't have a team code style or your own style, you can use a style similar to the Kernighan and Ritchie styles used by most C programmers. However, to cite an extreme example, it is possible to eventually write code similar to the following:

int I;main () {for (; i["]<i;++i) {-I.}"]; Read ('-'-'-'-', i+++ ' hell/
O, world!/n ", '/'/'/'));} Read (j,i,p) {write (J/p+p,i---j,i/i);

-1984 "Poor Prize" in the Fuzzy C code contest. Anonymous should be requested by the code author.
* The main routine is usually defined as main (). The corresponding ANSI writing method is int main (void), if not the command line arguments, or int main (int argc, char **argv). The previous compiler of ANSI omits the void declaration, or lists the variable name and the declaration that followed it.
* Space
Take advantage of horizontal and vertical spaces. Indentation and space spacing should reflect the block structure of the code.
The long string of the conditional operator should be split into separate lines. For example:

if (foo->next==null && number < limit && limit <=size
&& node_active (this_input)) {...

Better change to:

if (Foo->next = = NULL
&& number < limit && limit <= SIZE
&& node_active (This_input))
{
...

Similarly, the For loop that is described in detail should be split into different rows:

for (Curr = *varp, trail = VarP;
Curr! = NULL;
Trail = & (curr->next), Curr = Curr->next)
{
...

For other complex expressions, such as expressions that use the ternary operator?:, it is best to split them into rows as well.

z = (x = = y)
? n + f (x)
: f (Y)-N;

* Notes
Comments should describe what is happening, how it is done, what the parameters represent, what global variables are used, and any restrictions or errors. But avoid unnecessary annotations. If the code is clear, and a good variable name is used, it should be able to describe itself better. Because the compiler does not check comments, they are not guaranteed to be correct. Comments that are inconsistent with the Code play the opposite role. Too many comments can confuse the code.
Here's an extra style of commenting:

i=i+1; /* ADD one to I */

The variable i is obviously incremented by 1. There are worse ways to annotate it:

/************************************
* *
* ADD one to I *
* *
************************************/
i=i+1;

* Naming conventions
Names with leading and trailing underscores are reserved for system use and should not be used for any user-created names. The agreement stipulates that:
1. #define constants should all be capitalized.
2. Enum constants should start with uppercase letters or all uppercase.
3. Functions, type definitions (typedef) and variable names, and struct (struct), Union (Union), and Enum (enum) tag names should be lowercase.
For clarity, avoid using names that differ only in case, such as Foo and foo. Similarly, avoid names such as Foobar and Foo_bar. Avoid using names that look similar. On many terminals and printers, "L", "1" and "I" look very similar. It is very unwise to use a variable named "L" because it looks very much like a constant "1".
* Variable Name
When choosing a variable name, the length is not important, but a clear expression is important. The long name can be used for global variables because it is not commonly used, and the array that will be used on each line of the loop is named I to be completely enough. If you use "index" or "Elementnumber", you will not only enter more, but also make the details of the calculation unclear. Using long variable names can sometimes make your code more difficult to understand. Comparison:

For (i=0 to 100)
Array[i]=0

And

For (elementnumber=0 to 100)
array[elementnumber]=0;

* Name of the function
The function name should reflect what the function does and what it returns. Functions are used in expressions, usually in an IF clause, so their intentions should be at a glance. For example:

if (checksize (x))

does not help because it does not tell us whether the Checksize returns true On error or True when there is no error;

if (validsize (x))

The intent of the function is clear.
* Disclaimer
All external data declarations should be preceded by the extern keyword.
The pointer qualifier "*" should be immediately adjacent to the variable name instead of the type. For example, you should use

Char *s, *t, *u;

Instead of

char* s, t, u;

The latter statement is not wrong, but it may not be what we expect because "T" and "U" are not declared as pointers.
* Header File
Header files should be organized by function, that is, the declaration of a separate subsystem should be in a separate header file. In addition, declarations that may change when code is ported from one platform to another should be in a separate header file.
Avoid using the same private header file name as the library header file name. The statement #include "MATH.H" will include the standard library math header file if the expected file cannot be found in the current directory. If this is the result you expect, you can comment out the line include statement.
Finally, it is not a good idea to use an absolute pathname for the header file. The C compiler's "include-path" option (-i-uppercase on many systems) is the preferred method for handling many private header vaults, allowing the directory structure to be re-organized without changing the source files.
* scanf
Never use scanf in important applications. Its error detection is not perfect. Take a look at the following example:

#include <stdio.h>
int main (void)
{
int i;
float F;
printf ("Enter an Integer and a float:");
scanf ("%d%f", &i, &f);
printf ("I read%d and%f/n", I, f);
return 0;
}

Test run
Enter an integer and a float:182 52.38
I Read 182 and 52.380001
Another test run
Enter an integer and a float:6713247896 4.4
I read-1876686696 and 4.400000
* + + and--
When you use the increment or decrement operator for a variable in a statement, the variable should not appear more than once in the statement, because the order of evaluation depends on the compiler. Do not make assumptions about the order when you write your code, nor do you write code that works as expected on a machine but does not have a well-defined behavior:

int i = 0, a[5];
A[i] = i++; /* Assign to a[0]? or a[1]? */

* Don't be fooled by appearances
Take a look at the following example:

while (c = = '/T ' | | c = ' | | c = = '/n ')
c = getc (f);

At first glance, the statement in the while clause appears to be a valid C code. However, using the assignment operator instead of the comparison operator produces semantically incorrect code. The priority of = is the lowest in all operators, so the statement is interpreted in the following way (parentheses are added for clarity):

while (c = = '/T ' | | c) = (' | | c = = '/n '))
c = getc (f);

The clause to the left of the assignment operator is:

(c = = '/T ' | | c)

It does not produce an lvalue. If C contains tabs, the result is "true" and no further evaluation is performed, and "true" cannot be on the left side of an assignment expression.
* Intention to be clear.
When you write code that can be interpreted as another intent, use parentheses or other methods to ensure that your intentions are clear. If you have to deal with the program in the future, it will help you understand your intentions. This can make maintenance tasks easier if others want to maintain the code.
It is sometimes possible to encode in a way that predicts that errors can occur. For example, you can place a constant on the left side of the comparison equation. That is, do not write:

while (c = = '/T ' | | c = = ' | | c = = '/n ')
c = getc (f);

Instead, write:

while ('/t ' = = c | | "' = = c | | '/n ' = = c)
c = getc (f);

The following methods are used to obtain the compiler diagnostics:

while ('/t ' = C | | "' = = c | | '/n ' = = c)
c = getc (f);

This style lets the compiler find the problem; The above statement is invalid because it attempts to assign a value to "/t".
* Unexpected trouble.
Various C implementations are usually different in some ways. Persisting in the use of language may be helpful for all implementations that are public. By doing this, you will be more likely to port the program to a new machine or compiler, and you will not encounter problems with the specifics of the compiler. For example, consider a string:

/*/*/2*/**/1

This takes advantage of the "maximum fit (maximal munch)" rule. If you can nest annotations, you can interpret the string as:
/*/*/2 */* */1

The two/* symbol matches the two */symbol, so the value of the string is 1. If annotations are not nested, on some systems, the/* in comments is ignored. On other systems, a warning is issued against/*. In either case, the expression can be interpreted as:

/*/* 2 */* */1

2 * 1 is worth 2.
* Empty the output buffer
When an application terminates abnormally, the tail of its output is often lost. The application may not have the opportunity to completely empty its output buffer. Some part of the output may still be in memory and will never be written out. On some systems, this output may be several pages long.
Losing output in this way is misleading because it gives the impression that the program failed long before it actually failed. The workaround for this problem is to force the output to be purged from the buffer, especially during debugging. The exact method varies depending on the system, but there are common methods, as follows:

Setbuf (stdout, (char *) 0);

The statement must be executed before any content is written to standard output. Ideally, this will be the first statement in the main program.
* GETCHAR ()-Macro or function
The following program copies its inputs to its output:

#include <stdio.h>
int main (void)
{
register int A;
while ((A = GetChar ())! = EOF)
Putchar (a);
}

Removing the #include statement from the program will make the program unable to compile because EOF will be undefined.
We can rewrite the program in the following ways:

#define EOF-1
int main (void)
{
register int A;
while ((A = GetChar ())! = EOF)
Putchar (a);
}

This is possible on many systems, but it is much slower to run on some systems.
Because function calls usually take a long time, GetChar is often implemented as a macro. This macro is defined in stdio.h, so when the #include <stdio.h> is removed, the compiler does not know what GetChar is. On some systems, assume that GetChar is a function that returns an int.
In fact, many C implementations have getchar functions in their libraries, in part because they prevent such errors. Thus, in the case of #include <stdio.h> omission, the compiler uses the GetChar version of the function. The overhead of a function call slows the program down. Putchar have the same problem.
* NULL pointer
A null pointer does not point to any object. Therefore, it is illegal to use null pointers for purposes other than assignment and comparison.
Do not redefine the NULL symbol. The NULL symbol should always be a constant value of 0. A null pointer of any given type is always equal to constant 0, and a variable with a value of zero or a comparison to a non-0 constant whose behavior is defined by the implementation.
Dereferencing a null pointer can cause strange things to happen.
* What does a+++++b mean?
The only meaningful way to parse it is to:

A + + + + b

However, the "best fit" rule requires that it be broken down into:

A + + + + b

This is syntactically invalid: it is equal to:

((a++) + +) + b

However, the result of a++ is not an lvalue, so the operand as + + is unacceptable. Thus, the parsing of lexical ambiguity rules makes it impossible to parse the example in a syntactically meaningful way. The prudent approach, of course, is to avoid such constructs, in fact, without fully determining their meaning. Of course, adding spaces helps the compiler understand the intent of the statement, but it is preferable (from a code maintenance perspective) to split this construct into multiple rows:

++b;
(a++) + B;

* Handle functions with care
The function is the most common structure concept in C. They are used to implement a "top-down" problem resolution-that is, to break up the problem into smaller sub-problems until each sub-problem can be represented in code. This helps with the modularity and documentation of the program. In addition, programs made up of many small functions are easier to debug.
If you have some function parameters that are not the expected types, cast them to the desired type, even if you are sure that you do not need to, because (if not converted) They may cause you trouble when you least expect them to. In other words, the compiler typically promotes and converts the type of a function parameter to the desired data type to conform to the declaration of the function parameter. However, doing this manually in your code can clearly explain the programmer's intentions and ensure that you have the correct results when porting code to other platforms.
If the header file fails to declare the return type of the library function, declare them yourself. Surround your claims with #ifdef/#endif statements in case the code is ported to another platform.
A function prototype should be used to make the code more robust and make it run faster.
* Floating Else
Unless you know what you're doing, you should avoid the "dangling else" issue:

if (a = = 1)
if (b = = 2)
printf ("***/n");
Else
printf ("###/n");

The rule is the else appended to the nearest if. When in doubt, or with ambiguous possibilities, add curly braces to illustrate the block structure of your code.
* Array bounds
Check the array bounds of all arrays, including the string, because someone might enter "Floccinaucinihilipilification" where you are now typing "fubar". Robust software products should not use the gets ().
The fact that the C subscript starts with 0 makes all the counting problems simpler. However, it takes some effort to master how to deal with them.
* Empty statement
The empty statement body of a for or while loop should be on a single line and annotated, indicating that the empty statement body is intentionally placed, rather than omitting the code.

while (*dest++ = *src++)
; /* VOID */

* Test True (TRUE) or False (false)
Do not test non-0 values by default, that is:

if (f ()! = FAIL)

Better than

if (f ())

Although the value of FAIL may be 0 (false in C). (Of course, there should be a tradeoff between this style and the constructs demonstrated in the "Function name" section.) When someone later thinks that the return value of the failure should be-1 instead of 0 o'clock, an explicit test will help you.
A common problem is to use the STRCMP function to test whether a string is equal and should never handle its results in the default way. A more desirable approach is to define the macro streq:

#define STREQ (STR1, str2) (strcmp ((str1), (str2)) = = 0)

In this way, the statement

If (Streq (inputstring, somestring)) ...

There is an implied behavior that the Act does not change without your knowledge (people tend not to rewrite or redefine standard library functions like strcmp ().
Do not use 1 to check for equality Boolean values (TRUE and YES, etc.), and 0 to test for inequality (FALSE and NO, etc.). Most functions are guaranteed to return 0 if the condition is False (false), but nonzero if the condition is true (true). Therefore, it is best to

if (func () = = TRUE) {...

Written

if (func ()! = FALSE)

* EMBED statement
Use embedded assignment statements to see time and place. In some constructs, there is no better way to achieve results without using more and less readable code:

while ((c = GetChar ())! = EOF) {
Process the character
}

It is possible to use embedded assignment statements to improve run-time performance. However, you should weigh between increasing speed and reducing maintainability, and using an embedded assignment statement in a man-specified location can result in a reduced serviceability. For example:

x = y + z;
D = x + R;

should not be replaced by:

D = (x = y + z) + R;

Even though the latter may save a cycle. Ultimately, the difference in running time between the two will be reduced as the optimizer increases, and the difference in serviceability will increase.
* Goto Statement
Goto should be used conservatively. When jumping out of a layer of switch, for, and while nesting, it is useful to use this statement, but if there is such a need, the internal constructs should be decomposed into separate functions.

For (...) {
while (...) {
...
if (wrong)
Goto error;

}
}
...
Error
Print a message

When you must use Goto, the accompanying label should be on a single line, with a tab or at the beginning of the line to the left of the subsequent code. Both the Goto statement and the target should be annotated to illustrate its role and purpose.
* "Failed" in switch (Fall-through)
When a piece of code has several labels, place the labels on separate lines. This style is consistent with the use of vertical spaces and makes it a simple task to rearrange the case options (if that is required). The "fail" feature of the C switch statement should be commented to facilitate future maintenance. If this feature has brought you "trouble", then you can understand the importance of doing so!

Switch (expr) {
Case ABC:
Case DEF:
Statement
Break
Case UVW:
Statement /*fallthrough*/
Case XYZ:
Statement
Break
}

Although technically, the last break is not required, if you later add another case after the last one, the consistent use of break can prevent a "fail" error. If you use the default case statement, it should always be the last, and (if it is the last statement) does not require the last break statement.
* constant
Symbolic constants make the code easier to read. The use of numeric constants should be avoided as much as possible, and a #define function using C preprocessor gives a meaningful name to a constant. Defining values in one location (preferably in a header file) also makes it easier to manage large programs, because you can change the constant value uniformly by simply changing the definition. You might consider using an enumeration data type as an improvement to a variable that only takes a set of discrete values for the declaration. Using enumerations also allows the compiler to warn you of any misuse of your enumerated types. Any direct-coded numeric constant must have at least one comment indicating the source of the value.
The definition of a constant should be consistent with its use, for example, to use 540.0 for floating-point numbers instead of 540 by using implicit floating-point type casts. That is, in some cases, constants 0 and 1 can appear directly in their own form, rather than in the form of a definition. For example, if a for loop iterates through an array, then:

for (i = 0; i < arraysub; i++)

Very reasonable, while the code:

gate_t *front_gate = Opens (Gate[i], 7);
if (front_gate = = 0)
Error ("Can ' t open%s/n", gate[i]);

is unreasonable. In the second example, Front_gate is a pointer, and when the value is a pointer, it should be compared to NULL instead of 0. Even simple values such as 1 or 0 are usually best represented by definitions such as TRUE and FALSE (sometimes YES and NO are read more clearly).
Do not use floating-point variables where discrete values are required. This is due to the imprecise representation of the floating-point number (see the second Test in scanf above). Use <= or >= to test floating-point numbers; a precise comparison (= = or! =) may not detect an "acceptable" equivalence.
A simple character constant should be defined as a character literal instead of as a number. Non-literal characters are not advocated because they are not portable. If you must use non-literal characters, especially if they are used in strings, you should write them using an escape character (such as "/007") with three-bit octal numbers (not one character). Even so, such usage should be considered machine-related and should be treated as such.
* Conditional Compilation
Conditional compilation can be used for machine affinity, debugging, and setting certain options at compile time. Various controls can be easily combined in unpredictable ways. If the #ifdef is used for machine affinity, make sure that errors occur when no machine is specified, rather than using the default machine. #error pseudo-directives can be used more conveniently for this purpose. If you use #ifdef for optimization, the default value should be a non-optimized code instead of a non-compiled or incorrect program. Be sure to test the code that is not optimized.

Other
* Like make this utility for compiling and linking greatly simplifies the task of moving applications from one environment to another. During development, make compiles only those modules that have changed since the last time that you made it.
often use lint. Lint is a C program checker that examines C source files to detect and report mismatch and inconsistency between the types of function definitions and invocations, as well as possible program errors, and so on.
Also, look at the compiler documentation to learn about the switches that make the compiler "picky". The compiler's job is to be precise, so it reports possible errors by using the appropriate command-line options.
* Minimizes the number of global symbols in the application. One of the benefits of doing this is the reduced likelihood of conflicts with system-defined functions.
* Many programs fail when the input is omitted. Null input tests should be performed on all programs. This may also help you understand how the program works.
* Do not assume any excessive assumptions about your users or the language implementations you are using. Things that are "impossible" can happen sometimes. A robust program can guard against such situations. If you need to find a boundary condition, your users will find it in some way!
Never make any assumptions about the size of a given type, especially pointers.
When using char types in expressions, most implementations treat them as unsigned types, but some implementations use them as signed types. When you use them in arithmetic expressions, it is recommended that you always type-cast them.
Do not rely on initialization of automatic variables and the memory returned by malloc.
* Make clear the purpose and structure of your program.
* Remember that you may be asked later to modify your code or run it on another machine. Write your code carefully so that it can be ported to other machines.

Conclusion
The maintenance of the application takes a lot of programmer's time, which is well known. This is partly due to the use of non-portable and nonstandard features as well as the unsatisfactory programming style when developing applications. In this article, we have introduced some guidelines that have been of great help to us over the years. We believe that as long as these guidelines are adhered to, it will make application maintenance easier in a team environment.

Resources
* obfuscated C and other mysteries, written by Don Libes, John Wiley and Sons, Inc. ISBN 0-471-57805-3
* The C programming Language,second Edition, written by Brian W. Kernighan and Dennis M. Ritchie, PRENTICE-HALL,ISBN 0-13-110370-9
* Safer C, written by Les Hatton, MCGRAW-HILL,ISBN 0-07-707640-0
* C Traps and pitfalls written by Andrew Koenig, at T-Bell LABORATORIES,ISBN 0-201-17928-9


About the author

Shiv Dutta is a technical advisor to IBM Systems Group who helps independent software vendors enable their applications on pseries servers. Shiv has extensive experience as a software developer, system Administrator, and instructor. He supports AIX for system management, problem determination, performance tuning, and sizing guidance. Shiv was involved in this work at the time of AIX's birth. He received a PhD in physics from Ohio University and can contact him via [email protected].

Gary R. Hook is IBM's senior technical consultant providing application development, porting, and technical assistance to independent software vendors. Mr. Hook's professional experience is primarily in the development of Unix-based applications. When he joined IBM in 1990, he worked at the Aix Technical Support Center in Texas State Southlake to provide consulting and technical assistance to clients, focusing on AIX application architectures. Mr. Hook now lives in Austin, and during 1995-2000 he was a member of the Aix Kernel development team, specializing in Aix-linked programs, loading programs, and common application development tools. You can contact him by [email protected].

http://blog.csdn.net/adcxf/article/details/2113999

C Programming Best Practices (writing style)

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.