平时在写C程序的时候都是将所有代码全部写入一个.c文件中,这样写对小程序来说是适合的。但是对于比较大的项目,将所有代码写在一个文件不利于代码的维护。
最近由于项目需求,将一个服务器程序按照功能整理成了几个.c文件和.h文件。该服务器程序包含以下文件:
.
|-- config_system.c
|-- generic_function.c
|-- log_system.c
|-- server.c
`-- server.h
代码整理完毕之后,开始对源文件进行重新编译。在编译的过程中遇到了一些问题,现在总结如下。
1. 自定义头文件的使用
server.h文件包含服务器程序中所有自定义函数原型的声明和一些常量定义,将该自定义函数放在当前程序的目录下即可。自定义头文件的使用方法就和普通头文件的使用方法一样,加在程序最开始的位置即可,不过要将包含头文件的尖括号<>改成双引号“ ”。
通过这个实践,也可以更好的理解包含头文件的两种方法。对于尖括号包含的头文件,gcc在系统默认的目录中(/usr/include)查找相应的头文件;对于双引号包含的头文件,编译器首先会在当前目录下或指定的目录下(如果有指定)去查找头文件,如果当前目录下没有该头文件则去默认的头文件目录下查找。
刚才我们提到了头文件的指定目录,在大型项目中,头文件可能会更多,往往将头文件单独放在一个目录当中,此时应该使用-Idirname选项来告诉编译器到指定的目录dirname中去查找头文件。比如我们将server.h放在head目录中再进行编译:
gcc -Ihead config_system.c server.c log_system.c generic_function.c -o server
选项I代表include,该选项的作用阶段是预处理阶段。
2. 多个源文件的编译
我们可以先将每个源文件编译成目标文件,最后再生成一个可执行文件。比如:
edsionte@edsionte-desktop:~/server$ gcc -c server.c
edsionte@edsionte-desktop:~/server$ gcc -c config_system.c
edsionte@edsionte-desktop:~/server$ gcc -c log_system.c
edsionte@edsionte-desktop:~/server$ gcc -c generic_function.c
edsionte@edsionte-desktop:~/server$ gcc server.o config_system.o log_system.o generic_function.o -o server
也可以直接使用 一条命令:
edsionte@edsionte-desktop:~/server$ gcc server.c generic_function.c log_system.c config_system.c -o server
不管是那种方法都可以编译源文件,最后连接成可执行文件。在第一种方法中,单独使用-c选项只是编译源文件,并不涉及链接的过程,因此源文件是否包含main函数我们并不关心。第二种方法是将编译链接通过一条命令来完成。
其实在使用gcc编译程序时,整个编译过程分为预编译、编译、汇编和链接四个步骤。上述两种方法都自动完成了前两个步骤。
3. extern的使用
按照上面的命令进行编译程序,出现了下面的错误提示:
config_system.c:80: error: ‘config_filename’ undeclared (first use in this function)
这条错误是在说明config_filename这个变量未声明。config_filename作为一个全局变量在generic_function.c文件中已经声明并定义过,那么如何让编译器知道该变量已经定义过并且可以在config_system.c文件中使用?此时extern关键字就派上了用场。
extern的作用是声明一个变量,它的作用仅仅是声明,而不是像定义该变量时那样为其分配内存空间。一个变量在整个程序中只能定义一次,但是却可以声明多次。
4. 使用脚本编译并运行服务器程序
由于将代码按照功能分离后,每次编译都需要很长一串的命令,因此写个脚本就显得很有必要了。下面的脚本先将原有的可执行文件(如果存在的话)删除,再编译程序,最后运行可执行文件。
#!/bin/bash
test server && rm server
gcc server.c config_system.c log_system.c generic_function.c -o server
echo "compliing success!"
echo "sever is running.."
./server
通过这次“分离”代码的实践,给我最大的感受就是平时看似懂的概念只有到真正使用的时候才真的开始理解。