Definition and declaration of variables and functions

Source: Internet
Author: User

2. Definitions and declarations
2.1. extern and static keywords
In the previous section, we put two program files together to compile the link, main. c uses the push, pop, and is_empty functions from the stack. c provides, in fact, a small problem, we use the-Wall option to compile main. c:

$ Gcc-c main. c-Wall
Main. c: In function 'main ':
Main. c: 8: warning: implicit declaration of function 'push'
Main. c: 12: warning: implicit declaration of function 'is _ empty'
Main. c: 13: warning: implicit declaration of function 'pop' we discussed this issue in section 2nd "user-defined functions" because the compiler did not find a function prototype when processing the function call code, you have to make an implicit declaration based on the function call code, and declare the three functions:

Int push (char );
Int pop (void );
Int is_empty (void); now it is easier to understand this rule than to learn "user-defined functions" in section 2nd. Why does the compiler need a function prototype when processing function call code? You must know the type and number of parameters and the type of the returned value to know what commands are generated. Why is implicit declaration unreliable? The implicit declaration is derived from the function call code. In fact, the type of the form parameter defined by the function may be different from the type of the real parameter passed by the function call code, if the function definition has a variable parameter (for example, printf), the function call code cannot tell that the function has a variable parameter. In addition, the function call code does not show the type of the returned value. Therefore, the implicit declaration only specifies that the returned values are of the int type. Since implicit declaration is unreliable, why don't the compiler look for function definitions by itself, instead of asking us to write the function prototype before calling it? Because the compiler often does not know where to look for function definitions, as in the above example, I asked the compiler to compile main. c, and the definitions of these functions are in the stack. in c, how does the compiler know? Therefore, the compiler can only guess the function prototype through implicit declaration. This kind of speculation often fails, but it is still available in a relatively simple situation, for example, the example in the previous section can get the correct result after compilation.

Now we declare the prototype of these functions in main. c:

/* Main. c */
# Include <stdio. h>

Extern void push (char );
Extern char pop (void );
Extern int is_empty (void );

Int main (void)
{
Push ('A ');
Push ('B ');
Push ('C ');
 
While (! Is_empty ())
Putchar (pop ());
Putchar ('\ n ');

Return 0;
} Then the compiler will not report a warning. Here, the extern keyword indicates that this identifier has External Linkage. External Linkage is defined in the previous chapter, but it should be easier to understand now. The push identifier has External Linkage. c and stack. c is linked together, if the push is in main. c and stack. c has declarations (in stack. c Declaration is also defined), then these declarations refer to the same function, followed by the same GLOBAL symbol, representing the same address. The extern in the function declaration can also be omitted without writing. The function declaration without writing the extern also indicates that this function has the External Linkage.

If a function declaration is modified with the static keyword, it indicates that the identifier has an Internal Linkage. For example, there are two program files:

/* Foo. c */
Static void foo (void) {}/ * main. c */
Void foo (void );
Int main (void) {foo (); return 0;} An error occurred when compiling the link:

$ Gcc foo. c main. c
/Tmp/ccRC2Yjn. o: In function 'main ':
Main. c :(. text + 0x12): undefined reference to 'foo'
Collect2: ld returned 1 exit status although in foo. c defines the function foo, but this function only has Internal Linkage, only in foo. c Indicates the same function multiple times, but in main. c does not indicate it. If foo. c is compiled into the target file. The function name foo is a LOCAL symbol in it and does not participate in the Link process. Therefore, main. in c, an External Linkage foo function is used, but the linker cannot find its definition, and cannot determine its address, so it cannot parse the symbol, so it has to report an error. All variables or functions declared multiple times must have and only one declaration is defined. If there are multiple or none of them, the linker cannot complete the link.

The above describes how to use static and extern to modify the function declaration. Now let's look at the situation where they are used to modify variable declarations. The example of stack. c and main. c is still used. If I want to directly access the top variable defined in stack. c In main. c, I can declare it with extern:

/* Main. c */
# Include <stdio. h>

Void push (char );
Char pop (void );
Int is_empty (void );
Extern int top;

Int main (void)
{
Push ('A ');
Push ('B ');
Push ('C ');
Printf ("% d \ n", top );
 
While (! Is_empty ())
Putchar (pop ());
Putchar ('\ n ');
Printf ("% d \ n", top );

Return 0;
} The variable top has External Linkage, and its storage space is in the stack. c, so main. in c, the variable declaration extern int top; is not a variable definition because it does not allocate storage space. The preceding function and variable declaration can also be written in the main function body so that the declared identifier has a block scope:

Int main (void)
{
Void push (char );
Char pop (void );
Int is_empty (void );
Extern int top;

Push ('A ');
Push ('B ');
Push ('C ');
Printf ("% d \ n", top );
 
While (! Is_empty ())
Putchar (pop ());
Putchar ('\ n ');
Printf ("% d \ n", top );

Return 0;
} Note: the Declaration of variables is a little different from that of function declaration. The extern of function declaration can be written but not written. If the declaration of variables is not written, the meaning of extern will change completely, if the preceding example does not contain extern, it indicates defining a local variable top in the main function. Note that the stack. the definition in c is int top =-1;, while main. initializer cannot be added to the Declaration in c. If the preceding example is written as extern int top =-1, the compiler reports an error.

In main. in c, you can use variable declarations to access the stack. c. from the perspective of the c module, the top variable does not want to be accessed by the outside world. Both the top and stack variables belong to the internal status of the module, the outside world should only allow the push and pop functions to change the internal status of the module, so as to ensure the LIFO feature of the stack. If the outside world can randomly access the stack or modify the top, the status of the stack is messy. So how can we prevent external access to top and stack? The answer is to declare them as Internal Linkage using the static Keyword:

/* Stack. c */
Static char stack [512];
Static int top =-1;

Void push (char c)
{
Stack [++ top] = c;
}

Char pop (void)
{
Return stack [top --];
}

Int is_empty (void)
{
Return top =-1;
} In this way, the top and stack variables of stack. c cannot be accessed even if extern is declared in main. c. This protects the internal status of the stack. c module, which is also an Encapsulation idea.

It is also for this purpose to declare functions with Internal Linkage using the static keyword. In a module, some functions are provided for External use, also known as Export for External use. These functions are declared as External Linkage. Some functions are declared as Internal Linkage if they are used only within the module and do not want to be accessed by the outside world.

2.2. header file
We will continue with the previous discussions on stack. c and main. c. The stack. c Module encapsulates the top and stack variables and exports three function interfaces: push, pop, and is_empty, which have been well designed. However, it is difficult to write three function declarations in every program file of this module. Assume that there is another foo. c also uses this module, main. c and foo. c requires three function declarations. Repeated code should always be avoided. In the past, we used various methods to extract repeated code, for example, in section 2nd "Array application instance: "Counting random numbers" refers to the problem of avoiding hard coding with macro definitions. What can be done this time? The answer is that you can write a header file stack. h:

/* Stack. h */
# Ifndef STACK_H
# Define STACK_H
Extern void push (char );
Extern char pop (void );
Extern int is_empty (void );
# In this way, endif only needs to include this header file in main. c, without writing three function declarations:

/* Main. c */
# Include <stdio. h>
# Include "stack. h"

Int main (void)
{
Push ('A ');
Push ('B ');
Push ('C ');
 
While (! Is_empty ())
Putchar (pop ());
Putchar ('\ n ');

Return 0;
} First, why does # include <stdio. h> use angle brackets, while # include "stack. h" use quotation marks. For header files contained in parentheses, gcc first looks for the directory specified by the-I option, and then finds the system's header file directory (usually/usr/include, my system also includes/usr/lib/gcc/i486-linux-gnu/4.3.2/include); for header files included in quotation marks, gcc first looks for header files that contain the header file. the directory where the c file is located, then find the directory specified by-I option, and then find the system's header file directory.

Assume that all three code files are in the current directory:

$ Tree
.
| -- Main. c
| -- Stack. c
'-- Stack. h

0 directories, 3 files can be compiled using gcc-c main. c. gcc will automatically find stack. h In the directory where main. c is located. If we move stack. h to a sub-directory:

$ Tree
.
| -- Main. c
'-- Stack
| -- Stack. c
'-- Stack. h

1 directory and 3 files must be compiled using gcc-c main. c-Istack. Use the-I option to tell the gcc header file to be found in the subdirectory stack.

You can use relative paths in the # include pre-processing instructions. For example, you can change the above Code to # include "stack/stack. h ", you do not need to add the-Istack option during compilation, because gcc will automatically. c is located in the directory, and the header file is relative to main. the relative path of directory c is stack/stack. h.

In the stack. in h, we see two new pre-processing indicators # ifndef STACK_H and # endif, which means that if the macro STACK_H has not been defined, the code from # ifndef to # endif is included in the output result of preprocessing. Otherwise, the Code cannot appear in the output result of preprocessing. Stack. h The content of this header file is fully enclosed by # ifndef and # endif. If the macro STACK_H has been defined when this header file is included, it is equivalent to nothing in this header file, contains an empty file. How can this be used? Assume that main. c contains two stack. h:

...
# Include "stack. h"
# Include "stack. h"

Int main (void)
{
... The macro STACK_H is not defined when stack. h is included for the first time. Therefore, the header file content is included in the output result of preprocessing:

...
# Define STACK_H
Extern void push (char );
Extern char pop (void );
Extern int is_empty (void );
# Include "stack. h"

Int main (void)
{
... The macro STACK_H has been defined. Therefore, the second inclusion of stack. h is equivalent to containing an empty file, which avoids Repeated inclusion of header file content. This Header file protection method is called Header Guard. In the future, Header Guard will be added to each Header file, and the macro definition name will be capitalized with the Header file name. This is a standard practice.

So why do we need to prevent repeated inclusion? Who will include a header file twice? No one will make such obvious mistakes as above, but sometimes the repeated errors are not so obvious. For example:

# Include "stack. h"
# Include "foo. h". However, foo. h contains bar. h, bar. h, and stack. h. In large projects, it is common to include header files in header files, which usually contain four or five layers. At this time, it is difficult to find out the problem of Repeated inclusion. For example, in my system header file directory/usr/include, errno. h contains bits/errno. h. The latter also contains linux/errno. h. The latter contains asm/errno. h. The latter contains asm-generic/errno. h.

Another problem is, even if I repeatedly include the header file, is there any harm? Like the preceding three function declarations, it is no problem to declare them twice in the program. For functions with External Linkage, the Declaration multiple times also represents the same function. Repeated header file inclusion has the following problems:

First, the preprocessing speed is slowed down, and many header files that do not need to be processed.

Second, if there is foo. h contains bar. h, bar. h also contains foo. in the case of h, the pre-processor will be in an endless loop (in fact, the compiler will set a maximum number of layers ).

Third, some code in the header file cannot be repeated. Although variables and functions can be declared multiple times (as long as they are not defined multiple times ), however, some code in the header file is not allowed to appear many times, such as the typedef type definition and struct Tag definition. It can only appear once in a program file.

There is another problem. Since we want to # include the header file, we should simply include "stack. c" in main. c. In this way, the stack. c and main. c are merged into the same program file, which is equivalent to returning to the first example 12.1 "printing in inverted order with stacks. Of course, this can also be compiled, but it cannot be done in a large project. What should I do if another foo. c uses the stack. c module? If. c # include "stack. c ", which is equivalent to the push, pop, is_empty functions in main. c and foo. c is defined, so main. c and foo. c cannot be linked together. If the header file is included, the three functions are defined only once in stack. c. You can link main. c, stack. c, AND foo. c together. As shown in:

Figure 20.2. Why include the header file instead of the. c file

 



Similarly, variables and function declarations in header files cannot be defined. If the variables or function definitions appear in the header file and the header file is contained by multiple. c files, these. c files cannot be linked together.

2.3. Detailed rules for definition and Declaration
The definitions and declarations in the above two sections only introduce the most basic rules. It is enough to master these basic rules when writing code. However, there are still many complicated rules for definition and declaration in C language, you need to understand these rules when analyzing the cause of the error or maintaining a large project. The two tables in this section are from [Standard C].

First, let's look at the function declaration rules.

Table 20.1. Role of the Storage Class keyword on function declaration

Storage Class File Scope Declaration Block Scope Declaration
None previous linkage
Can define
Previous linkage
Cannot define
 
Extern previous linkage
Can define
Previous linkage
Cannot define
 
Static internal linkage
Can define
N/

 

Previously we said that the "extern keyword indicates that this identifier has External Linkage" is actually inaccurate, and it should be a Previous Linkage. Previous Linkage is defined as: the identifier of this declaration has what kind of Linkage depends on the Previous declaration, this Previous declaration has the same identifier name, and must be a declaration of the file scope, if the previous declaration is not found in the program file (this declaration is the first declaration), This identifier has External Linkage. For example, the same function is declared twice in the file scope in a program file:

Static int f (void);/* internal linkage */
Extern int f (void);/* previous linkage */The identifier of the extern modifier here has Interanl Linkage instead of External Linkage. From the first two rows in the table above, we can sum up the previously mentioned rule "the function declaration does not add the extern keyword is the same ". The above table also shows that defining functions is allowed in the file scope, and defining functions is not allowed in the block scope, or function definitions cannot be nested. In addition, the block scope does not allow the use of the static keyword to declare a function.

Variable declaration rules are more complex:

Table 20.2. Role of the Storage Class keyword on variable Declaration

Storage Class File Scope Declaration Block Scope Declaration
None external linkage
Static duration
Static initializer
Tentative definition
No linkage
Automatic duration
Dynamic initializer
Definition
 
Extern previous linkage
Static duration
No initializer [*]
Not a definition
Previous linkage
Static duration
No initializer
Not a definition
 
Static internal linkage
Static duration
Static initializer
Tentative definition
No linkage
Static duration
Static initializer
Definition
 

 

Each cell in the Table above is divided into four rows, which respectively describe the link property and lifetime of the variable, and how to initialize the variable and determine whether to calculate the variable definition. The Link Attributes include External Linkage, Internal Linkage, No Linkage, and Previous Linkage. The lifetime includes Static Duration and Automatic Duration. For more information, see the definition in this chapter and the Previous chapter. Static Initializer and Dynamic Initializer can be initialized. The former indicates that only constant expressions can be used in Initializer, and the value of the expressions must be determined during compilation, the latter indicates that any right value expression can be used in Initializer, and the expression value can be calculated at runtime. Whether to calculate the Definition of a variable involves three conditions: Definition (variable Definition), Not a Definition (Not variable Definition), and Tentative Definition (Tentative variable Definition ). What is "tentative variable definition? A variable Declaration has a file scope, does not have the Storage Class keyword modifier, or uses the static keyword modifier. If it has Initializer, the compiler considers it a variable definition, if it does not have Initializer, the compiler tentatively defines it as a variable. If there is a clear definition of this variable in the program file, it is clearly defined. If the program file does not have a clear definition of this variable, use this tentative variable to define [32]. In this case, the variable is initialized as 0. There is an example in [C99:

Int i1 = 1; // definition, external linkage
Static int i2 = 2; // definition, internal linkage
Extern int i3 = 3; // definition, external linkage
Int i4; // tentative definition, external linkage
Static int i5; // tentative definition, internal linkage
Int i1; // valid tentative definition, refers to previous
Int i2; // 6.2.2 renders undefined, linkage disagreement
Int i3; // valid tentative definition, refers to previous
Int i4; // valid tentative definition, refers to previous
Int i5; // 6.2.2 renders undefined, linkage disagreement
Extern int i1; // refers to previous, whose linkage is external
Extern int i2; // refers to previous, whose linkage is internal
Extern int i3; // refers to previous, whose linkage is external
Extern int i4; // refers to previous, whose linkage is external
Extern int i5; // refers to previous, whose linkage is internal variables i2 and i5 are declared as Internal Linkage for the first time, and External Linkage for the second time, which is not allowed, the compiler reports an error. Note that the cells marked with [*] in the above table allow Initializer to declare the extern variable in the File Scope and consider it as a definition. However, gcc will report a warning for such writing, this method should be avoided for compatibility purposes.

 

Related Article

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.