一、搭建环境
1、安装 Linux 系统(虚拟机安装、云服务器)
https://releases.ubuntu.com/bionic/
2、安装 XSHELL、XFTP
https://www.netsarang.com/zh/free-for-home-school/
3、安装 visual studio code
https://code.visualstudio.com/
4、Linux环境配置
安装 VMTools
解压 vmware-tools-distrib 到桌面,进入文件夹后,用命令行打开,并输入下面命令进行安装
sudo ./vmware-install.pl
安装 openssh-server
这里提示出了,依赖的openssh-client包版本高了,安装不了openssh-server
openssh-server : 依赖: openssh-client (= 1:7.6p1-4)
因此需要重新安装这个包,也就是给系统里这个包降级,将如上的版本号跟在安装包后面指定要安装的版本
正在读取软件包列表... 完成
正在分析软件包的依赖关系树
正在读取状态信息... 完成
有一些软件包无法被安装。如果您用的是 unstable 发行版,这也许是
因为系统无法达到您要求的状态造成的。该版本中可能会有一些您需要的软件
包尚未被创建或是它们已被从新到(Incoming)目录移出。
下列信息可能会对解决问题有所帮助:
下列软件包有未满足的依赖关系:
openssh-server : 依赖: openssh-client (= 1:7.6p1-4)
依赖: openssh-sftp-server 但是它将不会被安装
推荐: ssh-import-id 但是它将不会被安装
E: 无法修正错误,因为您要求某些软件包保持现状,就是它们破坏了软件包间的依赖关系
sudo apt-get install openssh-client=1:7.6p1-4
sudo apt install -y openssh-server
安装 net-tools
安装后就可以使用 ifconfig 命令来查看本机IP
sudo apt install net-tools
安装 vim
sudo apt-get purge vim-common
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install vim
5、Visual Studio Code 安装相关插件
Chinese (Simplified) (简体中文) Language
Remote Development
C/C++ Extension Pack
Rainbow Brackets
安装完 Remote Development 插件后需要进行配置
修改为自己 Linux 的相关配置信息
先查看 Ubuntu 的 IP 地址信息
# Read more about SSH config files: https://linux.die.net/man/5/ssh_config
Host Ubuntu18_1
HostName 192.168.182.128
User qykhhr
点击 文件夹样式按钮就能通过 SSH 连接 Linux 服务器
点击后就会打开一个新的 VS code ,提示用户输入 Linux 的密码,输入密码后,就能正常连接 Linux 服务器了
我们在 home 目录下创建 Linux 目录,然后用 VS code 打开这个目录
mkdir /home/qykhhr/Linux
配置 SSH 免密登录
Windows
使用命令在本机 C盘用户目录下生成 ssh 密钥
ssh-keygen -t rsa
Linux
使用命令在 Linux 用户目录下生成 ssh 密钥
ssh-keygen -t rsa
在生成的 .ssh 目录下创建 authorized_keys 文件,将 Windows 下产生的公钥写入到这个文件中
vim authorized_keys
我们再使用VSCode连接 Linux 服务器就不会在提示输入密码了。
二、GCC
1、简介
- 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 -y gcc g++
(版本大于 4.8.5) - 查看版本 gcc/g++ -v/--version
2、安装 GCC、G++
安装g++时遇到了如下问题。
输入sudo apt-get install g++
后:
qykhhr@qykhhr:~/.ssh$ sudo apt install -y gcc
正在读取软件包列表... 完成
正在分析软件包的依赖关系树
正在读取状态信息... 完成
有一些软件包无法被安装。如果您用的是 unstable 发行版,这也许是
因为系统无法达到您要求的状态造成的。该版本中可能会有一些您需要的软件
包尚未被创建或是它们已被从新到(Incoming)目录移出。
下列信息可能会对解决问题有所帮助:
下列软件包有未满足的依赖关系:
gcc : 依赖: gcc-7 (>= 7.3.0-12~) 但是它将不会被安装
推荐: libc6-dev 但是它将不会被安装 或
libc-dev
E: 无法修正错误,因为您要求某些软件包保持现状,就是它们破坏了软件包间的依赖关系。
为了解决这个问题,上网一搜,发现是 Ubuntu 安装的软件包版本高,而所安装软件的依赖包版本低的原因。解决方法:
终端输入:
sudo apt-get install aptitude
sudo aptitude install gcc g++
注意在执行 sudo aptitude install g++
命令时,我们需要第一次选 no ,第二次再选 yes
若此处选择n的话会给你提供其他方案。当然根据自身情况不同,可能需要选择的方案也不同。
此处可以看见gcc已成功降级,g++也安装成功:
3、编译 hello world
先创建要给 test.c 文件,写入以下内容:
#include<stdio.h>
int main()
{
printf("hello world\n");
}
使用下面命令来编译生成一个运行文件
gcc test.c -o app
如果直接使用 gcc test.c
会默认生成 a.out 可执行文件
4、编译语言的发展
5、GCC 工作流程
GCC编译选项 | 说明 |
---|---|
-E | 预处理指定源文件,不进行编译 |
-S | 编译指定源文件,但是不进行汇编 |
-c | 编译、汇编指定的源代码,但是不进行链接 |
-o [file1] [file2] 或者 [file2] -o [file1] | 将文件 file2 编译成可执行文件 file1 |
-I directory | 指定 include 包含文件的搜索目录 |
-g | 在编译的时候,生成调试信息,该程序可以被调试器调试 |
-D | 在程序编译的时候,指定一个宏 |
-W | 不生成任何警告信息 |
测试源代码编程汇编流程
1)、修改 test.c 文件,增加宏和注释的等信息
#include<stdio.h>
#define PI 3.14
int main()
{
// 这是测试代码
int sum = PI + 10;
printf("hello world\n");
}
2)、预处理源文件,不进行编译
预处理 test.c 文件,并且指定生成 test.i 文件
gcc test.c -E -o test.i
查看预处理后的 test.i 文件,此时宏值已经被替换成真正的值,#incldue
的头文件也自动将源文件插入当前文件中。
3)、 编译指定源文件,但是不进行汇编
编译预处理后的文件,生成汇编文件
gcc test.i -S -o test.s
或者
gcc test.i -S(默认生成 test.s 文件)
查看编译后的汇编代码 test.s
4)、汇编指定的源代码,但是不进行链接
将汇编代码通过汇编器转换为二进制代码
gcc test.s -c -o test.o
此时我们查看 test.o 发现已经不能查看。
5)、将二进制代码进行链接成可执行文件
gcc test.o -o test
此时我们可以直接执行 test
./test
测试跳过流程进行编译汇编
1)、跳过预处理流程
通过下面命令,我们可以跳过预处理流程
,直接进行编译操作,生成汇编代码
gcc test.c -S
查看生成的汇编代码,没有变化
2)、跳过预处理、编译流程
执行下面命令可以跳过预处理、编译流程,直接生成可执行文件
gcc test.c
6、gcc 和 g++ 的区别
gcc 编译 C 程序,g++ 编译 C++ 程序。那么 g++ 能编译 C 程序吗?
是可以的,我们可以通过g++ test.c -o test
来编译汇编 C 程序,生成可执行文件。
- gcc 和 g++ 都是 GNU(组织)的一个编译器
- 误区一:gcc 只能编译 C 代码,g++ 只能编译 C++代码。两者都可以编译 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,二者等价。
7、GCC 常用参数选项
GCC编译选项 | 说明 |
---|---|
-E | 预处理指定源文件,不进行编译 |
-S | 编译指定源文件,但是不进行汇编 |
-c | 编译、汇编指定的源代码,但是不进行链接 |
-o [file1] [file2] 或者 [file2] -o [file1] | 将文件 file2 编译成可执行文件 file1 |
-I directory | 指定 include 包含文件的搜索目录 |
-g | 在编译的时候,生成调试信息,该程序可以被调试器调试 |
-D | 在程序编译的时候,指定一个宏 |
-W | 不生成任何警告信息 |
-Wall | 生成所有警告信息 |
-On | n的取值范围:0~3。编译器的优化选项的4个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高 |
-I | 在程序编译的时候,搜索的库的路径 |
-fPIC/fpic | 生成与位置无关的代码 |
-shared | 生成共享目标代码,通常用在建立共享库时 |
-std | 指定 C 方言,如:-std=c99,gcc 默认的方言是 GNU C |
演示:-o [file1] [file2] 或者 [file2] -o [file1]
gcc test.c -o test
gcc -o app test.c
演示:-D 在程序编译的时候,指定一个宏
先创建一个 test.c 文件,内容如下:
#include<stdio.h>
int main()
{
int a = 10;
#ifdef DEBUG
printf("我是一个程序猿,我不会爬树...\n");
#endif
for(int i = 0;i < 3; i++)
{
printf("hello,GCC!!!\n");
}
return 0;
}
使用 gcc 默认编译汇编链接后,生成执行文件,执行这个文件,可以发现并没有打印 printf("我是.......") 内容。
gcc -o test test.c
./test
再次使用 gcc 编译汇编链接,并加上 -DDEBUG 参数后,执行生成的程序,可以发现打印我们定义的输出语句
gcc -o app test.c -DDEBUG
./app
演示:-Wall 生成所有警告信息
gcc test.c -o test -Wall
演示:-On n的取值范围:0~3。编译器的优化选项的4个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高
#include<stdio.h>
int main()
{
int b,c,d,f;
b = 10;
c = b;
d = c;
f = d;
/*
编译器优化后:
int b,c,d,f;
b = 10;
c = 10;
d = 10;
f = 10;
*/
return 0;
}
三、静态库的制作和使用
1、什么是库
- 库文件是计算机上的一类文件,可以简单的把库文件看成一种代码仓库,它提供给使用者一些可以直接拿来用的变量、函数或类。
- 库是特殊的一种程序,编写库的程序和编写一般的程序区别不大,只是库不能单独运行。
- 库文件有两种,静态库和动态库(共享库),区别是:
- 静态库在程序的链接阶段被复制到了程序中;
- 动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用。
- 库的好处
- 代码保密
- 方便部署和分发
2、命名规则
- Linux:libxxx.a
- lib:前缀(固定)
- xxx:库的名字,自己命名即可
- .a:后缀(固定)
- Windows:libxxx.lib
3、静态库的制作
-
gcc 获得 .o 文件
-
将多个 .o 文件打包,使用 ar 工具(archive)
ar rcs libxxx.a xxx.o yyy.o zzz.o
- r:将文件插入备存文件中
- c:建立备存文件
- s:索引
生成一个计算器的库
首先准备6个文件
add.c
#include <stdio.h>
#include "head.h"
int add(int a, int b)
{
return a+b;
}
sub.c
#include <stdio.h>
#include "head.h"
int subtract(int a, int b)
{
return a-b;
}
mult.c
#include <stdio.h>
#include "head.h"
int multiply(int a, int b)
{
return a*b;
}
div.c
#include <stdio.h>
#include "head.h"
double divide(int a, int b)
{
return (double)a/b;
}
head.h
#ifndef _HEAD_H
#define _HEAD_H
// 加法
int add(int a, int b);
// 减法
int subtract(int a, int b);
// 乘法
int multiply(int a, int b);
// 除法
double divide(int a, int b);
#endif
main.c
#include <stdio.h>
#include "head.h"
int main()
{
int a = 20;
int b = 12;
printf("a = %d, b = %d\n", a, b);
printf("a + b = %d\n", add(a, b));
printf("a - b = %d\n", subtract(a, b));
printf("a * b = %d\n", multiply(a, b));
printf("a / b = %f\n", divide(a, b));
return 0;
}
-C 编译、汇编指定的源代码,但是不进行链接
gcc -c add.c div.c mult.c sub.c
创建静态库
ar rcs libcalc.a add.o div.o mult.o sub.o
4、静态库的使用
创建 lesson05 文件夹,然后将 calc、library 文件夹复制到 lesson05 文件夹下
mkdir lesson05
cp -r calc/ library/ ../lesson05
回顾静态库的制作过程:
将生成的 libcalc.a 拷贝到 library/lib 目录下
cp /home/qykhhr/Linux/lesson05/calc/libcalc.a /home/qykhhr/Linux/lesson05/library/lib/
使用静态库需要知道静态库的头文件(./include/head.h),src 目录下是源文件,不参与编译过程,我们使用libcalc.a
静态库来实现加减乘除运算。
编译 main.c
#include <stdio.h>
#include "head.h"
int main()
{
int a = 20;
int b = 12;
printf("a = %d, b = %d\n", a, b);
printf("a + b = %d\n", add(a, b));
printf("a - b = %d\n", subtract(a, b));
printf("a * b = %d\n", multiply(a, b));
printf("a / b = %f\n", divide(a, b));
return 0;
}
直接将 main.c 进行编译
找不到 head.h
文件,没有指定头文件路径,默认是当前文件的目录下找head.h
,没有找到这个文件。
gcc main.c -o app
-I 在程序编译的时候,搜索的库的路径
-I 路径
-I ./include/
指定搜索库的路径,指定在./include/(当前目录下的 include 目录下)
找head.h
文件。
但是没有指定使用哪个静态库
gcc main.c -o app -I ./include/
指定要加载的库
-l 库名称
-l calc
(加载 libcalc.a 静态库)
指定要加载的库,但是找不到 这个库,没有指定库路径。
gcc main.c -o app -I ./include/ -l calc
指定要加载库的路径
-L路径
-L./lib
gcc main.c -o app -I ./include/ -l calc -L./lib
5、回顾整个静态库的制作与使用过程
1、静态库的制作
cd src/
gcc -c add.c sub.c div.c mult.c -I ../include/
ar rcs libsuanshu.a add.o sub.o div.o mult.o
2、静态库的使用
先将静态库复制到 lib 目录下
cp libsuanshu.a ../lib/
gcc -o main main.c -I ./include/ -l suanshu -L ./lib
./main
四、动态库(共享库)的制作和使用
1、动态库(共享库)的制作
1)、命名规则
- Linux:libxxx.so(libxxx.so 库文件名称,xxx 库名称)
- lib:前缀(固定)
- xxx:库的名字,自己定义即可
- .so:后缀(固定)
- 在Linux下是一个可执行文件
- Windows:libxxx.dll
2)、动态库(共享库)的制作
-
gcc 得到 .o 文件,得到和位置无关的代码
gcc -c -fpic/-fPIC a.c b.c
-fpic 用于编译阶段,产生的代码没有绝对地址,全部用相对地址,这正好满足了共享库的要求,共享库被加载时地址不是固定的。如果不加-fpic ,那么生成的代码就会与位置有关,当进程使用该.so文件时都需要重定位,且会产生成该文件的副本,每个副本都不同,不同点取决于该文件代码段与数据段所映射内存的位置。
-
gcc 得到动态库(共享库)
gcc -shared a.o b.o -o libcalc.so
创建 lesson06 ,将上次代码复制到当前目录下
进入 calc 目录,执行下面命令生成汇编文件
cd calc
gcc -c -fpic add.c div.c mult.c sub.c
生成动态库(共享库)
gcc -shared add.o sub.o div.o mult.o -o libcalc.so
2、动态库的使用
将生成的动态库拷贝到我们需要使用的目录下的 lib 目录
cp ../calc/libcalc.so ./lib/
生成 main 程序的过程和静态库使用相同
但是在执行生成的程序时出现了错误:./main: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory
2、动态库加载失败的原因
动态库的工作原理
-
静态库:GCC 进行链接时,会把静态库中代码打包到可执行程序中
-
动态库:GCC 进行链接时,动态库的代码不会被打包到可执行程序中
-
程序启动之后,动态库会被动态加载到内存中,通过 ldd(list dynamic dependencies)命令检查动态库依赖关系
-
如何定位共享库文件呢?
当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系统的动态载入器(/lib64/ld-linux-x86-64.so.2)来获取该绝对路径。对于 elf 格式的可执行程序,是由 ld-linux.so 来完成的,它先后搜索 elf 文件的
DT_RPATH段 -> 环境变量 LD_LIBRARY_PATH -> /etc/ld.so.cache 文件列表
->/lib/,/usr/lib
目录找到库文件后将其载入内存。
将动态库所在路径加入到 env 环境变量中
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/qykhhr/Linux/lesson06/library/lib
// 通过下面命令可以输出我们刚设置的环境变量值
echo $LD_LIBRARY_PATH
此时在执行ldd main
命令,就可以看到依赖的动态库libcalc.so
可以正常被找到了
执行程序
配置完环境变量后,我们就可以正常执行程序了
qykhhr@qykhhr:~/Linux/lesson06/library$ ./main
a = 20, b = 12
a + b = 32
a - b = 8
a * b = 240
a / b = 1.666667
但是新开一个窗口,程序又不能运行。我们需要将环境变量进行用户级别配置或者系统级别配置。
解决方式一:配置用户级别的环境变量
进入用户目录,编辑.bashrc
vim .bashrc
// shift + G 直接跳转到最后一行
// 在最后一行加入下面内容,然后保存退出
// source .bashrc 重新生效
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/qykhhr/Linux/lesson06/library/lib
source .bashrc
或者 source .bashrc
使修改的配置生效
此时再次执行ldd main
就会找到 libcalc.so 动态库了,执行 main 程序也成功输出。
解决方式二:配置系统级别的环境变量
sudo vim /etc/profile
// 在最后一行加上
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/qykhhr/Linux/lesson06/library/lib
解决方式三:配置 /etc/ld.so.cache 文件
这个文件是二进制文件不能直接进行修改,我们需要修改 /etc/ld.so.conf 文件
sudo vim /etc/ld.so.conf
// 末尾加上下面内容
/home/qykhhr/Linux/lesson06/library/lib
// 更新
sudo ldconfig
解决方式四:将生成的动态库放在 /lib/,/usr/lib 目录下(不推荐)
将生成的动态库放进这两个目录,程序在使用动态库时就会自动加载这连个目录下的动态库文件。
但是不推荐这样使用,因为这两个目录下有系统使用的动态库,如果动态库名称冲突,就会有问题。
五、静态库和动态库的对比
1、程序编译成可执行程序的过程
2、静态库制作过程
3、动态库制作过程
4、静态库的优缺点
优点
静态库被打包到应用程序中加载速度快
发布程序无需提供静态库,移植方便
缺点
消耗系统资源,浪费内存
更新、部署、发布麻烦
5、动态库的优缺点
优点
可以实现进程间资源共享(共享库)
更新、部署、发布简单
可以控制何时加载动态库
缺点
加载速度比静态库慢
发布程序时需要提供依赖的动态库
六、Makefile
1、什么是 Makefile
- 一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,Makefile 文件定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 Makefile 文件就像一个 Shell 脚本一样,也可以执行操作系统的命令。
- Makefile 带来的好处就是“自动化编译”,一旦写好,只需要一个 make 命令,整个工程完全自动编译,极大的提高了软件开发的效率。make 是一个命令工具,是一个解释 Makefile 文件中指令的命令工具,一般来说,大多数的 IDE 都有这个命令,比如 Delphi 的 make,Visual C++ 的 nmake,Linux 下的 GNU 的 make。
redis的Makefile文件内容
2、Makefile 文件命名和规则
文件命名
makefile 或者 Makefile
Makefile 规则
-
一个 Makefile 文件中可以有一个或者多个规则
目标 ... : 依赖 ... 命令(Shell 命令) ...
- 目标:最终要生成的文件(伪目标除外)
- 依赖:生成的目标所需要的文件或是目标
- 命令:通过执行命令对依赖操作生成目标(命令前必须 Tab 缩进)
-
Makefile 中的其他规则一般都是为第一条规则服务的。
测试 Makefile
在 lesson07 目录下创建 Makefile 文件,内容如下:
app:sub.c add.c mult.c div.c main.c
gcc sub.c mult.c div.c add.c main.c -o app
保存退出后,我们执行make
,如果没有 make ,需要使用sudo apt install -y make
命令来安装 make 。
3、工作原理
命令在执行之前,需要先检查规则中的依赖是否存在
- 如果存在,执行命令
- 如果不存在,向下检查其他的规则,检查有没有一个规则是用来生成这个依赖的,如果找到了,则执行该规则中的命令
测试:检查有没有一个规则是用来生成这个依赖的,如果找到了,则执行该规则中的命令
首先会检查 app:xxx.c xxx.c
中声明的对应的 .c 文件是否存在,如果存在就执行下面的 gcc 编译汇编命令。
我们修改 Makefile 文件,内容如下:
app:sub.o add.o mult.o div.o main.o
gcc sub.o mult.o div.o add.o main.o -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
在执行第一行命令时,会发现没有 sub.o 等文件,于是会先向下执行,发现有生成 sub.o 等文件的命令,就会先执行生成 sub.o 等文件的命令,最后在执行生成 app 的命令。Makefile 中的其他规则一般都是为第一条规则服务的。
Makefile 默认执行第一条规则,如果下面的规则和第一条规则没有关系就不会执行。
测试:如果下面的规则和第一条规则没有关系就不会执行
首先将 add.c 复制一份名为 b.c 文件
cp add.c b.c
然后修改 Makefile 文件内容
增加:
b.o:b.c
gcc -c b.c -o b.o
删除上次生成的执行文件和所有 *.o 文件后,再次执行make
rm -rf *.o
make
会发现除了 b.o 文件没有生成,剩余的 .o 文件都会生成。
检测更新,在执行规则中的命令时,会比较目标和依赖文件的时间
- 如果依赖的时间比目标的时间晚,需要重新生成目标(即由 .c 文件生成的 .o 文件的时间比生成它的 .c 文件还早,说明此时 .c 文件已经被修改过,需要重新生成目标)
- 如果依赖的时间比目标的时间早,目标不需要更新,对应规则中的命令不需要被执行
执行一次make
命令后,立刻再次执行make
就会提示"xxx"已是最新
执行一次make
命令后,修改 main.c 文件(加一个空行),再次执行make
命令。
可以发现,gcc 只是将修改的 main.c 文件重新编译汇编,其他文件不会再次编译,最后再重新链接成一个新的可执行程序。所以我们第二次编写的 Makefile 更好。
4、变量
自定义变量
变量名=变量值
var=hello
获取我们定义的这个变量,使用 $(var)
即可获取变量值。
预定义变量
AR:归档维护程序的名称,默认值为 ar
CC:C 编译器的名称,默认值为 gcc
CXX:C++ 编译器的名称,默认值为 g++
$@:目标的完整名称
$<:第一个依赖文件的名称
$^:所有的依赖文件
$(CC) -c $^ -o $@
获取变量的值
$(变量名)
测试变量使用1
app:main.c a.c b.c
gcc -c main.c a.c b.c
自动变量只能在规则的命令中使用
app:main.c a.c b.c
$(CC) -c $^ -o $@
- $^:代表 main.c a.c b.c
- $@:代表要生成的可执行程序 app
测试变量使用2
使用变量前的 Makefile 文件内容:
app:sub.o add.o mult.o div.o main.o
gcc sub.o mult.o div.o add.o main.o -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
使用变量后的 Makefile 文件内容:
# 定义变量
src=sub.o add.o mult.o div.o main.o
target=app
$(target):$(src)
$(CC) $(src) -o $(target)
sub.o:sub.c
gcc -c sub.c -o sub.o
add.o:add.c
gcc -c add.c -o add.o
b.o:b.c
gcc -c b.c -o b.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
5、模式匹配
# 定义变量
src=sub.o add.o mult.o div.o main.o
target=app
$(target):$(src)
$(CC) $(src) -o $(target)
sub.o:sub.c
gcc -c sub.c -o sub.o
add.o:add.c
gcc -c add.c -o add.o
b.o:b.c
gcc -c b.c -o b.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
# 定义变量
src=sub.o add.o mult.o div.o main.o
target=app
$(target):$(src)
$(CC) $(src) -o $(target)
%.o:%.c
$(CC) -c $< -o $@
- %:通配符,匹配一个字符串
- $<:代表第一个依赖文件的名称,即
%.c
这个文件 - $@:代表要生成的目标文件,即
%.o
这个文件
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
使用函数获取参数前的 Makefile 文件内容:
# 定义变量
src=sub.o add.o mult.o div.o main.o
target=app
$(target):$(src)
$(CC) $(src) -o $(target)
%.o:%.c
$(CC) -c $< -o $@
使用函数获取参数后的 Makefile 文件内容:
# 获取当前目录下所有的.c文件
src=$(wildcard ./*.c)
# 返回src变量中的所有.c文件名,并且将返回的文件名后缀改为.o
objs=$(patsubst %.c,%.o, $(src))
target=app
$(target):$(objs)
$(CC) $(objs) -o $(target)
%.o:%.c
$(CC) -c $< -o $@
src=$(wildcard ./*.c)
:获取当前 Makefile 目录下的所有 .c 文件并赋值到 src 变量中- src=add.c mult.c div.c sub.c main.c
objs=$(patsubst %.c,%.o, $(src))
:将所有输入的后缀为 .c 文件转为 .o 文件名并赋值给 objs 变量。- objs=add.o mult.o div.o sub.o main.o
最后我们是不需要生成的 .o 文件,我们可以在 Makefile 中声明在程序编译汇编链接生成后,将所有的 .o 文件删除的命令。
# 定义变量
src=$(wildcard ./*.c)
objs=$(patsubst %.c,%.o, $(src))
target=app
$(target):$(objs)
$(CC) $(objs) -o $(target)
%.o:%.c
$(CC) -c $< -o $@
clean:
rm $(objs) -f
执行完 make 后,我们在执行 make clean
既可以执行删除全部 .o 文件的命令
如果我们在 Makefile 目录下创建 clean 文件,再次执行 make clean
就会出现
原因是:clean在执行时没有源文件,所以每次执行make clean
,目标文件都是最新的,都会执行这个规则下的命令,但是我们创建一个 clean 文件,这个 clean 文件默认就是clean
这个目标的默认源文件。
clean:
rm $(objs) -f
我们可以在 Makefile 中添加下面内容,来表示这个 clean
是一个伪目标,就不会生成特定文件,就不会和我们创建 clean 文件进行对比。
# 定义变量
src=$(wildcard ./*.c)
objs=$(patsubst %.c,%.o, $(src))
target=app
$(target):$(objs)
$(CC) $(objs) -o $(target)
%.o:%.c
$(CC) -c $< -o $@
.PHONY:clean
clean:
rm $(objs) -f
七、GDB 调试
1、什么是 GDB
GDB 是由 GNU 软件系统社区提供的调试工具,同 GCC 配套组成了一套完整的开发环境,GDB 是 Linux 和 许多类Unix 系统中的标准开发环境。
一般来说,GDB 主要帮助你完成下面四个方面的功能:
- 启动程序,可以按照自定义的要求随心所欲的运行程序
- 可让被调试的程序在所指定的调置的断点处停住(断点可以是条件表达式)
- 当程序被停住时,可以检查此时程序中所发生的事
- 可以改变程序,将一个 BUG 产生的影响修正从而测试其他 BUG
2、准备工作
通常,在为调试而编译时,我们会关掉编译器的优化选项 ('-O') ,并打开调试选项('-g')。另外,'-Wall' 在尽量不影响程序行为的情况下选项打开所有 warning,也可以发现许多问题,避免一些不必要的 BUG。
gcc -g -Wall program.c -o program
'-g' 选项的作用是在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证 gdb 能找到源文件。
如果生成 gdb 程序后,将源文件 test.c 移走,再次执行 GDB 相关操作就会出现找不到文件错误
gcc test.c -o test -g
3、GDB 命令 - 启动、退出、查看代码
gcc test.c -o test -g
启动和退出
gdb 可执行程序
quit
给程序设置参数/获取设置参数
set args 10 20
show args
GDB 使用帮助
help
查看当前文件代码
list/l(从默认位置显示源文件内容)
list/l 行号(从指定的行显示)
list/l 函数名(从指定的函数显示)
查看非当前文件代码
list/l 文件名:行号
list/l 文件名:函数名
需要加上行号或者函数名,否则就会将文件名当成函数名进行处理
g++ bubble.cpp main.cpp select.cpp -o main -g
gdb main
设置显示的行数
设置每次执行 list 后默认显示的条数(默认显示10条数据)
show list/listsize
set list/listsize 行数
4、GDB 命令 - 断点操作
设置断点
b/break 行号
b/break 函数名
b/break 文件名:行号
b/break 文件名:函数
查看断点
i/info b/break
删除断点
d/del/delete 断点编号
设置断点无效
dis/disable 断点编号
设置断点生效
ena/enable 断点编号
设置条件断点(一般用在循环的位置)
b/break 10 if i==5
5、GDB 命令 - 调试命令
运行 GDB 程序
start(程序停在第一行)
run(遇到断点才停)
继续运行,到下一个断点停
c/continue
向下执行一行代码(不会进入函数体)
n/next
变量操作
p/print 变量名(打印变量值)
ptype 变量名(打印变量类型)
向下单步测试(遇到函数进入函数体)
s/step
finish(跳出函数体)
自动变量操作
display 变量名(自动打印指定变量的值)
i/info display
undisplay 编号
其他操作
set var 变量名=变量值(循环中用的较多)
until(跳出循环)
八、文件 IO(I:input,O:output)
1、标准 C 库 IO 函数
标准 C 库 IO 函数操作文件是可以跨平台的。
跨平台有几种实现方式,其中一种是 Java语言,Java语言跨平台的实现方式是:Java程序运行在 JVM 上,而对不同的系统设计不同的 JVM。另一种就是这种,通过调用标准库函数,在不同系统下调用不同系统的 API 去操作。
例如:我们使用标准 C 库 IO 函数fopen
打开文件,在 Windows系统中,就会调用Windows系统的 API 打开文件,在 Linux 系统中就会调用 Linux 系统的 API 打开文件。
我们可以使用 Linux 自带的工具来查看 fopen 函数。
// 标准 C 库函数都在第3章
man 3 fopen
FILE.h中将 _IO_FILE 结构体再次声明为 FILE 结构体。
#ifndef __FILE_defined
#define __FILE_defined 1
struct _IO_FILE;
/* The opaque type of streams. This is the definition used elsewhere. */
typedef struct _IO_FILE FILE;
#endif
我们点击 _IO_FILE 来查看这个结构体内容
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
// 文件读指针
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
// 文件写指针
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
// 缓冲区的起始指针
char* _IO_buf_base; /* Start of reserve area. */
// 缓冲区的结束指针
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
// 文件描述符(整数值)
int _fileno;
// ......
};
2、标准 C 库 IO 和 Linux 系统 IO 的关系
3、虚拟地址空间
4、文件描述符
PCB:Process Control Block
5、Linux 系统 IO 函数
- int open(const char *pathname, int flags);
- int open(const char *pathname, int flags, mode_t mode);
- int close(int fd);
- ssize_t read(int fd, void *buf, size_t count);
- ssize_t write(int fd, const void *buf, size_t count);
- off_t lseek(int fd, off_t offset, int whence);
- int stat(const char *pathname, struct stat *statbuf);
- int lstat(const char *pathname, struct stat *statbuf);
1)、open 打开文件
查看Linux系统函数 - open()
Linux系统函数我们可以使用下面命令进行查看
// Linux系统IO函数在第二章
man 2 open
// 两个头文件包含了 open 函数参数中 flags 定义的宏
#include <sys/types.h>
#include <sys/stat.h>
// 包含了open函数的声明
#include <fcntl.h>
// 打开一个已经存在的文件
int open(const char *pathname, int flags);
参数:
- pathname:要打开的文件路径
- ags:对文件的操作权限设置还有其他的设置
- Linux中可以通过 man 2 open 命令查看 open 函数相关信息,得到:
The argument flags must include one of the following access modes: O_RDONLY, O_WRONLY, or O_RDWR. These request opening the file read-only, write-only, or read/write, respectively.
必须要包含下面权限的一个:O_RDONLY(只读)、O_WRONLY(只写)、O_RDWR(可读可写)
- Linux中可以通过 man 2 open 命令查看 open 函数相关信息,得到:
返回值:
open(), openat(), and creat() return the new file descriptor, or -1 if an error occurred (in which case, errno is set appropriately).
返回一个新的文件描述符,如果调用失败,返回 -1,并将 errno 赋值为合适的值
errno:属于Linux系统函数库,库里面的一个全局变量,记录的是最近的错误号。
标准 C 库中定义了 perror() 函数用来打印 errno 对应的错误描述,我们可以通过 perror() 函数来打印错误信息。
#include <stdio.h>
void perror(const char *s);
参数:s
- 用户描述,比如
perror("hello")
,最终输出的内容是 hello:xxx(xxx是实际的错误描述)
查看Linux系统函数 - close()
man 2 close
Linux系统函数在第二卷,我们可以使用 man 2 close 来查看 close() 函数的相关信息
#include <unistd.h>
int close(int fd);
我们调用 close 函数来关闭文件,需要传入一个文件描述符(file descriptor)。
调用 open 函数打开文件
/*
// 两个头文件包含了 open 函数参数中 flags 定义的宏
#include <sys/types.h>
#include <sys/stat.h>
// 包含了open函数的声明
#include <fcntl.h>
// 打开一个已经存在的文件
int open(const char *pathname, int flags);
参数:
- pathname:要打开的文件路径
- flags:对文件的操作权限设置还有其他的设置
- Linux中可以通过 man 2 open 命令查看 open 函数相关信息,得到:The argument flags must include one of the following access modes: O_RDONLY, O_WRONLY, or O_RDWR. These request opening the file read-only, write-only, or read/write, respectively.
- 必须要包含下面权限的一个:O_RDONLY(只读)、O_WRONLY(只写)、O_RDWR(可读可写)
返回值:
- open(), openat(), and creat() return the new file descriptor, or -1 if an error occurred (in which case, errno is set appropriately).
- 返回一个新的文件描述符,如果调用失败,返回 -1
errno:属于Linux系统函数库,库里面的一个全局变量,记录的是最近的错误号
标准 C 库中定义了 perror() 函数用来打印 errno 对应的错误描述
#include <stdio.h>
void perror(const char *s);
参数:
-s:用户描述,比如 hello,最终输出的内容是 hello:xxx(xxx是实际的错误描述)
// C 语言中是不允许重载的,这里是通过可变参数来实现重载效果的
// 创建一个新的文件
int open(const char *pathname, int flags, mode_t mode);
Linux系统函数在第二卷,我们可以使用 man 2 close 来查看 close() 函数的相关信息
#include <unistd.h>
int close(int fd);
*/
#include <sys/types.h>
#include <sys/stat.h>
// 包含了open函数的声明
#include <fcntl.h>
// 声明了 perror 函数
#include <stdio.h>
// 声明了 close 函数
#include <unistd.h>
int main()
{
// 打开文件,返回一个 file descriptor(文件描述符)
int fd = open("a.txt",O_RDONLY);
if(fd == -1)
{
// 在没有 a.txt 这个文件时,perror函数就会打印:open函数: No such file or directory
perror("open函数"); // open函数: No such file or directory
}
// 读写操作
// 关闭文件,传入一个文件描述符(file descriptor)
close(fd);
return 0;
}
不存在 a.txt 文件时,perror函数就会打印错误信息。
2)、open 创建新文件
int open(const char *pathname, int flags, mode_t mode);
参数:
-
pathname:要创建的文件的路径
-
flags:对文件的操作权限和其他的设置
-
必选项:O_RDONLY、O_WRONLY、O_RDWR 这三个之间是互斥的,只能选择一个
- O_CREATE 文件不存在,创建新文件
- O_APPEND 文件以附加模式打开
- O_ASYNC 启用信号驱动I/O:当此文件描述符上的输入或输出成为可能时,生成一个信号(默认情况下为SIGIO
- ......
-
flags 参数是一个int类型的数据,占4个字节,32位,每一位就是一个标志位。 我们传入参数时,使用按位或的方式添加权限或其他设置,相当于就是给标志位进行合并的操作( O_RDWR | O_CREAT )。
-
-
mode:八进制的数,表示创建出的新的文件的操作权限,比如:0775(0开头是八进制) 最终的文件操作权限是:mode & ~umask (按位与:0和任何数都为0)
-
mode 是我们传入的值
-
umask Linux控制台输入 umask 指令,就会打印当前用户的 umask 值 umask -- 0002,我们需要取反 ~umask = 0777 - 0002 = 0775
0777 ---> 111111111 & 0775 ---> 111111101 ----------------------------- 111111101 按位与:0和任何数都为0
-
umask的作用就是抹去某些权限,让我们设置权限更合理一些,我们可以通过 umask 022 设置 umask 值,也可以通过 mode_t umask(mode_t mask); 函数(man 2 umask)来在程序中指定 umask 值
-
-rwxrwxr-x 1 qykhhr qykhhr 0 9月 25 10:12 create.txt*
- 第一个"-"符号文件的类型,"-"符号代表当前文件是一个普通文件,"d"符号代表当前文件是一个文件夹
- 第一个字串 "rwx" 表示当前用户对这个文件的操作权限
- 'r':表示当前用户这个文件有读取权限
- 'w':表示当前用户对这个文件有写入权限
- 'x';表示当前用户对这个文件有执行权限
- 第二个字串 "rwx" 表示当前用户组中的用户对这个文件的操作权限
- 第三个字串 "r_x" 表示其他用户组中的用户对这个文件的操作权限(没有了写入文件的权限)
/*
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags, mode_t mode);
参数:
- pathname:要创建的文件的路径
- flags:对文件的操作权限和其他的设置
- 必选项:O_RDONLY、O_WRONLY、O_RDWR 这三个之间是互斥的,只能选择一个
- 可选项:
- O_CREATE 文件不存在,创建新文件
- O_APPEND 文件以附加模式打开
- O_ASYNC 启用信号驱动I/O:当此文件描述符上的输入或输出成为可能时,生成一个信号(默认情况下为SIGIO
- ......
- flags 参数是一个int类型的数据,占4个字节,32位,每一位就是一个标志位。
我们传入参数时,使用按位或的方式添加权限或其他设置,相当于就是给标志位进行合并的操作( O_RDWR | O_CREAT )。
- mode:八进制的数,表示创建出的新的文件的操作权限,比如:0775(0开头是八进制)
最终的文件操作权限是:mode & ~umask
- mode 是我们传入的值
- umask Linux控制台输入 umask 指令,就会打印当前用户的 umask 值
umask -- 0002,我们需要取反 ~umask = 0777 - 0002 = 0775
0777 ---> 111111111
& 0775 ---> 111111101
-----------------------------
111111101
按位与:0和任何数都为0
umask的作用就是抹去某些权限,让我们设置权限更合理一些,我们可以通过 umask 022 设置 umask 值,也可以通过 mode_t umask(mode_t mask); 函数(man 2 umask)来在程序中指定 umask 值
-rwxrwxr-x 1 qykhhr qykhhr 0 9月 25 10:12 create.txt*
- 第一个"_"符号文件的类型
- 第一个字串 "rwx" 表示当前用户对这个文件的操作权限
- 'r':表示当前用户这个文件有读取权限
- 'w':表示当前用户对这个文件有写入权限
- 'x';表示当前用户对这个文件有执行权限
- 第二个字串 "rwx" 表示当前用户组中的用户对这个文件的操作权限
- 第三个字串 "r_x" 表示其他用户组中的用户对这个文件的操作权限(没有了写入文件的权限)
*/
// 包含 flags 参数的宏定义,O_RDONLY、ORDWR......
#include <sys/types.h>
#include <sys/stat.h>
// 包含 open 函数声明
#include <fcntl.h>
// 包含 perror 函数声明
#include<stdio.h>
// 包含 close 函数声明
#include <unistd.h>
int main()
{
// -rwxrwxr-x 1 qykhhr qykhhr 0 9月 25 10:12 create.txt*
// 生成的文件权限并不是我们设置的 0777 ,而是 0775,原因就是 mode & ~umask
int fd = open("create.txt",O_RDWR | O_CREAT,0777);
if(fd == -1)
{
perror("open函数");
}
close(fd);
return 0;
}
3)、read、write 函数
uni unix系统 unistd.h unix系统标准头文件
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
参数:
- fd:文件描述符(file descriptor),通过 open 函数得到的,通过这个文件描述符操作某个文件
- buf:需要读取数据存放的地方,数组的地址(传出参数)
- count:指定的数组的最大的字节数大小
返回值
- 成功: 返回值 > 0:返回实际的读取到的字节数 返回值 = 0:文件已经读取完毕
- 失败:-1,并且将 errno 设置为合适的值
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
参数:
- fd:文件描述符,通过 open 函数得到的,通过这个文件描述符操作某个文件
- buf:要往磁盘写入的数据
- count:要写的数据的实际的字节数大小
返回值
- 成功:返回实际写入的字节数
- 失败:返回-1,并将 errno 设置为合适的值
// read、write函数声明
#include <unistd.h>
// perror函数声明
#include<stdio.h>
// open 函数需要的头文件
// 定义一些读写权限的宏
#include <sys/types.h>
#include <sys/stat.h>
// open函数的声明
#include <fcntl.h>
/*
// uni unix系统
// unistd.h unix系统标准头文件
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
参数:
- fd:文件描述符(file descriptor),通过 open 函数得到的,通过这个文件描述符操作某个文件
- buf:需要读取数据存放的地方,数组的地址(传出参数)
- count:指定的数组的最大大小
返回值:
- 成功:
返回值 > 0:返回实际的读取到的字节数
返回值 = 0:文件已经读取完毕
- 失败:-1,并且将 errno 设置为合适的值
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
参数:
- fd:文件描述符,通过 open 函数得到的,通过这个文件描述符操作某个文件
- buf:要往磁盘写入的数据
- count:要写的数据的实际的大小
返回值:
成功:返回实际写入的字节数
失败:返回-1,并将 errno 设置为合适的值
*/
// read、write函数声明
#include <unistd.h>
// perror函数声明
#include<stdio.h>
// open 函数需要的头文件
// 定义一些读写权限的宏
#include <sys/types.h>
#include <sys/stat.h>
// open函数的声明
#include <fcntl.h>
int main()
{
// 1、通过 open 打开 english.txt 文件
int srcfd = open("english.txt",O_RDONLY);
if(srcfd == -1)
{
perror("open函数错误");
return -1;
}
// 2、创建一个新的文件(拷贝文件)
int destfd = open("copy.txt",O_WRONLY | O_CREAT,0664);
if(destfd == -1)
{
perror("open函数错误");
return -1;
}
// 3、频繁的读写操作
char buf[1024] = {0};
int len = 0;
// len 是读取文件后返回的实际buf中的字节数
while((len = read(srcfd,buf,sizeof(buf))) > 0)
{
// 向destfd文件描述符指向的文件写入buf中的数据,buf实际字节数是len长度
write(destfd,buf,len);
}
// 4、关闭文件
close(srcfd);
close(destfd);
return 0;
}
4)、lseek 函数
Linux系统函数中的 lseek 函数(man 2 lseek)
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
参数:
- fd:文件描述符,通过 open 函数得到的,通过这个 fd 操作某个文件
- offset:偏移量
- whence:
- SEEK_SET:设置文件指针的偏移量
- SEEK_CUR:设置偏移量:当前位置 + 第二个参数 offset 的值
- SEEK_END:设置偏移量:文件末尾 + 第二个参数 offset 的值
返回值:返回文件指针的位置
lseek函数的作用:
-
移动文件指针到文件头
lseek(fd, 0, SEEK_SET);
-
获取当前文件指针的位置 lseek(fd, 0, SEEK_CUR);
-
获取文件长度 lseek(fd, 0, SEEK_END);
-
拓展文件的长度,当前文件 10b,lseek(fd, 100, SEEK_END),增加了 100 个字节 lseek(fd, 100, SEEK_END);
平时在下载文件时,会先创建临时文件,占用实际的大小空间,下载过程中再依次替换成自己的数据,防止下载过程中空间不足。
/*
Linux系统函数中的 lseek 函数(man 2 lseek)
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
参数:
- fd:文件描述符,通过 open 函数得到的,通过这个 fd 操作某个文件
- offset:偏移量
- whence:
SEEK_SET
设置文件指针的偏移量
SEEK_CUR
设置偏移量:当前位置 + 第二个参数 offset 的值
SEEK_END
设置偏移量:文件末尾 + 第二个参数 offset 的值
返回值:返回文件指针的位置
作用:
1、移动文件指针到文件头
lseek(fd, 0, SEEK_SET);
2、获取当前文件指针的位置
lseek(fd, 0, SEEK_CUR);
3、获取文件长度
lseek(fd, 0, SEEK_END);
4、拓展文件的长度,当前文件 10b,lseek(fd, 100, SEEK_END),增加了 100 个字节
lseek(fd, 100, SEEK_END);
平时在下载文件时,会先创建临时文件,占用实际的大小空间,下载过程中再依次替换成自己的数据,防止下载过程中空间不足。
标准 C 库中的 lseek 函数(man 3 fseek)
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
*/
// lseek 函数声明
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<stdio.h>
int main()
{
int fd = open("hello.txt",O_RDWR);
if(fd == -1)
{
perror("open函数");
return -1;
}
// 拓展文件长度
int ret = lseek(fd,100,SEEK_END);
if(ret == -1)
{
perror("lseek函数");
return -1;
}
// 扩展完后,我们需要写入数据,否则文件大小还是原来大小
// 写入一个空字符串
write(fd," ",1);
close(fd);
return 0;
}
我们先使用 lseek 函数将 11字节的数据偏移了 100B 大小,但是文件大小没有改变,我们需要在偏移后使用 write 函数写入数据,此时偏移后的文件大小为 112B 大小,多出 1字节 大小,是因为我们写入了一个空字符。
5)、stat、lstat 函数
stat 函数
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
作用:获取一个文件相关的一些信息
参数:
- pathname:操作的文件的路径
- statbuf:结构体变量,传出参数,用于保存获取到的文件的信息
返回值:
- 成功:返回 0
- 失败:返回 -1,并设置合适的 errno
int lstat(const char *pathname, struct stat *statbuf);
参数:
- pathname:操作的文件的路径
- statbuf:结构体变量,传出参数,用于保存获取到的文件的信息
返回值:
- 成功:返回 0
- 失败:返回 -1,并设置合适的 errno
/*
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
作用:获取一个文件相关的一些信息
参数:
- pathname:操作的文件的路径
- statbuf:结构体变量,传出参数,用于保存获取到的文件的信息
返回值:
成功:返回 0
失败:返回 -1,并设置合适的 errno
int fstat(int fd, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
参数:
- pathname:操作的文件的路径
- statbuf:结构体变量,传出参数,用于保存获取到的文件的信息
返回值:
成功:返回 0
失败:返回 -1,并设置合适的 errno
*/
#include<sys/stat.h>
#include<unistd.h>
#include<stdio.h>
int main()
{
// 通过 stat 函数获取文件信息,进而获取文件大小信息
struct stat statbuf;
int ret = stat("a.txt", &statbuf);
if(ret == -1)
{
perror("stat函数");
return -1;
}
printf("size: %ld\n",statbuf.st_size);
return 0;
}
lstat 函数
我们使用命令给 a.txt 文件创建一个软链接,名字为 b.txt
ln -s a.txt b.txt
lstat 函数的作用就是获取软链接文件的信息。
6、stat 结构体
struct stat {
dev_t st_dev; // 文件的设备编号
ino_t st_ino; // 节点
mode_t st_mode; // 文件的类型和存取的权限
nlink_t st_nlink; // 连到该文件的硬连接数目
uid_t st_uid; // 用户 ID
gid_t st_gid; // 组 ID
dev_t st_rdev; // 设备文件的设备编号
off_t st_size; // 文件字节数 文件大小
blksize_t st_blksize; // 块大小
blkcnt_t st_blocks; // 块数
time_t st_atime; // 最后一次访问时间
time_t st_mtime; // 最后一次修改时间
time_t st_ctime; // 最后一次改变时间 指属性
};
7、st_mode 变量(文件的类型和存取的权限)
查看当前文件的类型
S_IFMT 0170000
文件类型和掩码 S_IFMT 进行按位与操作后,后面的位信息全部变为0,文件类型对应4位数据没变,然后再比较前四位的值和8种文件类型的对应的数值进行比较,从而达到判断这个文件类型的目的。
判断一个文件是否有其他组执行的权限
S_IXOTH 00007
我们需要将这个16位的数据和 S_IXOTH 进行按位与操作,如果有执行权限,最后一位就不为 0。
模拟实现 ls -l 命令
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
// 声明了 struct passwd *getpwuid(uid_t uid); 函数,通过 uid 获取用户名
#include <pwd.h>
// 声明了 struct group *getgrgid(gid_t gid); 函数,通过 gid 获取组名称
#include <grp.h>
// ctime() 函数声明
#include<time.h>
// strncpy() 函数声明
#include<string.h>
// 模拟实现 ls -l 指令
// -rw-rw-r-- 1 qykhhr qykhhr 12 9月 25 14:02 a.txt
int main(int argc,char * argv[])
{
printf("%d\n",argc);
// int len = sizeof(*argv);
// for(int i = 0; i < len; i++)
// {
// printf("%s\n",argv[i]);
// }
// 判断输入的参数是否正确
if(argc < 2)
{
printf("%s filename\n",argv[0]);
return -1;
}
// int stat(const char *pathname, struct stat *statbuf);
struct stat st;
int ret = stat(argv[1],&st);
if(ret == -1)
{
perror("stat函数");
return -1;
}
// 获取文件类型和文件权限
char perms[11] = {0}; // 用于保存文件类型和文件权限
switch(st.st_mode & __S_IFMT)
{
case __S_IFLNK:
perms[0] = 'l';
break;
case __S_IFDIR:
perms[0] = 'd';
break;
case __S_IFREG:
perms[0] = '-';
break;
case __S_IFBLK:
perms[0] = 'b';
break;
case __S_IFCHR:
perms[0] = 'c';
break;
case __S_IFSOCK:
perms[0] = 'p';
break;
default:
perms[0] = '?';
break;
}
// 判断文件的访问权限
// 判断文件所有者权限
perms[1] = (st.st_mode & S_IRUSR) ? 'r' : '-';// 判断文件是否有用户读权限
perms[2] = (st.st_mode & S_IWUSR) ? 'w' : '-';// 判断文件是否有用户写权限
perms[3] = (st.st_mode & S_IXUSR) ? 'x' : '-';// 判断文件是否有用户执行权限
// 判断文件所有者所在用户组权限
perms[4] = (st.st_mode & S_IRGRP) ? 'r' : '-';
perms[5] = (st.st_mode & S_IWGRP) ? 'w' : '-';
perms[6] = (st.st_mode & S_IXGRP) ? 'x' : '-';
// 判断其他组权限
perms[7] = (st.st_mode & S_IROTH) ? 'r' : '-';
perms[8] = (st.st_mode & S_IWOTH) ? 'w' : '-';
perms[9] = (st.st_mode & S_IXOTH) ? 'x' : '-';
// 获取硬链接数
int linkNum = st.st_nlink;
// 文件所有者
char * fileUser = getpwuid(st.st_uid)->pw_name;
// 文件所在组
char * filelGrp = getgrgid(st.st_gid)->gr_name;
// 文件大小
long int fileSize = st.st_size;
// 获取修改时间
char * time = ctime(&st.st_mtime);
// 生成的 time 默认会有一个换行,我们需要去除
char mtime[512] = {0};
strncpy(mtime,time,strlen(time) - 1);
// 输出
char buf[1024];
sprintf(buf,"%s %d %s %s %ld %s %s",perms,linkNum,fileUser,filelGrp,fileSize,mtime,argv[1]);
printf("%s\n",buf);
return 0;
}
8、文件属性操作函数
int access(const char *pathname, int mode);
#include <unistd.h>
int access(const char *pathname, int mode);
作用: 判断某个文件是否有某个权限,或者判断文件是否存在
参数:
- pathname:判断的文件路径
- mode:
- R_OK:判断是否有读权限
- W_OK:判断是否有写权限
- X_OK:判断是否有执行权限
- F_OK:判断文件是否存在
返回值:
- 成功:返回 0
- 失败:返回 -1,并将 errno 设置为合适的值
/*
#include <unistd.h>
int access(const char *pathname, int mode);
作用: 判断某个文件是否有某个权限,或者判断文件是否存在
参数:
- pathname:判断的文件路径
- mode:
R_OK:判断是否有读权限
W_OK:判断是否有写权限
X_OK:判断是否有执行权限
F_OK:判断文件是否存在
返回值:
成功:返回 0
失败:返回 -1,并将 errno 设置为合适的值
*/
#include <unistd.h>
#include <stdio.h>
int main()
{
int ret = access("a.txt",F_OK);
if(ret == -1)
{
perror("access函数");
return -1;
}
printf("文件存在!\n");
return 0;
}
int chmod(const char *filename,int mode);
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
作用:修改文件的权限
参数:
- pathname:需要修改的文件的路径
- mode:需要修改的权限值
返回值:成功返回0,失败返回 -1
/*
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
作用:修改文件的权限
参数:
- pathname:需要修改的文件的路径
- mode:需要修改的权限值
返回值:成功返回0,失败返回 -1
*/
#include<sys/stat.h>
#include<stdio.h>
int main()
{
int ret = chmod("a.txt",0755);
if(ret == -1)
{
perror("chmod函数");
return -1;
}
return 0;
}
int chown(const char *path, uid_t owner, gid_t group);
作用:修改文件的所在组
查看 Linux下用户相关信息
vim /etc/passwd
查看用户组信息
vim /etc/group
添加用户,查看用户组信息
sudo useradd hhr
id hhr
int truncate(const char *path, off_t length);
#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
作用:缩减或者扩展文件的尺寸至指定的大小
参数:
- path:需要修改的文件的路径
- length:需要最终文件变成的大小
将文件由12字节扩展到20字节
/*
#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
作用:缩减或者扩展文件的尺寸至指定的大小
参数:
- path:需要修改的文件的路径
- length:需要最终文件变成的大小
*/
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
int main()
{
int ret = truncate("b.txt",20);
if(ret == -1)
{
perror("truncate函数");
return -1;
}
return 0;
}
b.txt 文件内容也由"hello,world",变成了下面内容
将文件由20字节缩减到5字节
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
int main()
{
int ret = truncate("b.txt",5);
if(ret == -1)
{
perror("truncate函数");
return -1;
}
return 0;
}
9、目标操作函数
int mkdir(const char* pathname, mode_t mode);
#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);
作用:创建一个目录
参数:
- pathname:创建的目录的路径
- mode:权限,八进制的数
返回值:成功返回0,失败返回 -1
/*
#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);
作用:创建一个目录
参数:
pathname:创建的目录的路径
mode:权限,八进制的数
返回值:成功返回0,失败返回 -1
*/
#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
int main()
{
int ret = mkdir("aaa",0777);
if(ret == -1)
{
perror("mkdir函数");
return -1;
}
return 0;
}
int rmdir(const char* pathname);
删除文件夹,如果文件夹下有内容需要先删除。
int rename(const char *oldpath, const char *newpath);
#include <stdio.h>
int rename(const char *oldpath, const char *newpath);
作用:重命名文件/文件夹
参数:
- oldpath 修改前的路径
- newpath 修改后的路径
/*
#include <stdio.h>
int rename(const char *oldpath, const char *newpath);
作用:重命名文件/文件夹
参数:
oldpath 修改前的路径
newpath 修改后的路径
*/
#include <stdio.h>
int main()
{
int ret = rename("aaa","bbb");
if(ret == -1)
{
perror("rename函数");
return -1;
}
return 0;
}
int chdir(const char *path);
#include <unistd.h>
int chdir(const char *path);
作用:修改进程的工作目录。比如在 /home/qykhhr 启动了一个可执行程序a.out,进程的工作目录就是 /home/qykhhr
参数:
- path:需要修改的工作目录
char* getcwd(char* buf, size_t size);
#include <unistd.h>
// cwd(current work directory,当前工作路径)
char *getcwd(char *buf, size_t size);
作用:获取当前工作目录
参数:
- buf:存储的路径,指向的是一个数组(传出参数)
- size:数组的大小
返回值:返回的指向的一块内存,这个数据就是第一个参数
获取当前的工作目录,修改当前工作目录,并创建一个新文件
/*
#include <unistd.h>
int chdir(const char *path);
作用:修改进程的工作目录。比如在 /home/qykhhr 启动了一个可执行程序a.out,进程的工作目录就是 /home/qykhhr
参数:
path:需要修改的工作目录
#include <unistd.h>
// cwd(current work directory,当前工作路径)
char *getcwd(char *buf, size_t size);
作用:获取当前工作目录
参数:
buf:存储的路径,指向的是一个数组(传出参数)
size:数组的大小
返回值:返回的指向的一块内存,这个数据就是第一个参数
*/
#include <unistd.h>
#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
int main()
{
// 获取当前的工作目录
char buf[128];
getcwd(buf,sizeof(buf));
printf("当前的工作目录是:%s\n",buf);
// 修改工作目录
int ret = chdir("/home/qykhhr/Linux/lesson13");
if(ret == -1)
{
perror("chdir函数");
return -1;
}
// 创建一个新的文件
int fd = open("chdir.txt",O_CREAT|O_RDWR,0644);
if(fd == -1)
{
perror("open函数");
return -1;
}
close(fd);
// 再次获取当前工作目录
char buf1[128];
getcwd(buf1,sizeof(buf1));
printf("当前的工作目录是:%s\n",buf1);
return 0;
}
10、目录遍历函数
DIR* opendir(const char *name);
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name); 可以通过(man 3 opendir)命令来查看这个指令的用法
作用:打开一个目录
参数:
- name:需要打开的目录的名称
返回值
- DIR * 类型,可以理解为目录流
- 错误返回 NULL
struct dirent *readdir(DIR *dirp);
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
作用:读取目录中的数据
参数:
- dirp:是 opendir 函数返回的结果
返回值:
- struct dirent 代表读取到的文件的信息
- 读取到了末尾或者失败了,返回 NULL
int closedir(DIR* dirp);
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
作用:关闭目录
读取某个目录下所有的额普通文件的个数
/*
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name); 可以通过(man 3 opendir)命令来查看这个指令的用法
作用:打开一个目录
参数:
name:需要打开的目录的名称
返回值
DIR * 类型,可以理解为目录流
错误返回 NULL
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
作用:读取目录中的数据
参数:
dirp:是 opendir 函数返回的结果
返回值:
struct dirent 代表读取到的文件的信息
读取到了末尾或者失败了,返回 NULL
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
作用:关闭目录
*/
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// 用于获取目录下所有普通文件的个数
int getFileNum(const char * path);
// 读取某个目录下所有的额普通文件的个数
int main(int argc,char* argv[])
{
if(argc < 2)
{
printf("%s path\n",argv[0]);
return -1;
}
int num = getFileNum(argv[1]);
printf("普通文件个数为:%d\n",num);
return 0;
}
// 用于获取目录下所有普通文件的个数
int getFileNum(const char * path)
{
// 1、打开目录
DIR * dir = opendir(path);
if(dir == NULL)
{
perror("opendir函数");
exit(0);
}
// 普通文件的数量
int total = 0;
struct dirent* ptr;
while((ptr = readdir(dir)) != NULL)
{
// 获取名称
char* dname = ptr->d_name;
// 忽略掉 . 和 ..
if(strcmp(dname,".") == 0 || strcmp(dname,"..") == 0)
{
continue;
}
// 判断是普通文件还是目录
if(ptr->d_type == DT_DIR)
{
// 目录,需要继续读取这个目录
char newpath[1024];
sprintf(newpath,"%s/%s",path,ptr->d_name);
total += getFileNum(newpath);
}
if(ptr->d_type == DT_REG)
{
// 普通文件
total++;
}
}
// 关闭目录
closedir(dir);
return total;
}
11、dirent 结构体和 d_type
struct dirent
{
// 此目录进入点的 inode
ino_t d_ino;
// 目录文件开头至此目录进入点的位移
off_t d_off;
// d_name 的长度 , 不包含 NULL 字符
unsigned short int d_reclen;
// d_name 所指的文件类型
unsigned char d_type;
// 文件名
char d_name[256];
};
d_type
- DT_BLK - 块设备
- DT_CHR - 字符设备
- DT_DIR - 目录
- DT_LNK - 软连接
- DT_FIFO - 管道
- DT_REG - 普通文件
- DT_SOCK - 套接字
- DT_UNKNOWN - 未知
12、dup、dup2 函数
int dup(int oldfd);
#include <unistd.h>
int dup(int oldfd);
作用:复制一个新的文件描述符,从空闲的文件描述符表中找一个最小的,作为新的拷贝的文件描述符
fd = 3, int fd1 = dup(fd);
其中 fd 指向的是 a.txt,fd1 也是指向a.txt,
/*
#include <unistd.h>
int dup(int oldfd);
作用:复制一个新的文件描述符,从空闲的文件描述符表中找一个最小的,作为新的拷贝的文件描述符
fd = 3, int fd1 = dup(fd);
fd指向的是a.txt,fd1也是指向a.txt,
*/
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
int main()
{
int fd = open("a.txt",O_CREAT | O_RDWR,0777);
int fd1 = dup(fd);
if(fd1 == -1)
{
perror("dup");
return -1;
}
printf("fd = %d, fd1 = %d\n",fd,fd1);
// 关闭文件
close(fd);
char * str = "hello,world";
int ret = write(fd1,str,strlen(str));
if(ret == -1)
{
perror("write函数");
return -1;
}
close(fd1);
return 0;
}
int dup2(int oldfd, int newfd);
#include <unistd.h>
int dup2(int oldfd, int newfd);
作用:重定向文件描述符
oldfd 指向 a.txt,newfd 指向 b.txt
调用函数成功后:newfd 和 b.txt 没有关联,newfd 指向了 a.txt
oldfd 必须是一个有效的文件描述符
oldfd 和 newfd 值相同,相当于什么都没做
/*
#include <unistd.h>
int dup2(int oldfd, int newfd);
作用:重定向文件描述符
oldfd 指向 a.txt,newfd 指向 b.txt
调用函数成功后:newfd 和 b.txt 没有关联,newfd 指向了 a.txt
oldfd 必须是一个有效的文件描述符
oldfd 和 newfd 值相同,相当于什么都没做
*/
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
int main()
{
int fd = open("1.txt",O_RDWR | O_CREAT, 0664);
if(fd == -1)
{
perror("open函数");
return -1;
}
int fd1 = open("2.txt",O_RDWR | O_CREAT,0664);
if(fd1 == -1)
{
perror("open函数");
return -1;
}
printf("fd = %d, fd1 = %d\n",fd,fd1);
int fd2 = dup2(fd,fd1);// 返回的fd2和fd1值相同
if(fd2 == -1)
{
perror("dup2函数");
return -1;
}
// 通过 fd1 操作数据,实际操作是1.txt,而不是2.txt
char * str = "hello,world\n";
int len = write(fd1,str,strlen(str));
if(len == -1)
{
perror("write函数");
return -1;
}
printf("fd = %d, fd1 = %d, fd2 = %d\n",fd,fd1,fd2);
close(fd);
// 因为 fd1 和 fd2 相同,所有我们只需要关闭一个即可
close(fd1);
return 0;
}
13、fcntl 函数
int fcntl(int fd, int cmd, .../* arg */);
- 复制文件描述符,
- 设置/获取文件标志
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... );
参数:
- fd:表示需要操作的文件描述符
- cmd:表示对文件描述符进行如何操作
- F_DUPFD:复制文件描述符,复制到第一个参数fd,得到一个新的文件描述符(返回值) int ret = fcntl(fd,F_DUPFD)l
- F_GETFL:获取指定的文件描述符文件状态flag 获取的flag和我们通过open函数传递的flag是一个东西
- F_SETFL:设置文件描述符文件状态flag 必选项:O_RDONLY,O_WRONLY,O_RDWR 不可以被修改 可选项:O_APPEND。O_ASYNC,O_NONBLOCK O_APPEND 表示追加数据 O_NONBLOCK 设置成非阻塞
阻塞和非阻塞:描述的是函数调用的行为。
/*
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... );
参数:
fd:表示需要操作的文件描述符
cmd:表示对文件描述符进行如何操作
- F_DUPFD:复制文件描述符,复制到第一个参数fd,得到一个新的文件描述符(返回值)
int ret = fcntl(fd,F_DUPFD)l
- F_GETFL:获取指定的文件描述符文件状态flag
获取的flag和我们通过open函数传递的flag是一个东西
- F_SETFL:设置文件描述符文件状态flag
必选项:O_RDONLY,O_WRONLY,O_RDWR 不可以被修改
可选项:O_APPEND。O_ASYNC,O_NONBLOCK
O_APPEND 表示追加数据
O_NONBLOCK 设置成非阻塞
阻塞和非阻塞:描述的是函数调用的行为。
*/
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
int main()
{
// 1、复制文件描述符
// int fd = open("1.txt",O_RDONLY);
// int copyfd = fcntl(fd,F_DUPFD);
// 2、修改或者获取文件状态 flag
int fd = open("1.txt",O_RDWR);
if(fd == -1)
{
perror("open函数");
return -1;
}
// 获取文件描述符状态 flag
// 直接加上O_APPEND这个参数会丢失之前的文件状态,我们需要先获取文件状态然后和O_APPEND进行按位或
int flag = fcntl(fd, F_GETFL);
if(flag == -1)
{
perror("fcntl函数");
return -1;
}
flag |= O_APPEND; // flag = falg | O_APPEND;
// 修改文件描述符的flag,给flag加入O_APPEND 这个标记,直接加上O_APPEND这个参数会丢失之前的文件状态,我们需要先获取文件状态然后和O_APPEND进行按位或
int ret = fcntl(fd,F_SETFL,flag);
if(ret == -1)
{
perror("fcntl函数");
return -1;
}
char * str = "world\n";
int len = write(fd,str,strlen(str));
if(len == -1)
{
perror("write函数");
return -1;
}
close(fd);
close(ret);
return 0;
}
评论