操作系统实践02—gcc使用
文章目录
1.创建GCC环境
这里我使用的是Docker Hub上的一个GCC镜像,拉取到本地并创建相应的容器,命令如下。
$ docker pull gcc
$ docker run -it --name gcc gcc
该容器中没有安装vim编辑器,需要手动安装,安装命令如下。
$ apt-get update
$ apt-get install vim
2.基本用法
2.1编译文件
格式如下。
gcc [选项] [文件]
gcc -o outfile file
:将file输出名为outfile的可执行文件。缺省情况下,gcc输出的可执行文件名为a.out。
指令用法例子:
gcc test.c
:编译test.c,生成文件名为a.out的可执行文件。
gcc -o test test.c
:编译test.c,生成文件名为test的可执行文件。
实践如下:
# 创建test.c文件,里面是写好的代码
root@3fb104e6cb43:/jobs# vi test.c
# 查看代码
root@3fb104e6cb43:/jobs# cat test.c
#include<stdio.h>
int main(){
puts("Hello World!");
return 0;
}
# gcc将test.c编译成可执行文件a.out
root@3fb104e6cb43:/jobs# cc test.c
# 当前目录下,存在文件a.out
root@3fb104e6cb43:/jobs# ls
a.out test.c
# 尽管a.out在当前目录下,但是系统提示没有找到命令
root@3fb104e6cb43:/jobs# a.out
bash: a.out: command not found
# 需要在命令名前加上当前目录./,才可以运行当前目录下的命令,可以看到程序成功执行
root@3fb104e6cb43:/jobs# ./a.out
Hello World!
# gcc将test.c编译成可执行文件test
root@3fb104e6cb43:/jobs# cc -o test test.c
# 当前目录下,存在文件test
root@3fb104e6cb43:/jobs# ls
a.out test test.c
# 执行程序
root@3fb104e6cb43:/jobs# ./test
Hello World!
root@3fb104e6cb43:/jobs#
2.2 运行程序
程序的绝对路径和名称:
例如,系统程序cat位于/bin
目录(该目录存放了很多指令)。程序cat的绝对路径是/bin/cat。程序cat的名称是cat。
在命令行中运行程序时,需要输入程序绝对路径或名称:
- 输入可执行程序的绝对路径
/bin/ls /home
。 - 可执行程序所在目录在
PATH环境变量
中,输入可执行程序的名称ls /home
,此时在功能上ls == /bin/ls
。 - 可执行程序在当前目录下,输入可执行程序的名称。比如
./test
在当前目录下存在可执行程序test;仅仅输入test,系统会提示找不到程序。
2.3 PATH环境变量
环境变量是Linux中用于设置系统参数的字符串:
- 环境变量
HOME
定义用户的主目录路径 - 环境变量
LANG
定义语言和字符集属性 - 环境变量
PATH
定义可执行程序的搜索目录 - 环境变量名一般为大写
在PATH环境变量中设置系统程序所在目录:
Linux系统中,系统程序位于目录/bin
、/usr/bin
目录下,设置PATH环境变量的值为/bin:/usr/bin
,PATH环境变量中可以保存多个目录,使用:
隔开。输入可执行程序名vi,系统会依次在/bin
、/usr/bin
目录中查找vi,从而定位到vi的绝对路径。
为什么要引入PATH环境变量?
例如程序cat的绝对路径是/bin/ls
,程序vi的绝对路径是/usr/bin/vi
,运行cat或者vi时,都要输入绝对路径,就太麻烦了。希望仅仅输入程序名称就可以运行程序,故引入PATH环境变量方便用户的操作。
实践如下:
# echo指令显示环境变量的值
root@3fb104e6cb43:/# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
root@3fb104e6cb43:/jobs# echo $HOME
/root
# 查看当前目录下的文件
root@3fb104e6cb43:/jobs# ls
a.out test test.c
# 尝试用程序名称运行程序,但由于没有在环境变量PATH中设置当前路径,故无法执行
root@3fb104e6cb43:/jobs# a.out
bash: a.out: command not found
root@3fb104e6cb43:/jobs# pwd
/jobs
root@3fb104e6cb43:/jobs# /jobs/a.out
Hello World!
# 将当前路径加到PATH环境变量中
root@3fb104e6cb43:/jobs# PATH=.:/bin:/usr/bin
# 用程序名称执行成功
root@3fb104e6cb43:/jobs# a.out
Hello World!
# 设置PATH环境变量为空
root@3fb104e6cb43:/jobs# PATH=
# 不能使用程序名称执行
root@3fb104e6cb43:/jobs# ls
bash: ls: No such file or directory
# 使用绝对路径可以执行程序
root@3fb104e6cb43:/jobs# /bin/ls
a.out test test.c
root@3fb104e6cb43:/jobs# PATH=/bin:/usr/bin
root@3fb104e6cb43:/jobs# ls
a.out test test.c
root@3fb104e6cb43:/jobs#
3.环境变量与安全
3.1常见问题
在Linux中,假设可执行程序test位于当前目录下,输入test
执行程序,系统提示找不到程序test;输入./test
执行程序,才可以运行程序,./
代表指定在当前目录下查找程序test。
为什么PATH环境变量中没有包含当前目录?(牵涉到Linux中用户和权限的概念)
3.2人性化设计
Linux中存在看上去不人性化的设计:比如登录输入密码时没有字符回显,缺点是用户在输入密码时以为系统没有响应,优点是隐藏了密码长度的信息
;缺省情况下,PATH环境变量不包含当前目录,比如执行当前目录下的程序时,系统提示找不到程序。
-
Linux主要用于服务器,面向计算机专业用户,更加关注系统安全性。
-
Windows主要用于个人用户,面向计算机非专业用户,更加关注系统的易于使用,强调对新手要友好。
3.3提高安全性
PATH环境变量中的目录必须是可信的,/bin
和/usr/bin
是系统目录,目录下的可执行程序一定是可信、无恶意的。不能将来历不明的目录加入到环境变量中。
举个例子,当前目录下有一个恶意程序,该程序会删除一些重要的文件,且名称为ls伪装成系统程序ls。
-
如果PATH环境变为
/bin:/usr/bin
,PATH环境变量中不包含当前目录,用户输入ls,则执行的程序是系统程序/bin/ls
。 -
如果PATH环境变量为
.:/bin:/usr/bin
,PATH环境变量中包含当前目录,用户输入ls,则执行的程序是恶意程序./ls
。
3.4安全演示说明
Linux中存在两类用户:普通用户和管理员用户。
在Linux中,/etc/hosts
是系统文件,只有管理员才能删除,普通用户的权限不够。如果普通用户创建恶意程序/tmp/ls
,与系统程序/bin/ls
同名,功能为删除/etc/hosts
。
如果将当前目录加入到PATH环境变量中,切换到/tmp/
目录下,使用ls命令查看当前目录。由于当前目录在PATH环境变量中,此时执行的是/tmp/ls
,而不是/bin/ls
,而且执行命令的是管理员,有足够的权限删除系统文件,就会导致无意间删除了系统文件。
# 查看/tmp/ls的内容,其中rm -f /etc/hosts指令用于删除/etc/hosts中的数据
$ cat /tmp/ls
#!/bin/sh
echo "现在执行的是恶意程序ls,它伪装成系统程序/bin/ls"
echo "rm -f /etc/hosts"
rm -f /etc/hosts
# 使用chmod命令赋予ls程序可执行权限
$ chmod +x /tmp/ls
# 用户是普通用户,权限不够
$ /tmp/ls
现在执行的是恶意程序ls,它伪装成系统程序/bin/ls
rm -f /etc/hosts
rm: 无法删除/etc/hosts: 权限不够
# 使用su命令切换到系统管理员用户,管理员用户的提示符是#,普通用户的提示符是$
$ su
密码:
# cd /tmp
# 执行的是系统程序/bin/ls
# ls
ls
# 现在将当前目录加入到PATH环境变量中
# PATH=.:/bin:/usr/bin
# ls
现在执行的是恶意程序ls,它伪装成系统程序/bin/ls
rm -f /etc/hosts
# 已经被删除
# cat /etc/hosts
cat: /etc/hosts: 没有那个文件或目录
4.编译与链接
编译器的工作过程,假设项目源程序由多个文件构成
-
首先,将每个源文件分别编译成目标文件。
-
然后,将多个目标文件链接形成可执行文件。
编译:
-
cc -c file1.c
:将源文件file1.c编译成目标文件file1.o -
cc -c file2.c
:将源文件file2.c编译成目标文件file2.o
链接:
cc -o file file1.o file2.o
:将目标文件file1.o、file2.o链接成可执行文件file。
快捷操作:
- 使用编译、链接两步操作,生成可执行文件。
cc -c file1.c
cc -c file2.c
cc -o file file1.o file2.o
- 也可以将编译、链接两步操作合并为一步。
cc -o file file1.c file2.c
# 查看文件
root@3fb104e6cb43:/jobs# cat math.c
int min(int a, int b)
{
if(a<b)
return a;
else
return b;
}
int max(int a, int b)
{
if(a>b)
return a;
else
return b;
}
root@3fb104e6cb43:/jobs# cat math.h
extern int max(int a,int b);
extern int min(int a,int b);
root@3fb104e6cb43:/jobs# cat main.c
#include<stdio.h>
#include<math.h>
int main()
{
printf("min=%d\n",min(1,2));
printf("max=%d\n",max(1,2));
return 0;
}
# 在当前目录下有3个源文件程序
root@3fb104e6cb43:/jobs# ls
main.c math.c math.h
# gcc将math.c编译成目标文件math.o
root@3fb104e6cb43:/jobs# cc -c math.c
root@3fb104e6cb43:/jobs# ls
main.c math.c math.h math.o
# gcc将main.c编译成目标文件main.o
root@3fb104e6cb43:/jobs# cc -c main.c
main.c: In function 'main':
main.c:6:27: warning: implicit declaration of function 'min'; did you mean 'main'? [-Wimplicit-function-declaration]
6 | printf("min=%d\n",min(1,2));
| ^~~
| main
main.c:7:27: warning: implicit declaration of function 'max'; did you mean 'fmax'? [-Wimplicit-function-declaration]
7 | printf("max=%d\n",max(1,2));
| ^~~
| fmax
root@3fb104e6cb43:/jobs# ls
main.c main.o math.c math.h math.o
# gcc将main.o和math.o链接成可执行文件exe
root@3fb104e6cb43:/jobs# cc -o exe main.o math.o
root@3fb104e6cb43:/jobs# ls
exe main.c main.o math.c math.h math.o
# 运行可执行文件exe
root@3fb104e6cb43:/jobs# ./exe
min=1
max=2
root@3fb104e6cb43:/jobs#
5.与库链接
5.1常见函数库
基本函数库,库的名称为libc
(简称为c),包含有最基础的函数,分为若干类:
-
文件读写:fopen、fprintf、fread、fwrite、fclose
-
字符串操作:strcpy、strlen、strcat、sprintf
-
进程管理:fork、exec、wait、exit
数学运算库,库的名称为libm
(简称为m),用于数学计算的函数,分为若各类:
-
三角函数:sin、cos、asin、acos
-
指数运算:log、log2、log10
线程管理库,库的名称为libpthread
(简称为pthread),用于管理线程的函数,分为若各类:
-
线程创建:pthread_create、pthread_exit
-
线程同步:pthread_cond_wait、pthread_cond_signal
5.2链接
格式如下:
gcc -l库的简称 文件
注意:
使用库的简称而不是库的全称。
- 正确用法:
cc -lm test.c
- 错误用法:
cc -llibm test.c
-l
和库的简称之间没有空格。
- 正确用法:
cc -lm test.c
- 错误用法:
cc -l m test.c
# 查看文件
root@3fb104e6cb43:/jobs# cat math.c
#include<stdio.h>
#include<math.h>
int main()
{
printf("%1f\n",cos(0));
return 0;
}
# 缺少简称的编译和链接,能够链接并编译成功
root@3fb104e6cb43:/jobs# cc -o math1 math.c
root@3fb104e6cb43:/jobs# ls
math.c math1
root@3fb104e6cb43:/jobs# ./math1
1.000000
# 加上简称的编译和链接
root@3fb104e6cb43:/jobs# cc -o math2 -lm math.c
root@3fb104e6cb43:/jobs# ls
math.c math1 math2
root@3fb104e6cb43:/jobs# ./math2
1.000000
在Docker的gcc容器中,缺少简称参数也可以链接编译成功(可能gcc优化了),但还是推荐有简称的写法。
5.3Makefile
编写Makefile的基本格式如下。
object:dependency
action
以4.编译
的代码为例,编写Makefile文件,内容如下。
# action前面是tab键,而不是空格
exe:main.o print.o
cc -o exe main.o print.o
main.o:main.c
cc -c main.c
print.o:print.c
cc -c print.c
clean:
rm -f exe *.o
# 不带参数默认编译最终程序,即Makefile的第一行的目标程序exe,也可以指定编译目标make == make exe
root@3fb104e6cb43:/jobs# make
cc -c main.c
cc -c print.c
cc -o exe main.o print.o
root@3fb104e6cb43:/jobs# ./exe
Hello World!
# 更改main.c文件内容
root@3fb104e6cb43:/jobs# vi main.c
# 重新编译,可以看到只编译更新后的文件
root@3fb104e6cb43:/jobs# make exe
cc -c main.c
cc -o exe main.o print.o
root@3fb104e6cb43:/jobs# ./exe
Hello World!
Hello World!