变量和函数的定义和声明
$ gcc -c main.c -Wallmain.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’这个问题我们在第 2 节 “自定义函数”讨论过,由于编译器在处理函数调用代码时没有找到函数原型,只好根据函数调用代码做隐式声明,把这三个函数声明为:
int push(char);int pop(void);int is_empty(void);
现在你应该比学第 2 节 “自定义函数”的时候更容易理解这条规则了。为什么编译器在处理函数调用代码时需要有函数原型?因为必须知道参数的类型和个数以及返回值的类型才知道生成什么样的指令。为什么隐式声明靠不住呢?因为隐式声明是从函数调用代码推导而来的,而事实上函数定义的形参类型可能跟函数调用代码传的实参类型并不一致,如果函数定义带有可变参数(例如/* 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;}这样编译器就不会报警告了。在这里/* foo.c */static void foo(void) {}/* main.c */void foo(void);int main(void) { foo(); return 0; }编译链接在一起会出错:
$ 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
虽然在/* 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;}变量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;}注意,变量声明和函数声明有一点不同,函数声明的/* 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;}这样,即使在2.2. 头文件 我们继续前面关于/* stack.h */#ifndef STACK_H#define STACK_Hextern void push(char);extern char pop(void);extern int is_empty(void);#endif这样在/* 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;}首先说为什么$ tree.|-- main.c|-- stack.c`-- stack.h0 directories, 3 files则可以用$ tree.|-- main.c`-- stack |-- stack.c `-- stack.h1 directory, 3 files则需要用...#include "stack.h"#include "stack.h"int main(void){...则第一次包含...#define STACK_Hextern void push(char);extern char pop(void);extern int is_empty(void);#include "stack.h"int main(void){...其中已经定义了#include "stack.h"#include "foo.h"然而图 20.2. 为什么要包含头文件而不是
2.3. 定义和声明的详细规则 以上两节关于定义和声明只介绍了最基本的规则,在写代码时掌握这些基本规则就够用了,但其实C语言关于定义和声明还有很多复杂的规则,在分析错误原因或者维护规模较大的项目时需要了解这些规则。本节的两个表格出自[Standard C]。
首先看关于函数声明的规则。
表 20.1. Storage Class关键字对函数声明的作用
Storage Class File Scope Declaration Block Scope Declaration nonestatic int f(void); /* internal linkage */extern int f(void); /* previous linkage */则这里的表 20.2. Storage Class关键字对变量声明的作用
Storage Class File Scope Declaration Block Scope Declaration noneint i1 = 1; // definition, external linkagestatic int i2 = 2; // definition, internal linkageextern int i3 = 3; // definition, external linkageint i4; // tentative definition, external linkagestatic int i5; // tentative definition, internal linkageint i1; // valid tentative definition, refers to previousint i2; // 6.2.2 renders undefined, linkage disagreementint i3; // valid tentative definition, refers to previousint i4; // valid tentative definition, refers to previousint i5; // 6.2.2 renders undefined, linkage disagreementextern int i1; // refers to previous, whose linkage is externalextern int i2; // refers to previous, whose linkage is internalextern int i3; // refers to previous, whose linkage is externalextern int i4; // refers to previous, whose linkage is externalextern int i5; // refers to previous, whose linkage is internal变量i2和i5第一次声明为Internal Linkage,第二次又声明为External Linkage,这是不允许的,编译器会报错。注意上表中标有[*]的单元格,对于文件作用域的extern变量声明,C99是允许带Initializer的,并且认为它是一个定义,但是gcc对于这种写法会报警告,为了兼容性应避免这种写法。