当前位置: 首页 > news >正文

linux系统编程入门

一、搭建环境

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_1HostName 192.168.182.128User 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生成所有警告信息
-Onn的取值范围: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 DEBUGprintf("我是一个程序猿,我不会爬树...\n");
#endiffor(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.cgcc 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.ogcc sub.o mult.o div.o add.o main.o -o appsub.o:sub.cgcc -c sub.c -o  sub.o
add.o:add.cgcc -c add.c -o add.o
mult.o:mult.cgcc -c mult.c -o mult.o
div.o:div.cgcc -c div.c -o div.o
main.o:main.cgcc -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.cgcc -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.cgcc -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.ogcc sub.o mult.o div.o add.o main.o -o appsub.o:sub.cgcc -c sub.c -o  sub.o
add.o:add.cgcc -c add.c -o add.o
mult.o:mult.cgcc -c mult.c -o mult.o
div.o:div.cgcc -c div.c -o div.o
main.o:main.cgcc -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.cgcc -c sub.c -o  sub.o
add.o:add.cgcc -c add.c -o add.o
b.o:b.cgcc -c b.c -o b.o
mult.o:mult.cgcc -c mult.c -o mult.o
div.o:div.cgcc -c div.c -o div.o
main.o:main.cgcc -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.cgcc -c sub.c -o  sub.o
add.o:add.cgcc -c add.c -o add.o
b.o:b.cgcc -c b.c -o b.o
mult.o:mult.cgcc -c mult.c -o mult.o
div.o:div.cgcc -c div.c -o div.o
main.o:main.cgcc -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 主要帮助你完成下面四个方面的功能:

  1. 启动程序,可以按照自定义的要求随心所欲的运行程序
  2. 可让被调试的程序在所指定的调置的断点处停住(断点可以是条件表达式)
  3. 当程序被停住时,可以检查此时程序中所发生的事
  4. 可以改变程序,将一个 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 1struct _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(可读可写)

返回值:

  • 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 directoryperror("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 = 07750777  ---> 111111111&   0775  ---> 111111101-----------------------------111111101按位与:0和任何数都为0umask的作用就是抹去某些权限,让我们设置权限更合理一些,我们可以通过 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函数的作用:

  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);

平时在下载文件时,会先创建临时文件,占用实际的大小空间,下载过程中再依次替换成自己的数据,防止下载过程中空间不足。

/*
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; // 用户 IDgid_t st_gid; // 组 IDdev_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
{// 此目录进入点的 inodeino_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.txtoldfd 必须是一个有效的文件描述符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.txtchar * 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_NONBLOCKO_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、修改或者获取文件状态 flagint 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;
}

在这里插入图片描述

相关文章:

linux系统编程入门

一、搭建环境 1、安装 Linux 系统&#xff08;虚拟机安装、云服务器&#xff09; 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、Linu…...

JS代码安全防护常见的方式

文章目录1. 常量的混淆1.1 十六进制字符串1.2 unicode字符串1.3 字符串的ASCII码混淆1.4 字符串常量加密1.5 数值常量加密2. 增加逆向分析难度2.1 数组混淆2.2 数组乱序2.3 花指令2.4 jsfuck3. 代码执行流程的防护3.1 流程平坦化3.2 逗号表达式4. 其他代码防护方案4.1 eval加密…...

PHP(13)HTTP协议

PHP&#xff08;13&#xff09;HTTP协议一、HTTP请求1. 请求行2. 请求头3. 请求体二、HTTP响应1. 响应行2. 响应头三、设置HTTP响应四、模拟HTTP请求一、HTTP请求 1. 请求行 请求行独占一行。形式&#xff1a;请求方式 资源路径 协议版本号 GET /index.php HTTP/1.1 2. 请求…...

基于支持向量机 (SVM) 用php实现预测气温

Windows 10自带的天气应用有一个基于历史数据预测气温的功能&#xff0c;有一定的参考价值。那么如何去实现这一功能呢&#xff1f;本文采用php进行实现。 使用机器学习方法实现预测当日气温的算法需要涵盖许多的步骤&#xff0c;以下是一种基于支持向量机 (SVM) 的算法的简化…...

MySQL(五)

通过索引进行优化 索引基本知识 索引的优点 1、大大减少了服务器需要扫描的数据量2、帮助服务器避免排序和临时表3、将随机io变成顺序io 索引的用处 1、快速查找匹配WHERE子句的行2、从consideration中消除行,如果可以在多个索引之间进行选择&#xff0c;mysql通常会使用找到…...

Linux常用命令2

目录1.查找find&#xff08;1&#xff09;普通用法&#xff08;2&#xff09;组合用法2.xargs命令3.管道符4.查看文件内容(1)查看两个文件的差别&#xff1a;diff file1 fille2(2)正序查看文件内容cat(3)倒序查看文件内容tac(4)分页查看文件内容more(5)分页查看文件内容less(6)…...

『C/C++养成计划』Visual Studio Code编辑器配置(外观通用型扩展Minmal)

Visual Studio Code编辑器配置(外观&通用型扩展&Minmal)! 文章目录 一. vscode配置外观|通用型扩展1.1. 色彩主题配置扩展(GitHub Theme)1.2. 图标主题扩展(Material Icon Theme)1.3. 代码高亮扩展(better-comments)1.4. 错误警告扩展(error lens)1.5. 执行代码扩展(c…...

设计模式(适配器模式)

设计模式&#xff08;适配器模式&#xff09; 第二章 设计模式之适配器模式&#xff08;Adapter&#xff09; 一、Adapter模式介绍 适配器模式位于实际情况和需求之间&#xff0c;填补两者之间的差距。 二、示例程序1&#xff08;使用继承的适配器&#xff09; 1.示例程序示…...

在基于全志D1s的芒果派麻雀上运行国产开源rt-smart系统

想必RT-Thread系统大家不陌生了&#xff0c;RT-Thread Smart&#xff08;简称 rt-smart&#xff09;是基于 RT-Thread 操作系统衍生的新分支&#xff0c;面向带 MMU&#xff0c;中高端应用的芯片&#xff0c;例如 ARM Cortex-A 系列芯片&#xff0c;MIPS 芯片&#xff0c;带 MM…...

【代码随想录训练营】【Day15】第六章|二叉树|层序遍历|226.翻转二叉树|101.对称二叉树

层序遍历 题目详细&#xff1a;LeetCode.102 层序遍历与上一节讲的三种遍历方式有所不同&#xff0c;层序遍历是指按从上到下&#xff0c;从左到右的顺序&#xff0c;逐层地遍历二叉树的节点。 从其节点的遍历顺序上观察&#xff0c;我们可以发现其跟广度优先遍历&#xff0…...

基于圆展开自适应三边测量算法的室内定位

基于圆展开自适应三边测量算法的室内定位 具有无线通信功能的移动设备的日益普及刺激了室内定位服务的增长。室内定位用于实时定位设备位置&#xff0c;方便访问。然而&#xff0c;由于大量障碍物&#xff0c;与室外定位相比&#xff0c;室内定位具有挑战性。全球定位系统非常适…...

使用中断子系统实现对LED灯的控制

中断顶半部&#xff1a;不允许耗时操作 代码流程&#xff1a; 1、基于字符设备驱动的注册&#xff08;手动/自动&#xff09; 2、基于设备树文件的自定义完成(myled, myirq) 2、基于GPIO子系统实现led的点亮&#xff08;流水/测试文件控制&#xff09; 3、中断子系统操作流程 …...

《爆肝整理》保姆级系列教程python接口自动化(十五)--参数关联接口(详解)

简介 我们用自动化新建任务之后&#xff0c;要想接着对这个新建任务操作&#xff0c;那就需要用参数关联了&#xff0c;新建任务之后会有一个任务的Jenkins-Crumb&#xff0c;获取到这个Jenkins-Crumb&#xff0c;就可以通过传这个任务Jenkins-Crumb继续操作这个新建的任务。 …...

【JDK8】MyBatis源码导入Idea

1.背景 为了更好的将MyBatis的开发设计思想带到日常开发工作&#xff0c;将MyBatis源码导入到本地开发工具中(idea)。我自己在导入的时候碰到几个问题&#xff0c;耽误了自己一点时间&#xff0c;这里我把它们记下来&#xff0c;后边的小伙伴可不要踩我的坑。 Java版本&#x…...

三层交换机DHCP中继

关于中继&#xff0c;我们需要有一个对比。正常情况下我们是不是需要配置单臂路由然后开启DHCP地址池&#xff0c;然就设置网段网关以及DNS。这样的话考验 的其实是命令功底。但是为了方便&#xff0c;我们 可以添加服务器&#xff0c;将这个服务给到服务器去配置&#xff0c;这…...

C++之RALL机制

RALL是Resource acquisition is initialization的缩写&#xff0c;意思是“资源获取即初始化”&#xff0c;其核心思想是利用C对象生命周期的概念来控制程序的资源。它的技术原理很简单&#xff0c;如果希望对某个重要资源进行跟踪&#xff0c;那么创建一个对象&#xff0c;并将…...

回溯算法章末总结

组合问题的特点 &#xff08;1&#xff09;abba 选中a之后&#xff0c;就不再选了 &#xff08;2&#xff09;找出所有的组合 &#xff08;长度可以不相等&#xff09; 组合问题模板 做回溯题步骤 &#xff08;0&#xff09;判断问题类型 &#xff08;1&#xff09;树状图 …...

【SpringBoot】为异步任务规划线程池

一、线程池的作用 防止资源占用无限的扩张调用过程省去资源的创建和销毁所占用的时间 在上一节中&#xff0c;我们的一个异步任务打开了一个线程&#xff0c;完成后销毁。在高并发环境下&#xff0c;不断的分配新资源&#xff0c;可能导致系统资源耗尽。所以为了避免这个问题…...

SAP ABAP 输出结果带有空格

方法一&#xff1a; 字段内容前增加空格&#xff0c;需使用全角空格&#xff0c;使用半角空格时&#xff0c;ALV显示无效&#xff0c;空格无法显示&#xff0c; 全角与半角的切换方法&#xff1a;shift空格切换&#xff0c; 如下的标记部分&#xff0c;要想通过ALV显示空格&…...

Opengl ES之踩坑记

前因 最近在尝试使用Opengl ES实现一些LUT滤镜效果&#xff0c;在实现这些滤镜效果的时候遇到一些兼容性的坑&#xff0c;踩过这些坑后我希望把这几个坑分享给读者朋友们&#xff0c; 希望同在学习Opengl ES的朋友们能少走弯路。 关于LUT滤镜相关的介绍&#xff0c;也是这个O…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现

目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

TDengine 快速体验(Docker 镜像方式)

简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能&#xff0c;本节首先介绍如何通过 Docker 快速体验 TDengine&#xff0c;然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker&#xff0c;请使用 安装包的方式快…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销&#xff0c;平衡网络负载&#xff0c;延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:

在 HarmonyOS 应用开发中&#xff0c;手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力&#xff0c;既支持点击、长按、拖拽等基础单一手势的精细控制&#xff0c;也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档&#xff0c…...

STM32标准库-DMA直接存储器存取

文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA&#xff08;Direct Memory Access&#xff09;直接存储器存取 DMA可以提供外设…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享

文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的&#xff0c;根据Excel列的需求预估的工时直接打骨折&#xff0c;不要问我为什么&#xff0c;主要…...

vue3+vite项目中使用.env文件环境变量方法

vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量&#xff0c;这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...

Java多线程实现之Thread类深度解析

Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度

文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...

JavaScript基础-API 和 Web API

在学习JavaScript的过程中&#xff0c;理解API&#xff08;应用程序接口&#xff09;和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能&#xff0c;使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...