操作系统实践02—gcc使用

操作系统实践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!
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

暄踽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值