1 Linux系统编程入门
1.1Linux开发环境搭建
我使用的是阿里云2核2G的服务器1年108元
- 设置服务器root密码,重启服务器
- root用户登录,进行添加新用户
- 注册自己使用的用户
sudo useradd -r -m -s /bin/bash tset #tset是用户名
其中参数的意义如下:
- -r:建立系统账号
- -m:自动建立用户的登入目录
- -s:指定用户登入后所使用的shell
- 设置密码
sudo passwd tset
- 输入
ls /home
可以看到以及创建成功
设置用户相关教程链接:ubuntu添加用户 - 修改用户权限
sudo chmod +w /etc/sudoers #添加写入的权限
sudo vim /etc/sudoers #修改文件
#修改vim中的权限,保存退出:wq
sudo chmod -w /etc/sudoers #移除写入的权限
- 创建组并且添加用户
groupadd learngroup
usermod -g learngroup mobbu
id mobbu #查看是否添加成功
-
使用vscode远程连接扩展,搜索remote安装,连接到服务器。
-
在本机创建密钥
ssh-keygen -t rsa
,默认回车即可,在用户账户可以查找到.ssh文件,找到id_rsa.pub文件(公钥文件),然后在服务器端使用同样命令创建密钥,cd .ssh
,再修改文件vim authorized_keys
,将本机的公钥打开复制粘贴到此文件中保存退出即可。此时环境配置完成。
1.2 GCC
1.2.1 什么是GCC
- GCC 原名为 GNU C语言编译器(GNU C Compiler)
- GCC(GNU Compiler Collection,GNU编译器套件)是由 GNU 开发的编程语言译器。GNU 编译器套件包括 C、C++、Objective-C、Java、Ada 和 Go 语言前端,也包括了这些语言的库(如 libstdc++,libgcj等)
- GCC 不仅支持 C 的许多“方言”,也可以区别不同的 C 语言标准;可以使用命令行选项来控制编译器在翻译源代码时应该遵循哪个 C 标准。例如,当使用命令行参数
-std=c99
启动 GCC 时,编译器支持 C99 标准。 - 安装命令 sudo apt install gcc g++ (版本 > 4.8.5)
- 查看版本 gcc/g++ -v/--version
CTRL + L
清空命令行
1.2.2 编程语言发展
- 高级语言->汇编语言->机器语言->计算机
1.2.3 GCC工作流程
- 预处理:头文件展开、宏替换、删除注释
1.2.4 gcc 和 g++ 的区别
- gcc 和 g++都是GNU(组织)的一个编译器。
- 误区一:gcc 只能编译 c 代码,g++ 只能编译 c++ 代码。两者都可以,请注意:
- 后缀为 .c 的,gcc 把它当作是 C 程序,而 g++ 当作是 c++ 程序
- 后缀为 .cpp 的,两者都会认为是 C++ 程序,C++ 的语法规则更加严谨一些
- 编译阶段,g++ 会调用 gcc,对于 C++ 代码,两者是等价的,但是因为 gcc命令不能自动和 C++ 程序使用的库联接,所以通常用 g++ 来完成链接,为了统一起见,干脆编译/链接统统用 g++ 了,这就给人一种错觉,好像 cpp 程序只能用 g++ 似的
- 误区二:gcc 不会定义 __cplusplus 宏,而 g++ 会
- 实际上,这个宏只是标志着编译器将会把代码按 C 还是 C++ 语法来解释
- 如上所述,如果后缀为 .c,并且采用 gcc 编译器,则该宏就是未定义的,否则, 就是已定义
- 误区三:编译只能用 gcc,链接只能用 g++
- 严格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用gcc/g++,而链接可以用 g++ 或者 gcc -lstdc++。
- gcc 命令不能自动和C++程序使用的库联接,所以通常使用 g++ 来完成联接。但在编译阶段,g++ 会自动调用 gcc,二者等价
1.2.5 GCC常用参数选项
gcc编译选项 | 说明 |
---|---|
-E | 预处理指定的源文件,不进行编译 |
-S | 编译指定的源文件,但是不进行汇编 |
-c | 编译、汇编指定的源文件,但是不进行链接 |
-o [file1] [file2] | 将文件 file2 编译成可执行文件 file1 |
-I directory | 指定 include 包含文件的搜索目录 |
-g | 在编译的时候,生成调试信息,该程序可以被调试器调试 |
-D | 在程序编译的时候,指定一个宏 |
-w | 不生成任何警告信息 |
-Wall | 生成所有警告信息 |
-On | n的取值范围:0~3。编译器的优化选项的4个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高 |
-l | 在程序编译的时候,指定使用的库 |
-L | 指定编译的时候,搜索的库的路径 |
-fpic | 生成与位置无关的代码 |
-shared | 生成共享目标文件,通常用在建立共享库时 |
-std | 指定C方言,如:-std=c99,gcc默认的方言是GNU C |
例子:
gcc test.c -o app #当前目录下生成test.c的可执行文件app
./app #运行app
gcc test.c -E -o test.i # 预处理,进行头文件展开、宏替换、删除注释
gcc test.i -S -o test.s # 编译,生产test.s汇编代码
gcc test.s -c -o test.o # 汇编,产生test.o机器01代码,可直接运行
-D
#ifdef DEBUG
printf("success\n");
#endif
添加上述代码后,命令行输入gcc test.c -o test -DDEBUG
,此时添加DEBUG宏后,将会执行上述代码,打印success
1.3 静态库的制作和使用
1.3.1 静态库的制作
-
库文件是计算机上的一类文件,可以简单的把库文件看成一种代码仓库,它提供给使用者一些可以直接拿来用的变量、函数或类。
-
库是特殊的一种程序, 编写库的程序和编写一般的程序区别不大,只是库不能单独运行。
-
库文件有两种,静态库和动态库(共享库),区别是:静态库在程序的链接阶段被复制到了程序中;动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用。
-
库的好处:
- 代码保密
- 方便部署和分发
-
命名规则:
- Linux :libxxx.a
- lib :前缀(固定)
- XXX:库的名字,自己起
- .a:后缀(固定)
- Windows : libxxx. lib
- Linux :libxxx.a
-
静态库的制作:
- gcc获得.o文件
- 将.o文件打包,使用ar工具(archive)
ar rcs 1ibxxx.a xxx.o Xxx.o
- r - 将文件插入备存文件中
- c - 建立备存文件
- s - 索引
1.3.2 静态库的使用
- 生产静态库
- 使用静态库
gcc main.c -o app -L lib_address -l lib_name
- main.c为执行文件
- -o 为生成执行文件
- lib_address 库地址
- lib_name 库名字
cd ./calc
gcc -c add.c div.c mult.c sub.c # 生成四个函数的汇编文件
ar rcs libcalc.a add.o div.o mult.o sub.o # 生成对应四个函数的静态库calc
cd ../library
mv ../calc/libcalc.a ./lib
gcc main.c -o app -I ./include/ -l calc -L ./lib/
# 执行main.c 生产执行文件app,头文件地址为./include,使用库calc,库地址为./lib
./app # 执行代码
1.4 动态库的制作和使用
1.4.1 动态库的制作
- 命名规则:
- Linux : libxxx.so
- lib : 前缀(固定)
- xxx : 库的名字,自己起
- .so : 后缀(固定)
在Linux下是一个可执行文件
- Windows : libxxx.dll
- Linux : libxxx.so
- 动态库的制作:
- gcc 得到 .o 文件,得到和位置无关的代码
gcc -c –fpic/-fPIC a.c b.c
- gcc 得到动态库
gcc -shared a.o b.o -o libcalc.so
- gcc 得到 .o 文件,得到和位置无关的代码
1.4.2 动态库加载失败的原因
- 静态库:GCC 进行链接时,会把静态库中代码打包到可执行程序中
- 动态库:GCC 进行链接时,动态库的代码不会被打包到可执行程序中
- 程序启动之后,动态库会被动态加载到内存中(当GCC使用add方法的时候),通过 ldd(list dynamic dependencies)命令检查动态库依赖关系
- 如何定位共享库文件呢?
当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系统的动态载入器来获取该绝对路径。对于elf格式的可执行程序,是由ld-linux.so来完成的,它先后搜索elf文件的 DT_RPATH段 ——> 环境变量LD_LIBRARY_PATH ——> /etc/ld.so.cache文件列表 ——> /lib/,/usr/lib目录找到库文件后将其载入内存。
1.4.3 解决动态库加载失败问题
添加环境变量LD_LIBRARY_PATH文件
-
方法一:在终端输入以下命令,缺点:临时生产,下次打开终端环境变量就没了
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/mobbu/Linux/lesson06/library/lib
其中冒号:之后为动态所在的绝对地址,通过
pwd
命令获得 -
方法二:用户端修改环境变量,永久存在
- 步骤一
cd / #返回根目录 vim .bashrc
-
步骤二
在bashrc文件中,shift+G
跳到最后一行,并且插入export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/mobbu/Linux/lesson06/library/lib
,保存退出后 -
步骤三
. .bashrc # 等于source .bashrc
-
方法三:root环境下修改环境变量,永久存在,需要root权限
sudo vim /etc/profile
插入
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/mobbu/Linux/lesson06/library/lib
,保存退出后
. .bashrc # 等于source .bashrc
1.5 静态库和动态库对比
1.5.1 程序编译成可执行程序的过程
静态库、动态库区别来自链接阶段如何处理,链接成
可执行程序。分别称为静态链接方式和动态链接方式。
1.5.2 静态库制作过程
1.5.3 动态库制作过程
1.5.4 静态库的优缺点
-
优点:
- 静态库被打包到应用程序中加载速度快
- 发布程序无需提供静态库,移植方便
-
缺点:
- 消耗系统资源,浪费内存
- 更新、部署、发布麻烦
1.5.5 动态库的优缺点
- 优点:
- 可以实现进程间资源共享(共享库)
- 更新、部署、发布简单
- 可以控制何时加载动态库
- 缺点:
- 加载速度比静态库慢
- 发布程序时需要提供依赖的动态库
1.6 MakeFile
1.6.1 什么是 Makefile
- 一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,Makefile 文件定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 Makefile 文件就像一个 Shell 脚本一样,也可以执行操作系统的命令。
- Makefile 带来的好处就是“自动化编译” ,一旦写好,只需要一个 make 命令,整个工程完全自动编译,极大的提高了软件开发的效率。make 是一个命令工具,是一个解释 Makefile 文件中指令的命令工具,一般来说,大多数的 IDE 都有这个命令,比如 Delphi 的 make,Visual C++ 的 nmake,Linux 下 GNU 的 make
tar -zxvf redis-5.0.10.tar.gz # 解压到当前文件夹
1.6.2 Makefile文件命名和规则
- 文件命名
makefile 或者 Makefile - Makefile 规则
- 一个 Makefile 文件中可以有一个或者多个规则\
目标 ...: 依赖 ... 命令(Shell 命令) ...
- 目标:最终要生成的文件(伪目标除外)
- 依赖:生成目标所需要的文件或是目标
- 命令:通过执行命令对依赖操作生成目标(命令前必须 Tab 缩进)
- Makefile 中的其它规则一般都是为第一条规则服务的
- 一个 Makefile 文件中可以有一个或者多个规则\
例子:
首先输入vim Makefile
,然后再Makefile文件里添加以下代码
app:sub.c add.c mult.c div.c main.c
gcc sub.c add.c mult.c div.c main.c -o app
最后输入make
指令即可
1.6.3 工作原理
- 命令在执行之前,需要先检查规则中的依赖是否存在
- 如果存在,执行命令
- 如果不存在,向下检查其它的规则,检查有没有一个规则是用来生成这个依赖的,如果找到了,则执行该规则中的命令
- 检测更新,在执行规则中的命令时,会比较目标和依赖文件的时间
- 如果依赖的时间比目标的时间晚,需要重新生成目标
- 如果依赖的时间比目标的时间早,目标不需要更新,对应规则中的命令不需要被执行
1.6.4 变量
- 自定义变量
变量名=变量值 var=hello - 预定义变量
AR : 归档维护程序的名称,默认值为 ar
CC : C 编译器的名称,默认值为 cc
CXX : C++ 编译器的名称,默认值为 g++
$@
: 目标的完整名称
$<
: 第一个依赖文件的名称
$^
: 所有的依赖文件 - 获取变量的值
$(变量名)
\
# define
src = sub.o add.o mult.o div.o main.o
target = app
$(target):$(src)
$(CC) $(src) -o $(app)
sub.o:sub.c
gcc -c sub.c -o sub.o
add.o:add.c
gcc -c add.c -o add.o
mult.o:mult.c
gcc -c mult.c -o mult.o
div.o:div.c
gcc -c div.c -o div.o
main.o:main.c
gcc -c main.c -o main.o
1.6.5 模式匹配
add.o:add.c
gcc -c add.c
div.o:div.c
gcc -c div.c
sub.o:sub.c
gcc -c sub.c
mult.o:mult.c
gcc -c mult.c
main.o:main.c
gcc -c main.c
%.o:%.c
- %: 通配符,匹配一个字符串
- 两个%匹配的是同一个字符串
%.o:%.c
gcc -c $< -o $@
例子:
# define
src = sub.o add.o mult.o div.o main.o
target = app
$(target):$(src)
$(CC) $(src) -o $(app)
%.o:%.c
gcc -c $< -o $@
1.6.6 函数
-
$(wildcard PATTERN...)
- 功能:获取指定目录下指定类型的文件列表
- 参数:PATTERN 指的是某个或多个目录下的对应的某种类型的文件,如果有多个目录,一般使用空格间隔
- 返回:得到的若干个文件的文件列表,文件名之间使用空格间隔
- 示例:
$(wildcard *.c ./sub/*.c)
返回值格式:a.c b.c c.c d.c e.c f.c
-
$(patsubst <pattern>,<replacement>,<text>)
- 功能:查找
<text>
的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合
模式<pattern>
如果匹配的话,则以<replacement>
替换。 <pattern>
可以包括通配符%
,表示任意长度的字串。如果<replacement>
中也包含%
,那么,<replacement>
中的这个%
将是<pattern>
中的那个%所代表的字串。(可以用\
来转义,以\%
来表示真实含义的%
字符)- 返回:函数返回被替换过后的字符串
- 示例:
$(patsubst %.c, %.o, x.c bar.c)
返回值格式:x.o bar.o
- 功能:查找
示例:
# define
# add.c sub.c main.c mult.c div.c
src = $(wildcard ./*.c)
objs = $(patsubst %.c, %.o, $(src))
target = app
$(target):$(objs)
$(CC) $(objs) -o $(target)
%.o:%.c
$(CC) -c $< -o $@
.PHONY:clean
# clean为伪目标,防止外界有clean文件时无法执行make clean命令
clean:
rm $(objs) -f