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

政府门户网站群建设模式/手游推广平台哪个好

政府门户网站群建设模式,手游推广平台哪个好,怎么做外汇返佣的网站,清溪东莞网站建设进程间通信 一、初识进程间通信1. 进程间通信概念2. 进程间通信分类 二、管道1. 管道概念2. 管道原理3. 匿名管道4. 匿名管道系统接口5. 管道的特性和情况6. 匿名管道的应用(1)命令行(2)进程池 7. 命名管道(1&#xff…

进程间通信

  • 一、初识进程间通信
    • 1. 进程间通信概念
    • 2. 进程间通信分类
  • 二、管道
    • 1. 管道概念
    • 2. 管道原理
    • 3. 匿名管道
    • 4. 匿名管道系统接口
    • 5. 管道的特性和情况
    • 6. 匿名管道的应用
      • (1)命令行
      • (2)进程池
    • 7. 命名管道
      • (1)命名管道的系统接口
      • (2)理解命名管道
      • (3)使用命名管道
  • 三、初识日志
    • 1. 理解日志
    • 2. 获取时间
    • 3. 可变参数的使用

一、初识进程间通信

1. 进程间通信概念

进程间通信是两个或者多个进程实现数据层面的交换。但是由于进程间存在独立性,所以导致进程间通信的成本比较高。

那么为什么要有进程间通信呢?其中有以下几种目的:

  • 数据传输:一个进程需要将它的数据发送给另一个进程;
  • 资源共享:多个进程之间共享同样的资源;
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程);
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

那么怎么做到进程间通信呢?由于进程之间具有独立性,所以在不打破它们的独立性的前提下,使它们看到同一份“资源”,这就是进程间通信的本质。那么这个“资源”是什么呢?是谁提供的呢?其实就是特定形式的空间,一般由操作系统提供。必定是不能由一个进程提供,假设是由一个进程提供,这个资源就是属于这个进程独有了,破坏了进程的独立性!

所以我们进程访问这个空间,进行通信,本质就是访问操作系统!进程代表就是用户,所以这个“资源”从创建、使用、释放,都是通过系统调用接口!从底层设计,从接口设计,都要由操作系统独立设计,一般操作系统会有一个独立的通信模块,它隶属于文件系统,叫做 IPC通信模块

2. 进程间通信分类

  1. 基于文件级别的通信方式 - - - 管道
  • 匿名管道 pipe
  • 命名管道
  1. System V IPC
  • System V 消息队列
  • System V 共享内存
  • System V 信号量
  1. POSIX IPC
  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

二、管道

1. 管道概念

  • 管道是Unix中最古老的进程间通信的形式;
  • 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。

2. 管道原理

首先我们知道,每一个进程都有自己的文件描述符表,文件描述符表中 0、1、2 默认已经被打开,分别指向键盘、显示器、显示器。如今我们新建一个文件,我们是否能做到该文件不在磁盘中被打开呢?也就是说,我们有该文件的 fdinodefile_operators缓冲区,但是该文件就不在磁盘中,也就是实现真正的内存级文件。答案是可以做到的,而且在操作系统内核中,会存在非常多的内存级文件,而这些文件不在磁盘中真正存在,只要它们能在内存里被我们用起来即可。也就是下图的结构:

在这里插入图片描述

当我们以只读方式打开一个文件时,同时创建一个子进程,操作系统会帮我们将父进程的 task_struct 拷贝给子进程,也就是,文件描述符表也拷贝给子进程了!那么文件描述符表中的内容也和父进程一样,我们知道,文件描述符表其实是数组指针,那么子进程中的文件描述表中的指针也指向了父进程的表中的指向!也就是说父进程和子进程都能看见一样的文件资源!如下图:

在这里插入图片描述

那么如果父进程想向缓冲区里面写入,子进程是不是就可以从缓冲区中读取呢?是的,这样就能实现进程间通信了!所以管道的本质,也叫做文件!因为管道就是文件,只是不是我们理解的磁盘文件,只是内存级文件。

但是,我们上面打开文件的时候是以只读方式打开的,创建子进程时子进程也是只有只读的权限,那么它们之间怎么通信呢,没有办法一个读一个写。所以父进程在打开文件的时候就不能这么随意地打开啦,接下来我们继续理解管道的原理。

所以在系统当中,父进程在打开一个文件的时候,并不是单方面的以读、写方式或者读写方式打开的。它在创建管道时,把同一个文件既以读方式打开,又以写方式打开! 如下图:

在这里插入图片描述

接下来父进程在 fork 创建子进程,子进程它会拷贝父进程的文件描述符表,所以它们都会有对应的读写端指向管道,如下:

在这里插入图片描述

紧接着需要结合具体场景,我们是想让父进程读,子进程写,还是子进程读、父进程写。此时我们就要求父进程或子进程它们各自要关闭对应的读写端,来形成一个单向通信的管道!如下图:

在这里插入图片描述

那么有了上面的初步理解后,我们接下来站在内核的角度再次理解管道的本质。我们继续画图理解,如下,首先我们把同一个文件在同一个进程中再打开一次,在操作系统层面上还是要给它创建一个 struct_file,因为这两个文件的读写方式不一样!其中我们知道,每一个文件里都有自己当前的读写位置,比如我们读写到哪个偏移量,如果我们读写混合用的话,会很容易出问题的。但是这两个文件是指向同一个 inode方法集以及缓冲区

接下来父进程创建子进程,子进程中的文件描述表也就指向了对应的 struct_file;所以这时候就要实现父子进程的单向通信了,所以此时就需要我们用户决定到底是父进程写还是子进程写,那么我们在这就让子进程写入,父进程读取。所以父进程就要根据文件描述符关闭对应的 struct_file 了,子进程也如此,如下图:

在这里插入图片描述

至此,这就是管道通信的原理!正是因为它只能进行单向通信,所以给它命名为管道。

那么我们上面讲解的原理都是通过父进程创建子进程实现的,如果没有任何关系,可以用上面的原理进行通信吗?不可以!必须是父子关系、兄弟关系、爷孙关系…所以管道通信必须具有血缘关系才可以,常用于父子关系。

3. 匿名管道

在这里插入图片描述

我们上面讲的原理中,我们打开的文件有名字吗?有 inode 吗?有路径吗?都没有!因为这个文件不需要有名字,更不需要怎么去标定它,所以我们把这种管道叫做匿名管道,我们把红色框中的整体成为匿名管道。

至此我们还没有进行通信,我们一直都在建立通信信道!我们这么费劲建立就是因为进程具有独立性,通信是有成本的!

4. 匿名管道系统接口

接下来我们认识一下管道的系统接口:pipe

在这里插入图片描述

那么 pipe 中的参数是什么呢?为什么是一个只有两个变量的数组呢?其实它就是输出型参数,它会将文件的文件描述符数字带出来,让用户使用!

那么规定,pipefd[0] 是读端,pipefd[1] 是写端。

我们可以验证一下该结论,如下代码:

				#include <iostream>#include <unistd.h>using namespace std;int pipefd[2] = {0};int main(){int n = pipe(pipefd);cout << "pipefd[0]: " << pipefd[0] << endl << "pipefd[1]: " << pipefd[1] << endl;return 0;}

结果如下,被打开的文件果然是 3 号和 4 号,而3号fd就是读端,4号fd就是写端:

在这里插入图片描述

其中返回值,如果成功返回0,否则返回-1,错误码被设置:

在这里插入图片描述

接下来我们按照上面的原理建立一个管道。首先我们把整体架构搭建出来,代码如下:

				#include <iostream>#include <string>#include <unistd.h>#include <sys/types.h>#include <sys/wait.h>#include <cstring>#include <cstdlib>#include <cstdio>#define NUM 1024using namespace std;int pipefd[2] = {0};int main(){int n = pipe(pipefd);if(n < 0) return -1;// 创建子进程pid_t id = fork();if(id < 0) return -1;// 子进程写入if(id == 0){close(pipefd[0]);Write(pipefd[1]);close(pipefd[1]);exit(0);} // 父进程读取close(pipefd[1]);Read(pipefd[0]);pid_t wid = waitpid(id, nullptr, 0);if(wid < 0) return -1;close(pipefd[0]);return 0;}

接下来子进程进行写入,其中 snprintf 是以字符串格式向 buffer 写入,我们先打印出来观察结果:

				// 子进程写入void Write(int wfd){string str = "hello, world";pid_t myid = getpid();int number = 0;char buffer[NUM];while(true){buffer[0] = 0;snprintf(buffer, sizeof(buffer), "%s - %d - %d", str.c_str(), myid, number++);write(wfd, buffer, strlen(buffer)); // strlen(buffer) 只需要写入长度个字节,如果用sizeof(buffer)就相当于是buffer指针的大小了cout << buffer << endl;sleep(1);}}

在这里插入图片描述

接下来父进程向管道中读取数据,我们也先打印出来观察结果,我们关闭子进程的打印结果,观察父进程的打印结果:

				// 父进程读取void Read(int rfd){char buffer[NUM];while(true){buffer[0] = 0;ssize_t n = read(rfd, buffer, sizeof(buffer));if(n > 0){buffer[n] = 0;cout << "father get message[" << getpid() << "]# " <<   buffer << endl;}else if(n == 0){cout << "read done!" << endl;break;}else break;}}

在这里插入图片描述

如上图,父进程确实向管道中读取到了子进程写入的结果。接下来我们验证一些问题。

  1. 假设我们让子进程在写入前,休眠一段时间,而现在的管道是空的,我们观察父进程会如何:

     			// 子进程写入void Write(int wfd){string str = "hello, world";pid_t myid = getpid();int number = 0;char buffer[NUM];sleep(10);while(true){buffer[0] = 0;snprintf(buffer, sizeof(buffer), "%s - %d - %d", str.c_str(), myid, number++);write(wfd, buffer, strlen(buffer)); // strlen(buffer) 只需要写入长度个字节,如果用sizeof(buffer)就相当于是buffer指针的大小了}}
    

在这里插入图片描述

我们观察到,父进程阻塞了,所以我们得出一个结论,读写端正常,管道如果为空,读端就要阻塞

所以我们也就知道,父子进程是会进程协同的,同步和互斥的,这是为了保护管道文件的数据安全,这个我们后面再讨论。

  1. 下面我们验证如果管道被写满,会发生什么情况

所以我们在父进程中先让父进程休眠一段时间,并在子进程中打印 number,观察写端写满管道后会发生什么情况:

				// 父进程读取void Read(int rfd){char buffer[NUM];while(true){sleep(5);buffer[0] = 0;ssize_t n = read(rfd, buffer, sizeof(buffer));if(n > 0){buffer[n] = 0;cout << "father get message[" << getpid() << "]# " <<   buffer << endl;}else if(n == 0){cout << "read done!" << endl;break;}else break;}}

在这里插入图片描述

如上,我们发现写端也阻塞了!但是五秒后父进程休眠完毕就开始读取数据了:

在这里插入图片描述

所以我们得出结论,读写端正常,管道如果被写满,写端就会阻塞

但是根据以上现象,我们延申出另外一个问题,对于父进程来说,子进程写了多少次根本不重要,只要管道里有数据,有多少就会读多少,前提条件是我们缓冲区足够大。也就是说,当子进程向管道写满了,当父进程在读的时候,就会把多次写的信息一次读了出来,在父进程看来,它读到的就是一个一个的字符,对于我们用户用什么存取,如何区分,这是我们用户的事。所以我们得出一个管道的特点,管道是面向字节流的

当两个进程退出时,文件会被操作系统自动退出,所以管道资源会被自动释放,就像我们的0、1、2号fd文件,我们也从来没有打开和关闭过,这就是操作系统帮我们做的。所以我们又得出管道的一个特点,管道是基于文件的,而文件的生命周期是随进程的

我们上面也看到管道会被写满,那么管道的大小是多少呢?我们可以在系统中查看一下,指令为 ulimit -aulimit 是一条命令,用来查看操作系统对于很多重要资源的限制,如下:

在这里插入图片描述

我们可以看到,8指的是单个进程可以打开文件的个数,大小是512字节,所以管道的大小也就是4KB,我们可以验证一下。我们现在只需要子进程写入,父进程休眠即可,我们让子进程一次写入一个字节,也就是一个字符,写入一次number++一次,所以代码如下:

				void Write(int wfd){string str = "hello, world";pid_t myid = getpid();int number = 0;char buffer[NUM];while(true){char c = 'c';write(wfd, &c, 1); number++;cout << number << endl;}}

结果如下:

在这里插入图片描述

我们可以看到,管道写入了 65536 个字节,也就是 64KB,也就是说在我们的机器下管道的大小是 64KB. 那么我们要说一下了,在不同的内核里,管道的大小是有差异的。我们也可以读一下管道的手册:

在这里插入图片描述

如上,也就是说从 Linux 2.6.11 内核之后管道的大小就变成了 64KB;我们接着看:

在这里插入图片描述

我们可以看到有一个 PIPE_BUF 的东西,其实它就是单次向管道中写入的大小,我们可以看到它的大小是 4KB;上面的手册中提到了原子性的问题,例如,当子进程往管道中写数据时,父进程读数据,当子进程只写了一部分数据,还没有写完,就被父进程读走了,这就导致读取到的数据不完整。那么所以我们要保证,父进程在读的时候要么不读,要么就把完整的数据读取,这就叫做读取的原子性问题。所以管道在保证读取的原子性,它规定 PIPE_BUF 的大小,只要是父进程或子进程读写的单位是小于 PIPE_BUF 的,它们读写的过程就是原子的,也就是说当子进程写入的数据小于 PIPE_BUF,父进程也不会来读取的,这就是 PIPE_BUF 的本质,所以我们在 ulimit 中查到的管道大小我们可以理解成 PIPE_BUF 的大小。

  1. 读端正常读,写端关闭

接下来我们验证另一个问题,当读端正常,写端关闭会出现什么情况。接下来我们让子进程在写的时候,写入10个字节就退出,如下代码:

				// 子进程写入void Write(int wfd){string str = "hello, world";pid_t myid = getpid();int number = 0;char buffer[NUM];while(true){ sleep(1);char c = 'c';write(wfd, &c, 1); // strlen(buffer) + 1???number++;cout << number << endl;if(number >= 10) break;}}

结果如下:

在这里插入图片描述

我们观察到当写端还在写入的时候,读端在正常读,而写端退出后呢?如下:

在这里插入图片描述

read 的返回值返回的是读到的数据大小,以字节为单位,当返回值为0时,父进程也就退出了循环,所以我们得出结论,读端正常读,写端关闭,读端就会读到0,表明读到了文件(pipe)结尾,不会被阻塞

  1. 写端正常,读端关闭

首先我们要知道,操作系统是不会做低效、浪费资源和时间等类似的工作的,如果做了,操作系统就是bug;所以我们想,写端正常,读端关闭后,还有实际意义吗?没有了!因为写满了又怎样呢,又没有进程去读,所以当写端正常,读端关闭了,操作系统就要 kill 掉正在写入的进程。如何 kill 呢?通过信号,其实操作系统会使用13号信号 SIGPIPE kill 掉正在写入的进程:

在这里插入图片描述

5. 管道的特性和情况

所以对上面的现象总结,我们可以分别得到管道的四种情况和物种特性。

  • 管道情况
  1. 读写端正常,管道如果为空,读端就要阻塞;
  2. 读写端正常,管道如果被写满,写端就会阻塞;
  3. 读端正常读,写端关闭,读端就会读到0,表明读到了文件(pipe)结尾,不会被阻塞;
  4. 写端正常写入,读端关闭,操作系统会 kill 掉正在写入的进程。
  • 管道特性
  1. 具有血缘关系的进程才能进行进程间通信;
  2. 管道只能单向通信;
  3. 父子进程是会进程协同的,同步和互斥的,这是为了保护管道文件的数据安全;
  4. 管道是面向字节流的;
  5. 管道是基于文件的,而文件的生命周期是随进程的。

6. 匿名管道的应用

(1)命令行

那么我们上面学的管道,和我们以前学过的哪些有关系呢?首先我们以前接触过 | 这个符号,其实这个就是管道,例如我们在多条指令中使用 |

				cat test.c | head -10 | tail -5

那么它和我们上面学的管道又有什么关系呢?我们知道,每一条指令在命令行运行时都是会创建进程去执行的。下面我们观察现象得出结论:

在这里插入图片描述

如上图,我们使用管道运行 sleep 指令,而在右端终端我们看到,它们最终都会变成进程,它们三个的 pid 是不一样的,而 ppid 是同一个!我们查看一下该相同的 ppid 究竟是什么:

在这里插入图片描述

如图,它就是 bash!所以它们的父进程都是 bash,都是一样的,所以它们是具有血缘关系的进程。所以在我们使用 | 的时候,在上面的语句中,操作系统为我们创建两个管道,因为有两个 |,然后再连续创建三个进程,然后每个进程程序替换执行不同的命令。所以我们使用 | 的原理就是我们上面所说的 pipe

(2)进程池

通过上面的学习,我们可以使用管道实现一个简易的进程池。什么是进程池呢?例如内存池,内存池是提前向操作系统一次性申请一大片内存,供我们用户使用,这就可以有效减少我们调用系统接口的次数,因为系统调用是有成本的!因为涉及到操作系统帮我们申请空间、做空间内部的调整等等。

那么进程的本质其实就是帮助我们执行代码,让操作系统去调度的,如果我们执行任务的时候总是来一个任务才创建进程,然后去执行,这样是可以的,但是这样会非常慢。那么我们就可以提前将一些进程创建好,当有任务到来时,我们只派给其中一个进程。其中一次性把一批进程创建好,这个工作就叫做进程池的储备,提前储备好,当需要的时候再派任务给它们。

那么如下图,当父进程接收任务之前,它先一次性同时创建出若干个子进程。然后为了更好地控制这些子进程,父进程和每一个子进程都建立一条管道的信道,然后让每一个子进程只从管道中进行读取,而父进程每次想往哪个管道里写内容,就往哪个管道写内容。当父进程没有向管道里写内容时,对应的子进程就会阻塞等待父进程派任务,一旦父进程向管道中写了,子进程会读取对应的数据然后继续向后执行,结合读取的数据就可以执行对应的任务了。接下来我们规定,父进程向子进程管道里写的,都叫做任务码,也就是,我们规定好父子通信时,父进程每次写入时,只能写入4字节,子进程在读取时,也只能读取4字节。所以,我们让父进程向管道里写入4字节 数据,数据不同值代表不同任务,我们就可以想让哪个子进程执行什么任务,就让哪个子进程执行什么任务。

在这里插入图片描述

所以,当父进程想布置任务的时候,无非就是做两件事,一就是选择任务,二是选择进程。接下来我们就可以实现这样的代码,参考代码链接为:进程池.

7. 命名管道

(1)命名管道的系统接口

上面我们学到的匿名管道是没有名字的,因为打开那个文件的时候并没有告诉我们文件名,也就是管道并没有命名。我们直接以读方式写方式打开父子进程,各自拿一个读写端就可以通行。正是因为它没有名字,那么所以它必须得让我们对应的父子进程看到通信资源,它采用的是让父子继承的方案看到的。

那么有没有一种其他的方案呢?因为我们发现匿名管道只能用来进行具有血缘。如果毫不相关的进程进行进程间通信呢。所以我们需要有下一个方案叫做命名管道。接下来我们先使用一下命名管道,先看现象再解释。其中建立命名管道的接口为 mkfifo.

我们先看手册的介绍,我们可以在当前的工作目录下建立命名管道:

在这里插入图片描述

例如我们在当前目录下创建一个命名管道,名字为 pipefifo

在这里插入图片描述

如上,我们就创建了以 p 属性开头的管道文件,该管道就是命名管道。该管道看起来是在磁盘中存在,但是它实际数据并不会刷新到磁盘上。

那么如何让两个进程进行通信呢?我们创建两个终端,两个终端都在当前目录下,一个写,另一个读。观察现象:

在这里插入图片描述

如上图,当写端进行写入的时候,命令行会变成一个进程,向管道里写入,此时读端没有读取,所以写端正在阻塞。当读端进行读取后:

在这里插入图片描述

此时左侧的字符串会到了右侧。如果我们一直往管道里写,该管道的大小也不会有变化,这就是命名管道:

在这里插入图片描述

在这里插入图片描述

(2)理解命名管道

那么关于命名管道的理解,首先,如果两个不同的进程打开同一个文件的时候,在内核中,操作系统会打开几个文件呢?因为我们要打开的时候是以不同的读写方式打开,那么对应的这个被打开的文件,它的内核里的属性和它所谓的那些操作方法,还有缓冲区了这些东西,其实操作系统只会给我们维持一份。为什么呢?因为对于操作系统而言,没必要给你把属性相同的类似的属性写两份,写两份还不方便进行维护,那方法也只有一套就行了,属性也只有一套。更重要的是缓冲区我要给你留一个就行了,那为什么留一个呢?它不怕我们文件读写的时候出现错乱吗?我们都用两个进程打开同一个文件了,它在读写时不加保护的情况下,它在读写是注定会错乱的,你都不怕我怕什么?所以对我们来讲,你会发现我们如果两个不同的进程,打开同一个文件时,实际上在内核里它还是这张图:

在这里插入图片描述

当两个进程打开同一个文件时,在操作系统层面上还是这种结构。所以,可能有不同的读取文件对象,但是文件还是同一个。所以我们就理解我们把它叫做命名管道的原因了。

因为它也是基于文件,因为我们正常进程通信我们只想用它的内存及缓冲区,不想让我们对应的这个数据再进行刷盘。我们是想让一个进程将数据交给另一个进程,它只需要放到缓冲区里,然后不需要进行刷盘,另一个进程读取就可以了。如果打开普通文件,它就必须得刷盘了。所以我们就有了一个文件类型叫做管道文件,属性以 p 开头的。所以当看到这个管道文件时,原来系统里单独创建一个叫做管道文件,那么这个文件当我们的进程在不同地方在打开的时候就知道了,这个文件不需要刷盘,也就是说它只是一个内存及文件!

那么问题又来了,两个不同的进程打开同一个文件时,它们是怎么知道打开的是同一个文件?比如说我们上面讲的匿名管道,父子进程怎么知道打开的是同一个管道文件?因为可以通过继承的方式来进行。能按通过继承的方式让父子看到不同对应的文件。可是命名管道不一样,怎么知道我们两个进程打开的是同一个文件的呢?

很简单,两个进程只要看到同一个文件名,那么此时这两个进程就可以打开同一个文件了。可是其实可不仅仅只有文件名,还有一个前提条件叫做 pwd ;因为我们在上面使用的命名管道,都是在同一路径下的文件名,所以怎么知道两个进程打开的是同一个文件呢?就是用路径+文件名确定的,而路径+文件名具有唯一性,而且该文件是 p,是管道文件,所以就进行内存级通信就可以了,这就是命名管道。

(3)使用命名管道

接下来我们使用两个毫不相干的进程进行,建立命名管道,并且进行命名管道间的通信,形成两个可执行程序分别是 serverclient. 其中 server 是管理管道文件的,也就是说创建、删除等工作。

其中我们使用到的系统接口是 mkfifo,参考手册:

在这里插入图片描述

其中参数 pathname 是哪一个路径下的文件名,也就是保证唯一性的;第二个 mode,因为管道也是文件,所以这个文件的权限也要有。

在这里插入图片描述

其中返回值,成功返回0,失败返回-1,errno 被设置。

参考代码链接为:命名管道使用.

三、初识日志

1. 理解日志

关于日志,实际上我们程序在运行期间需要不断向显示器或者文件进行信息输出的,我们在运行代码时,想产生各种各样的日志数据,这些日志数据方便我们记录程序运行的痕迹,方便后期进行排查。

而日志的格式并没有严格的要求,我们可以按照自己的要求定制,但是一般都会有日志时间,日志等级,日志内容等,可能还会有文件的名称和行号。

那么什么是日志等级呢?对于我们写的软件还是服务器,在运行过程中避免不了各种各样的问题,所以根据严重程度的不同,我们的处理方法是不一样的。常见的日志等级有:

  • Info:常规消息
  • Warning:警告信息
  • Error:比较严重,可能需要立即处理
  • Fatal:致命问题
  • Debug:调试

那么我们上面实现的使用命名管道中,我们看见有许多的 perror 的信息,还可以加上一些常规消息,比如创建文件成功后打印一些数据,而这些信息我们都可以用一个日志函数处理,我们可以让这些信息直接打印在显示屏上,也可以让这些信息写到文件中,这个看我们的需求。所以我们下面实现一个简单的日志函数,引入到命名管道的代码中。

参考代码链接为:日志.

其中补充知识如下。

2. 获取时间

C语言 当中获取时间的方式非常多,接下来介绍一种,localtime

在这里插入图片描述

那么它的参数是 time_t 类型,那么就是 time() 接口的返回值,如下:

在这里插入图片描述

那么 localtime 就是我们传入一个 time_t 类型,它会帮我们转化为 struct tm 这样的结构,我们可以看一下这个结构:

在这里插入图片描述

所以这个 localtime 就可以让我们控制自己打印的时间。

注意,tm_year 是从 1900 年开始算的,tm_mon 是从 0 开始的。

3. 可变参数的使用

我们都见过可可变参数,但是还没有使用过,接下来介绍一下如何使用可变参数。假设我们有一个 sum 函数,是求任意个数个元素的求和,如下:

				int sum(int n, ...);

使用可变参数之前,必须要有一个 va_list 结构,其实它就是一个 char* 类型的结构。

在这里插入图片描述

那么我们在调函数的时候,函数创建栈帧结构,无论是可变参数还是确定参数,在使用的时候,参数是要压栈的,在形成栈帧结构之后,要将传入的参数从右向左依次入栈。

那么上面说 va_list 其实就是一个指针,所以我们创建一个 va_list 的对象,就是一个 char* 指针,它可以根据 va_list 帮我们提取可变参数一个一个的参数。我们初始化一个 va_list 的对象如下:

				int sum(int n, ...){va_list s;va_start(s, n);// ...}

因为参数在压栈的时候是从右往左压的,所以参数 n 是最后一个入栈的,所以 va_start 就是使用 s 指向 n;也就是说,我们想让 s 指向可变部分的开头,其实只要让 s 指针指向 n 的地址,然后加上 n 的大小个字节,就可以让 s 指向可变部分的开头,如下图:

在这里插入图片描述

也就是说, va_start 我们可以把它看作是 s = &n + 1,其中 +1 就是让指针向后移动 n 自身大小个字节。

s 指向了可变部分的开头处,我们只需要知道开头处的类型,就可以把这个元素访问出来,然后再让 s 加上类型大小,就可以继续指向下一个可变参数,依次解析。

其实 va_start 就是一个宏,还有其他的 va_listva_end() 等都是宏,它在实现的时候会自己取地址的。

所以在可变参数中,必须要有至少一个具体的参数!因为它要找可变部分的起始地址!那么如果我们想用上面的 sum 函数,就可以像如下代码使用:

				int sum(int n, ...){va_list s;va_start(s, n);int _sum = 0;while(n){va_arg(s, int);n--;}va_end(s); // s = NULL;return _sum;}

那么 va_arg() 就是用 s 根据类型来提取参数,第二个参数就是根据的类型,如今我们的代码是写固定了 int 类型,实际上需要像 printf 函数那样进行格式控制,进行字符串分析。

相关文章:

【Linux】基于管道进行进程间通信

进程间通信 一、初识进程间通信1. 进程间通信概念2. 进程间通信分类 二、管道1. 管道概念2. 管道原理3. 匿名管道4. 匿名管道系统接口5. 管道的特性和情况6. 匿名管道的应用&#xff08;1&#xff09;命令行&#xff08;2&#xff09;进程池 7. 命名管道&#xff08;1&#xff…...

Vue中间件的讲解案例分析

Vue中间件的讲解案例分析 1. Axios中间件&#xff1a; Axios是一个常用的HTTP客户端&#xff0c;可以与Vue结合使用&#xff0c;处理网络请求和数据获取。您可以创建一个Axios实例&#xff0c;并将其作为Vue的原型属性或插件使用&#xff0c;以便在整个应用程序中共享和使用。…...

【C生万物】C语言分支和循环语句

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更新的动力❤️ &#x1f64f;小杨水平有…...

Vue代理模式和Nginx反向代理(Vue代理部署不生效)

在使用axios时&#xff0c;经常会遇到跨域问题。为了解决跨域问题&#xff0c;可以在 vue.config.js 文件中配置代理&#xff1a; const { defineConfig } require(vue/cli-service) module.exports defineConfig({transpileDependencies: true,devServer: {port: 7070,prox…...

C语言中的作用域与生命周期

作用域&#xff08;scope&#xff09;是程设计概念&#xff0c;通常来说&#xff0c;一段程序代码中所⽤到的名字并不总是有效的&#xff0c;而限定这个名字的可⽤性的代码范围就是这个名字的作用域。 局部变量的作用域是变量所在的局部范围。全局变量的作用域是整个工程&…...

MATLAB计算多边形质心/矩心

前言&#xff1a;不规则四边形的中心 不规则四边形的出心有多种定义&#xff0c;以下是最常见的三种&#xff1a; 1.重心&#xff1a;重心是四边形内部所有顶点连线交点的平均位置。可以通过求解四个顶点坐标的平均值来找到重心。 2.质心&#xff1a;质心是四边形内部所有质点…...

IP地址如何保护网络安全

面对网络攻击时&#xff0c;仅依靠常态化的网络安全防御系统已捉襟见肘&#xff0c;如联合使用IP地址数据可以形成多元化的安全解决方案&#xff0c;全面监控网络活动&#xff0c;发现潜在威胁&#xff0c;制定有针对性的应对措施。 网络攻击追踪 当网站或应用遭受DDoS等网络攻…...

考研数据结构笔记(3)

顺序表存储结构 存储结构顺序结构定义基本操作的实现静态分配问题 动态分配代码功能 顺序表的特点: 顺序表小结顺序表的插入删除插入删除小结 顺序表的查找按位查找按值查找小结 存储结构 顺序结构 定义 线性表是具有相同数据类型的n(n>0)个数据元素的有限序列(每个数据元素…...

第二讲 数据结构 AcWing 827. 双链表

目录 双链表代码 && 思路 双链表 实现一个双链表&#xff0c;双链表初始为空&#xff0c;支持 5 种操作&#xff1a; 在最左侧插入一个数&#xff1b; 在最右侧插入一个数&#xff1b; 将第 k个插入的数删除&#xff1b; 在第 k 个插入的数左侧插入一个数&#xff1b…...

假期作业 2月6号

一、填空题 1、一个类的头文件如下所示&#xff0c;num初始化值为5&#xff0c;程序产生对象T&#xff0c;且修改num为10&#xff0c;并使用show()函数输出num的值10。 #include <iostream.h> class Test { private: static int num; public: Test(int); void show(); };…...

【wu-lazy-cloud-network】Java自动化内网穿透

项目介绍 wu-lazy-cloud-network 是一款基于&#xff08;wu-framework-parent&#xff09;孵化出的项目&#xff0c;内部使用Lazy ORM操作数据库&#xff0c;主要功能是网络穿透&#xff0c;对于没有公网IP的服务进行公网IP映射 使用环境JDK17 Spring Boot 3.0.2 功能 1.内网…...

【C++】C++入门(二)

个人主页 &#xff1a; zxctsclrjjjcph 文章封面来自&#xff1a;艺术家–贤海林 如有转载请先通知 文章目录 1. 前言2. 缺省参数2.1 缺省参数概念2.2 缺省参数分类 3. 函数重载3.1 函数重载概念3.2 C支持函数重载的原理--名字修饰(name Mangling) 1. 前言 在前面一篇文章中简…...

6.electron之上下文隔离,预加载JS脚本

如果可以实现记得点赞分享&#xff0c;谢谢老铁&#xff5e; Electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 Electron 将 Chromium 和 Node.js 嵌入到了一个二进制文件中&#xff0c;因此它允许你仅需一个代码仓库&#xff0c;就可以撰写支持 Windows、…...

【翻译】 Processing的安卓项目构建(译者用的是Android Studio)

原文链接&#xff1a;https://github.com/processing/processing-android/wiki/Building-Processing-for-Android&#xff0c;版本Apr 2, 2023 译者声明&#xff1a;这个文档是开源公开的&#xff0c;协议是GNU协议。译者自己得使用这个文档&#xff0c;所以才翻译的&#xff0…...

华为机考入门python3--(8)牛客8-合并表记录

分类&#xff1a;字典排序 知识点&#xff1a; 将输入转成int的列表 my_list list(map(int, input().strip().split( ))) 将列表转为元组 tuple(my_list) 访问元素为元组的列表 for first, second, third in my_list: 对字典进行排序 sorted(my_dict.items())…...

vue3-内置组件-KeepAlive

KeepAlive <KeepAlive> 是一个内置组件&#xff0c;它的功能是在多个组件间动态切换时缓存被移除的组件实例。 基本使用 默认情况下&#xff0c;一个组件实例在被替换掉后会被销毁。这会导致它丢失其中所有已变化的状态——当这个组件再一次被显示时&#xff0c;会创建…...

RxJava Subject

目录 AsyncSubjectBehaviorSubjectPublishSubjectReplaySubjectSerializedSubjectUnicastSubject 在Rxjava中&#xff0c; Subject可以同时表示Observer和Observable, 允许从单个源到多个子观察者multiple child Observers。 除了 onSubscribe(io.reactivex.disposables.Dispos…...

[N-141]基于springboot,vue网上拍卖平台

开发工具&#xff1a;IDEA 服务器&#xff1a;Tomcat9.0&#xff0c; jdk1.8 项目构建&#xff1a;maven 数据库&#xff1a;mysql5.7 系统分前后台&#xff0c;项目采用前后端分离 前端技术&#xff1a;vueelementUI 服务端技术&#xff1a;springbootmybatis-plusredi…...

新能源光伏发电设计全面解析

伴随碳达峰、碳中和“双碳”政策大力推行&#xff0c;以及新能源市场的利好&#xff0c;目前多个城市在大力推进光伏发电项目&#xff0c;本篇文章将详细介绍关于光伏发电设计的信息。 一、光伏发电概念 光伏发电是指利用太阳辐射能在太阳能电池板上产生的电能&#xff0c;通…...

踩坑实录(Third Day)

临近年关&#xff0c;同事们该回家的也都回家了&#xff0c;所以我对工作的欲望不是很强烈&#xff0c;所以就主要是自己学习了一下&#xff0c;在 B 站看看视频&#xff0c;自己敲代码&#xff0c;所以今天没遇到什么坑&#xff0c;但是可以分享一下之前踩到的两个坑。 此为第…...

Linux联网安装MySQL Server

yum安装 以下代码复制粘贴到控制台即可 yum list | grep mysql-server #查看可以下载的MySQLyum install -y mysql-server #安装MySQLmysql_secure_installation #引导安装 引导安装实例如下 systemctl enable mysqld 设置开机自动启动 systemctl sta…...

使用GDI画图片生成合成图片并调用打印机进行图片打印

使用GDI画图片生成合成图片并调用打印机进行图片打印 新建窗体应用程序PrinterDemo&#xff0c;将默认的Form1重命名为FormPrinter&#xff0c;添加对 Newtonsoft.Json.dll用于读写Json字符串 zxing.dll&#xff0c;zxing.presentation.dll用于生成条形码&#xff0c;二维码…...

【PyQt】04-Designer

文章目录 前言一、初级 Designer1.1 拖拽设计界面1.2 搞定之后记得保存ui文件1.3 载入代码1.4 运行结果 二、登入界面代码效果展示账号密码错误时账号和密码正确 总结 前言 自然还是跟着王铭东老师学的 一、初级 Designer 1.1 拖拽设计界面 进度条是这个 1.2 搞定之后记得保…...

第4节、电机多段转动【51单片机+L298N步进电机系列教程】

↑↑↑点击上方【目录】&#xff0c;查看本系列全部文章 摘要&#xff1a;本节介绍用控制步进电机三个主要参数角度、速度、方向&#xff0c;实现简单的步进电机多段控制 一、目标功能 输入多个目标角度&#xff0c;以及每个角度对应的速度&#xff0c;实现步进电机的多段多速…...

算法学习——华为机考题库1(HJ1 - HJ10)

算法学习——华为机考题库1&#xff08;HJ1 - HJ10&#xff09; HJ1 字符串最后一个单词的长度 描述 计算字符串最后一个单词的长度&#xff0c;单词以空格隔开&#xff0c;字符串长度小于5000。&#xff08;注&#xff1a;字符串末尾不以空格为结尾&#xff09; 输入描述&…...

PSQL常用操作

目录 前言 准备工作 添加postgres用户 初始化数据库 启动服务 创建数据库 psql连接数据库 常规操作 数据库 schema相关 插件 其他 前言 老折腾&#xff0c;还是记录点啥吧...... 基于本地PG数据库(打包为绿色版本了)&#xff0c;实操记录&#xff0c;版本pgsql12…...

C++ “万能血“ void*指针

本篇文章我们来介绍一下C “万能血” void指针 为什么说他万能呢&#xff1f; 原因:C void* 是一种特殊的指针类型&#xff0c;可用于存放任意对象的地址。在函数传参中也可以作为任何实参的形参 void型详细介绍 void* 是C中的一种特殊的指针类型&#xff0c;被称为"无类…...

微信小程序新手入门教程四:样式设计

WXSS (WeiXin Style Sheets)是一套样式语言&#xff0c;用于描述 WXML 的组件样式&#xff0c;决定了 WXML 的组件会怎么显示。 WXSS 具有 CSS 大部分特性&#xff0c;同时为了更适合开发微信小程序&#xff0c;WXSS 对 CSS 进行了扩充以及修改。与 CSS 相比&#xff0c;WXSS …...

类银河恶魔城学习记录1-6 Flip基本设置源代码 P33

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili Player.cs using System.Collections; using System.Collections.Generic; using Unity.VisualScripting; us…...

【Git教程】(一)基本概念 ——工作流、分布式版本控制、版本库 ~

Git教程 基本概念 1️⃣ 为什么要用 Git2️⃣ 为什么要用工作流3️⃣ 分布式版本控制4️⃣ 版本库5️⃣ 简单的分支创建与合并&#x1f33e; 总结 在本章中&#xff0c;将介绍一个分布式版本控制系统的设计思路&#xff0c;以及它与集中式版本控制系统的不同之处。除此之外&am…...