本系列文章系本人原创,欢迎转载,转载请注明出处
一、第一个程序: Hello World!
注:这篇文章所建立的所有文件(源文件,库文件,可执行文件等)均可在这里下载。下载的是chapter3.tar.gz文件,可以在Linux系统终端中用如下命令解压到当前目录:
tar zxvf chapter3.tar.gz
我们现在开始编写我们在Linux系统上的第一个程序。我们先建立我们要存放程序的目录:
读者可以根据个人喜好建立相应的目录,下面是笔者演示用的目录(/home/test/LinuxProgrammingBasics/chapter3):
接下来我们就开始编写第一个程序了,这个程序就一个功能:在终端上输出”Hello World!”.
输入下面的命令以打开vim:
vim sayHello.c
进入vim后,按i进入编辑模式,输入下面的代码:
#include <stdio.h>
int main()
{
printf("Hello World!\n");
return 0;
}
然后按ESC键进入命令模式,再按:键进入末行模式,输入wq并回车以保存文件并退出到终端/命令行。
回到终端后,我们可以用ls命令查看到当前目录下已经有一个sayHello.c文件了。
输入下面的命令以产生可执行文件:
gcc -o sayHello sayHello.c
此时,如果可以用ls命令查看到当前目录下有两个文件:
1) 源文件: sayHello.c
2) 可执行文件: sayHello
在终端中输入下面的代码以执行该可执行文件:
./sayHello
这将会产生如下的输出:
至此,我们的第一个程序就编写并执行成功了。
二、关于gcc的简单介绍
1. gcc命令
我们先来看一下我们在上面这个例子中用来生成可执行文件的命令:
gcc -o sayHello sayHello.c
在这个命令中,一目了然的是sayHello.c,这个是我们的源文件,代码就存在着里。那么其他的是什么呢?
gcc: GNU的C语言编译器
-o sayHello: 指定生成的可执行文件的名字是sayHello。有兴趣的读者可以尝试将 “-o sayHello”中的 “sayHello”改成其他的名字试试。如果不使用-o,而直接使用 “gcc 源文件名”生成可执行文件的话,那么默认会生成名字为a.out的可执行文件:
2. 头文件和库文件
我们再看一下代码:
#include <stdio.h>
int main()
{
printf("Hello World!\n");
return 0;
}
头文件
我们可以看到在源代码中我们使用了printf函数,但是在我们的源代码中我们并没有定义printf函数,那么当我们执行gcc命令时,为什么没有提示任何异常呢?
答案是我们在源代码的最开始引入了头文件: stdio.h, printf函数的声明就在这个文件里,也就是说这个文件里规定了printf接受什么样的参数,返回什么类型的值之类的信息。
讲到这,我们发现我们只是在源代码中简单的使用了include命令将stdio.h引用进来了,并没有指出具体位置,并且在执行gcc命令生成可执行文件的时候也没有指明头文件的位置,那么gcc是在什么地方去找这个头文件的呢?
c语言的头文件几乎都位于/usr/include目录及其子目录中。gcc会自动的在这个目录下去找指定的头文件,所以我们在执行gcc的时候不用指定去哪里找stdio.h文件。
那么如果我们自己写了头文件或者需要使用其他的不在这个目录下的头文件的时候该怎么办呢?此时,我们就必须在执行gcc命令的时候自行指定头文件的位置了:
gcc .. -I{头文件所在目录} ...
库文件
回到stdio.h,我们已经知道它的位置了(/usr/include/stdio.h),但是我们打开它的话会发现这里面只有printf函数的声明,函数体并不在这里。也就是说只有只有源代码和头文件,还不能生成可执行文件,因为还缺少printf的函数体(即printf具体会做哪些操作)。这里缺的就是库文件。
库文件就是一组已经编译好的函数的集和。常用的库文件几乎都位于/lib或/usr/lib目录下(也可能是/lib64或/usr/lib64,视情况而定)。printf是标准c语言库的,所以gcc会自动的去到这个目录下找标准c语言库。我们可以通过下面的命令来查看我们的可执行文件用到了哪些库:
ldd sayHello
在笔者的电脑上输出如下:
其中libc.so.6就是标准c语言库,它位于/lib64目录下。
事实上gcc只会去找标准c语言库,那么如果我们需要用到其他的库怎么办呢?这个时候就需要在gcc命令中指定我们用到的库了:
gcc .. -L{库文件所在文件夹} -l{库文件} ..
这里需要注意的是 “-l{库文件}”并不是要传入完整的文件名,例如如果输入以下命令:
gcc .. -L{/home/test/bin} -l{math} ..
则会在 “/home/test/bin”目录下去找库文件 “libmath.a”或 “libmath.so”。
“.a”和 “.so”分别是静态库和共享库,可以简单的理解为如果生成可执行文件的时候用的是静态库,则静态库中用到的内容会全部在可执行文件中,可执行文件较长。如果多个可执行文件均由同一个静态库生成,则这些文件一起执行的时候内存中会有同一函数的多份副本。而如果用共享库生成可执行文件,则可执行文件中只有对用到的部分的引用。如果多个可执行文件均由同一个共享库生成,则这些文件一起执行的时候内存中只有一份副本。
例子
接下来我们把上面打印 “Hello World!”的功能单独提出到一个函数里,建立头文件并创建库,来演示我们上面讲的知识。
1) 建立helloWorld.h头文件,声明sayHello方法,文件内容如下:
void sayHello(void);
2) 建立helloWorld.c源文件,实现sayHello方法:
//因为我们在sayHello方法中用到了printf,所以要引入stdio.h
#include <stdio.h>
void sayHello(void)
{
printf("Hello World!\n");
}
3) 通过helloWorld.c,制作对应的库文件libhello.a。在终端中输入如下命令:
#编译并汇编helloWorld.c,生成目标文件helloWorld.o
gcc -c helloWorld.c
#由目标文件sayHello.c生成库文件libhello.a
ar crv libhello.a helloWorld.o
4) 建立一个源文件prog.c,这个文件里面会用到我们在helloWorld.h中声明的sayHello方法:
//因为我们用到了在helloWorld.h中声明的sayHello方法,所以这里要引入它
#include "helloWorld.h"
int main()
{
sayHello();
}
好了,我们将prog.c生成可执行文件并执行它:
#下面的命令中的-L.中的 "."表示当前目录
gcc -o prog prog.c -L. -lhello
./prog
执行结果如下: