【从浅学到熟知Linux】进程控制上篇=>进程创建、进程终止与进程等待(含_exit与exit的区别、fork函数详解、wait与waitpid详解)

在这里插入图片描述

🏠关于专栏:Linux的浅学到熟知专栏用于记录Linux系统编程、网络编程等内容。
🎯每天努力一点点,技术变化看得见

文章目录

  • 进程创建
    • fork函数
    • 写时拷贝
  • 进程退出
    • 进程退出操作系统做了什么?
    • 进程退出场景
    • 进程退出的常见方法
      • 正常终止与异常终止
      • 缓冲区的概念
      • exit与_exit的区别
  • 进程等待
    • 进程等待的必要性
    • 进程等待方法
      • wait方法
      • waitpid方法
    • wait/waitpid获取子进程信息原理


进程创建

fork函数

创建子进程时,需要使用系统调用函数fork。创建的新进程为子进程,而原进程为父进程。
在这里插入图片描述
该函数在子进程创建成功时,给父进程返回子进程pid,给子进程返回0;创建失败时,给父进程返回-1。
在这里插入图片描述
下面我们使用fork函数来创建子进程↓↓↓

#include <stdio.h>
#include <assert.h>
#include <unistd.h>

int main()
{
	printf("before fork, pid = %d\n", getpid());
	pid_t id = fork();
	assert(id != -1);
	(void)id;

	printf("after fork, pid = %d, fork return %d\n", getpid(), id);
	return 0;
}

在这里插入图片描述
上面代码执行路径如下下图所示↓↓↓
在这里插入图片描述
进程调用fork。将会从用户空间转换到内核空间,内核做了如下内容:
①分配新的内存块和内核数据结构给子进程
②将父进程部分数据结构内容拷贝给子进程
③添加子进程到系统进程列表中
④fork返回,开始调度器调度

当进程调用fork后,就会有二进制代码相同的的两个进程,它们均运行到相同的地方,但两个代码相互独立,各自执行各自的。上面代码中,两个进程虽然执行了同一份代码,但由于父子进程的id值、pid值不同,故打印的结果不同。同时,这里到底是父进程先打印还是子进程先打印,取决于调度器的调度,哪个进程先被调度是不一定的。

★ps:创建子进程时,给子进程分配对应的内核结构,必须是子进程独有的,因为进程具有独立性。理论上,子进程也要有自己的代码和数据,但在子进程刚创建时,它的代码和数据是与父进程共享的。只有当子进程进行程序替换(修改代码)或对数据做修改等操作时,会发生写时拷贝,为子进程创建独立的存储空间。

fork的应用常见有哪些呢?

  • 一个父进程希望复制自己,使父进程同时执行不同的代码段。例如:父进程等待客户端请求,生成子进程来处理请求。
  • 一个进程要执行另一个完全独立的程序,类似于shell。

fork调用失败的原因有哪些?

  • 系统中有太多的进程
  • 实际用户的进程数超过了限制

写时拷贝

通常,父进程创建子进程后,在父进程不进行代码及数据的修改时,物理地址上的数据是父子进程共享的。

在这里插入图片描述
但当父进程或子进程尝试对代码或数据进行修改时,就会发生写时拷贝。若子进程尝试修改某个变量,此时发生写时拷贝,会给子进程开辟一个该变量的物理地址,子进程修改的结果保存在该物理地址上。
在这里插入图片描述
操作系统为什么选择写时拷贝呢?
①写时拷贝用的时候再分配空间,是一种延迟申请技术,是提高内存使用效率的表现,可以提高整机的内存使用效率;(不需要子进程复制父进程的全部数据,防止浪费空间)
②操作系统无法在代码执行前预知子进程哪些代码和父进程不同,因而无法预知子进程哪些代码和数据需要与父进程共享,哪些需要独立存储;
③因为写时拷贝的存在,父子进程不同的代码和数据得以分离,保证了进程的独立性。

进程退出

进程退出操作系统做了什么?

操作系统要释放进程申请的相关内核数据结构和对应的数据和代码(本质就是释放系统资源)。

进程退出场景

  • 代码执行完毕,结果正确
#include <stdio.h>
int Add(int from, int to)
{
	int sum = 0;
	for(int i = from; i <= to; i++)
	{
		sum += i;
	}
	return sum;
}
int main()
{
	printf("Add 1 to 100 is %d\n", Add(0, 100));
	return 0;
}

在这里插入图片描述

  • 代码运行完毕,结果不正确
#include <stdio.h>
int Add(int from, int to)
{
	int sum = 0;
	/*应该为i<=to*/
	for(int i = from; i < to; i++)
	{
		sum += i;
	}
	return sum;
}
int main()
{
	printf("Add 1 to 100 is %d\n", Add(0, 100));
	return 0;
}

在这里插入图片描述

  • 代码异常终止。即代码没有跑完,程序崩溃。
#include <stdio.h>

int main()
{
	int* p = NULL;
	*p = 100;//野指针异常
	return 0;
}

在这里插入图片描述

  • 在程序执行结束时,我们会使用return语句返回一个数值作为main函数的返回值。这个返回值是做什么用的呢?

【例子1】小明参与了一场考试后,回家后给老爹汇报成绩。
如果小明考了100分(满分100),那么他的老爹并不会关心他为什么考了100分;但当小明考了3分,他的老爹会关心他考3分的原因。如果做出如下约定,每个数字标识不同的原因:

状态码描述
1生病了导致没考好
2没好好学习导致没考好

在操作系统中,对于程序正常终止我们不做关心,但程序一旦出错,我们就需要知道程序出错的原因。在操作系统约定:如果进程返回的状态码为0,则标识进程正常结束;如果进程返回的状态码非0,则标识进程异常结束(不同的非零值标识不同的错误原因)。操作系统对于不同的状态码给了不同的错误描述信息,我们可以使用errno.h下的errno变量获取错误码,使用strerror(errno)获取错误码的错误描述。下面使用程序获取Linux的不同错误码及其描述↓↓↓

#include <stdio.h>
#include <string.h>

int main()
{
	for(int i = 0; i < 200; i++)
	{
		printf("[%d]->%s\n", i, strerror(i));
	}
	return 0;
}

在这里插入图片描述
★ps:我们自己写的程序可以不遵守操作系统提供的错误码与错误描述的对应关系,我们可以自定义错误码与错误码描述。

  • 那谁会关心当前进程的退出码呢?

子进程的退出码会返回给它的上一级进程,一般而言当前进程的父进程要关心当前进程的退出码,用于评判该子进程执行代码是否正确。例如我们登录Linux系统时使用的bash,它通过创建子进程程序来执行可执行文件(命令、用户程序等),它可以显示最近一次执行的子进程的退出码。我们可以使用echo $?来查看↓↓↓
在这里插入图片描述

  • 一旦程序异常终止,而不是正常终止的,此时的退出码还有意义吗?

【例子2】小明考试作弊
小明每门考试时都作弊考了100分。学校在颁发奖学金的时候会将小明计算在内吗?显然不会,因为小明的考试行为出现了异常,它的结果就不再被关心。

同理,如果程序出现了异常,此时的程序还来不及设置退出码,此时的退出码的数值是不确定的,故此时的退出码没有意义。

  • 程序出现异常,无法通过退出码评判程序异常原因,那要使用什么来查看程序的异常原因呢?

答案是信号。如果我们写出一个除0的错误程序,我们会看到什么的结果呢?

#include <stdio.h>

int main()
{
	int a = 8 / 0;
	return 0;
}

在这里插入图片描述

在该程序发生除0错误时,操作系统给执行该程序的进程发生了8号信号SIGFPE。我们可以使用kill -l查看所有的信号码与对应信号名↓↓↓
在这里插入图片描述
我们来验证一下,上面的程序就是收到8号SIGFPE才终止的。我们执行下面代码对应的可执行程序,并给对应的进程发送8号信号,验证上述进程确实收到了8号信号才异常终止↓↓↓

#include <stdio.h>

int main()
{
	while(1)
	{}
	return 0;
}

在这里插入图片描述
在这里插入图片描述

进程退出的常见方法

正常终止与异常终止

正常终止

  • 从main函数返回,即return+退出码
  • 调用exit
  • 调用_exit

异常终止
由于程序内部逻辑错误导致出现异常时,操作系统会给当前进程发送信号来终止程序,这种方式就是异常终止。当然,用户也可以通过kill -[信号编号] [进程pid]来给对应进程发送信号,使其异常终止。

★ps:ctrl + C本质是给当前bash中正在执行的前台进程发送2号信号,其他热键如ctrl + Z等本质也是通过给进程发送信号来实现的。

正常终止中的return+退出码及异常终止方式在上文已经有介绍了,下面我们来介绍一下exit、_exit方式如何使用,及它们的区别。

缓冲区的概念

在介绍exit与_exit前,我们要先理解一个概念——缓冲区↓↓↓

#include <stdio.h>

int main()
{
	printf("jammingpro is coding...");
	while(1)
	{}
	return 0;
}

在这里插入图片描述
从上面代码可以发现,在printf执行结束后,并没有打印字符串内容。这时因为,当我们使用C语言向显示器打印信息时,打印内容会被放入一片缓冲区中,如果缓冲区满了,或遇到\n时才会刷新缓冲区(C语言是行缓冲的,所以遇到\n会刷新缓冲区),将内容刷新到显示器中。当然,程序正常执行结束后(return 0),缓冲区的内容也会被刷新。

如果我们想让内容马上刷新到显示器上,可以使用stdio.h下的fflush手动刷新缓冲区↓↓↓

#include <stdio.h>

int main()
{
	printf("jammingpro is coding...");
	fflush(stdout);
	while(1)
	{}
	return 0;
}

在这里插入图片描述

exit与_exit的区别

_exit函数
status定义了进程的退出状态,父进程可以通过wait来获取status(关于wait,将在下文介绍)。
在这里插入图片描述

#include <stdio.h>
#include <unistd.h>

int main()
{
	printf("jammingpro");
	_exit(1);
	return 0;
}

在这里插入图片描述

从_exit的示例代码可以发现_exit执行并不会刷新缓冲区,故最后并没有打印"jammingpro"字符串。

exit函数
在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>

int main()
{
	printf("jammingpro");
	exit(1);
	return 0;
}

在这里插入图片描述
从_exit和exit对同一份代码的不同执行结果可以看出,_exit在退出时不会刷新缓冲区,而exit退出时会刷新缓冲区。

exit与_exit相同的是:它们两者均会调用内核的_exit;不同的是exit除了调用_exit,还做了其他工作:
①执行用户通过atexit或on_exit定义的清理函数
②关闭所有打开的流,所有的缓存数据均被写入(即刷新缓冲区)
③再调用_exit
在这里插入图片描述

★ps:main函数中,return语句就是终止进程,即return+退出码;如果return语句在main函数中,则标识进程结束;如果return语句位于非main函数的其他函数中,则表示该函数执行结束,而不是进程结束。而exit可以在代码的任何地方调用,均表示终止当前进程。

进程等待

进程等待的必要性

当一个进程fork出子进程后,父进程没有对子进程的进行回收时,子进程就会变成僵尸状态。此时的子进程无法被任何信号终止(包括kill -9),因为信号是用于终止进程的,而处于僵尸状态的进程已经处于终止状态,只是对应的系统资源尚未被回收,谁也无法杀死一个已经死去的进程。

子进程的僵尸只能由其父进程解决,父进程通过进程等待的方式,回收子进程资源,获取子进程的退出信息(因为父进程需要知道自己派给子进程运行的结果是否正确,子进程是否正常退出等)。

★ps:C/C++程序执行完后,空间会被系统回收,则不会发生内存泄漏。但如果类似服务器程序,它一直跑,不会结束,则会发生内存泄漏。因为当前进程未执行结束,则系统资源不会被回收,而由于内存没有释放,导致大量无用的内存空间无法被申请使用,故产生内存泄漏。

进程等待方法

wait方法

当等待子进程成功时,会返回等待成功的子进程的pid,失败则返回-1。而传入的参数status为输出型参数,如果需要回去子进程的退出状态,则需要传入status;若不关心,则可以填写NULL。
在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
	pid_t id = fork();
	if(id == 0)
	{
		int cnt = 5;
		while(cnt)
		{
			printf("[%d]->I am child process! pid = %d, ppid = %d\n", cnt, getpid(), getppid());
			cnt--;
			sleep(1);
		}
		exit(0);
	}
	int status = 0;
	pid_t ret = wait(&status);
	if(ret > 0)
	{
		printf("wait %d success! status = %d\n", ret, status);
	}
	return 0;
}

在这里插入图片描述
从上面代码可以发现,当子进程在执行时,父进程并不会执行wait之后的if判断语句,而是阻塞在pid_t ret = wait(&status);处等待子进程退出。故wait是阻塞等待的。

这里的status表示什么含义呢?其实,status并不是像上面代码那样使用的。下面介绍status如何使用↓↓↓

status是一个输出型参数,它的值在调用wait及接下来将要介绍的waitpid时,由操作系统自动填充。如果传递NULL,表示不关心子进程的退出状态信息;否则,操作系统会通过该参数,将子进程的退出信息反馈给父进程。

status不能像上面的代码那样使用,即不能将status看作是整型,而要将status当作位图来看待。这里我们只研究status的低16位,它的低16位信息如下↓↓↓
①正常终止时,第8到第15位保存进程的退出状态
②异常终止时,第8到第15位保存进程的退出状态(但异常终止的退出状态不一定能正确标识进程的退出状态,这里的退出状态是无效的,直接忽略),第7位为coredump标志位,这里先忽略这一位,将于后面的文章中介绍,第0到7位存储的是当前进程收到的信号。
在这里插入图片描述
如果我们想获取子进程的退出码和退出信号,则上面的代码应该修改成下面这样↓↓↓

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
	pid_t id = fork();
	if(id == 0)
	{
		int cnt = 5;
		while(cnt)
		{
			printf("[%d]->I am child process! pid = %d, ppid = %d\n", cnt, getpid(), getppid());
			cnt--;
			sleep(1);
		}
		exit(0);
	}
	int status = 0;
	pid_t ret = wait(&status);
	if(ret > 0)
	{
		printf("wait %d success! exitcode = %d sig = %d\n", ret, ((status >> 8) & 0xFF), (status & 0x7F));
	}
	return 0;
}

在这里插入图片描述
如果父进程创建了多个子进程,则wait等待的是哪一个进程呢?wait等待任意一个进程,只要等待到某个进程,则父进程不再阻塞于当前的wait语句上。下面代码演示了创建5个子进程,并用父进程循环回收5个子进程↓↓↓

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
	int i = 0;
	for(; i < 5; i++)
	{
		pid_t id = fork();
		if(id == 0)
		{
			int cnt = 5;
			while(cnt)
			{
				printf("[%d]->I am child process! pid = %d, ppid = %d\n", cnt, getpid(), getppid());
				cnt--;
				sleep(1);
			}
			exit(0);
		}
	}
	i = 0;
	while(; i < 5; i++)
	{
		int status = 0;
		pid_t ret = wait(&status);
		if(ret > 0)
		{
			printf("wait %d success! exitcode = %d sig = %d\n", ret, ((status >> 8) & 0xFF), (status & 0x7F));
		}
	}
	return 0;
}

在这里插入图片描述

waitpid方法

  • waitpid与wait相同,如果等待子进程成功,则会返回该子进程的pid,如果失败则返回-1;且一样可以传入status获取子进程的退出状态信息(用法与wait相同)。

waitpid多了两个参数,即pid和options。

  • 当指定pid为某个子进程的pid时,waitpid只会等待该子进程的pid,而不会等待其他子进程;如果将pid指定为-1,则表示等待任意一个子进程。
  • options设置为0时,表示阻塞等待,这与wait相同;如果设置为WNOHANG表示非阻塞式等待。关于阻塞等待和非阻塞等待,将于下文介绍。

在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
	pid_t id = fork();
	if(id == 0)
	{
		int cnt = 5;
		while(cnt)
		{
			printf("[%d]->child process, pid = %d\n", cnt, getpid());
			cnt--;
			sleep(1);
		}
	}
	int status = 0;
	pid_t ret = waitpid(id, &status, 0);
	if(ret == id)
	{
		printf("wait %d success! exitcode = %d, sig =%d\n", ret ,((status >> 8) & 0xFF), (status & 0x7F));
	}
	return 0;
}

在这里插入图片描述

下面我们来谈谈阻塞等待与非阻塞等待↓↓↓

【例子3】期末考前,临时抱佛脚的小明[阻塞等待篇]
因为明天要考操作系统,小明想要求助他们班的学霸李华,让他帮忙画重点并辅导一下,作为报酬,小明要请李华吃饭。小明来到李华家楼下,打电话给李华,李华告知他需要等一会儿,他还需要复习一会儿,需要50分钟左右。小明在李华家楼下不做其他事,他每1分钟给李华打电话,询问他是否可以出发去吃饭辅导了,知道李华下楼了,他才停止。

像这种循环等待,而不做其他事情,这种等待就称为阻塞式等待。上面的wait和waitpid的代码示例均是阻塞等待的,父进程在wait和waitpid函数上阻塞等待子进程退出,而不做其他事,等子进程执行结束才执行下面的if判断语句。

阻塞式等待可以及时回收子进程,但父进程花费大量时间阻塞等待子进程,只要CPU时间片轮到父进程,父进程就一直循环等待子进程,这样效率明显不高。

【例子4】考试前,临时抱佛脚的小明[非阻塞等待篇]
因为上次操作系统被李华辅导后,小明考了61分,至少没有挂科。操作系统考完的隔天要考数据结构,小明又找到李华帮他复习。小明来到李华家楼下,李华告诉他需要等待30分钟左右,这次小明每复习数据结构5分钟,才给李华打一次电话。

像这种再等待时,顺带做一下其他任务,这种称为非阻塞等待。非阻塞等待执行的其他任务不应花费过多时间,以免影响父进程对子进程的等待与回收(因为父进程等待子进程才是主要任务,执行其他代码只是顺带执行,不是重点)。

下面给出一个非阻塞式等待的代码↓↓↓

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#define NUM 5

typedef void(*func_t)();
func_t funcSet[NUM];

void task1()
{
	printf("处理日志信息\n");
}

void task2()
{
	printf("安全检测任务\n");
}

void task3()
{
	printf("其他处理任务\n");
}

void loadTask()
{
	funcSet[0] = task1;
	funcSet[1] = task2;
	funcSet[2] = task3;
}

int main()
{
	loadTask();
	pid_t id = fork();
	if(id == 0)
	{
		int cnt = 5;
		while(cnt)
		{
			printf("[%d]->child process, pid = %d\n", cnt, getpid());
			cnt--;
			sleep(1);
		}
		exit(0);
	}
	int status = 0;
	while(1)
	{
		pid_t ret = waitpid(id, &status, WNOHANG);
		if(ret == id)
		{
			printf("wait %d success! exitcode = %d, sig =%d\n", ret ,((status >> 8) & 0xFF), (status & 0x7F));
			break;
		}
		else
		{
			int i = 0;
			for(; i < 3; i++)
			{
				funcSet[i]();
			}
		}
		usleep(500000);
	}
	return 0;
}

在这里插入图片描述
★ps:进程等待时,要保证父进程最后退出。

★ps:waitpid等待错误的时候会返回-1,当等待的子进程pid不是该进程的子进程时,则会返回-1;如果等待进程还未执行结束,则会返回0;等待成功则返回子进程的pid。

★ps:除了使用((status >> 8) & 0xFF),系统还提供了WEXITSTATUS(status)用于获取子进程的退出码;提供了WIFEXITED(status)判断子进程是否等待成功,还提供了其他宏函数,请使用man手册查阅。下面给出上面两个宏函数使用的代码↓↓↓

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
	pid_t id = fork();
	if(id == 0)
	{
		int cnt = 5;
		while(cnt)
		{
			printf("[%d]->child process, pid = %d\n", cnt, getpid());
			cnt--;
			sleep(1);
		}
		exit(0);
	}
	int status = 0;
	pid_t ret = waitpid(id, &status, 0);
	if(WIFEXITED(status))
	{
		printf("wait %d success! exitcode = %d\n", ret ,WEXITSTATUS(status));
	}
	return 0;
}

在这里插入图片描述

wait/waitpid获取子进程信息原理

进程具有独立性,子进程的退出码等不也是子进程的数据吗?父进程凭什么拿到呢?wait/waitpid究竟是如何拿到子进程的退出状态的信息的?

当子进程处于僵尸状态时,系统至少要保留子进程的PCB,PCB中保存着子进程的退出码、收到的信号等退出信息。

当父进程在CPU上执行waitpid时,则进入内核态。处于内核态的执行语句有操作系统的权限,因此它可以读取子进程的退出信息,并将该退出信息填写入status。
在这里插入图片描述

🎈欢迎进入从浅学到熟知Linux专栏,查看更多文章。
如果上述内容有任何问题,欢迎在下方留言区指正b( ̄▽ ̄)d

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/548517.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue3 + Element-Plus 使用 Table 预览图片发生元素遮挡

Vue3 Element-Plus 使用 Table 预览图片发生元素遮挡 问题代码问题重现解决方法最终效果 问题代码 <el-table-column label"视频" align"center"><template #default"scope" style"display: flex;"><div style"…

【RV1106的ISP使用记录之基础知识】硬件连接关系与设备树的构建

RV1106具备2个mipi csi2 dphy硬件&#xff0c;1个VICAP硬件和1个ISP硬件。其中&#xff1a; 1、mipi csi2 dphy 用于对数据流的解析&#xff0c;支持MIPC,LVDS,DVP三种接口&#xff1b; 2、VICAP用于数据流的捕获&#xff1b; 3、ISP用于对图像数据进行处理&#xff1b; 这三个…

架构设计-灰度发布系统架构设计

灰度发布的定义 互联网产品需要快速迭代开发上线&#xff0c;又要保证质量&#xff0c;保证刚上线的系统&#xff0c;一旦出现问题可以很快控制影响面&#xff0c;就需要设计一套灰度发布系统。 灰度发布系统的作用&#xff0c;可以根据配置&#xff0c;将用户的流量导到新上…

【Kafka】Zookeeper集群 + Kafka集群

Zookeeper 概述 Zookeeper是一个开源的分布式的&#xff0c;为分布式框架提供协调服务的Apache项目。 Zookeeper 工作机制★★★ Zookeeper从设计模式角度来理解&#xff1a; 1&#xff09;是一个基于观察者模式设计的分布式服务管理框架&#xff1b; 它负责存储和管理大家都关…

HashMap的扩容看这一篇足够

在Java中&#xff0c;对于HashMap这样的实现&#xff0c;put方法是用来将一个键值对插入到Map中的核心方法。以下是HashMap类中put方法的大致执行流程&#xff1a; 计算Hash值&#xff1a; 首先&#xff0c;put方法会接收一个键&#xff08;Key&#xff09;和一个值&#xff0…

A股风格因子看板 (2024.4 第4期)

该因子看板跟踪A股风格因子&#xff0c;该因子主要解释沪深两市的市场收益、刻画市场风格趋势的系列风格因子&#xff0c;用以分析市场风格切换、组合风格景 露等。 今日为该因子跟踪第4期&#xff0c;指数组合数据截止日2024-03-31&#xff0c;要点如下 近1年A股风格因子收益走…

【Spring系列】- Spring事务底层原理

实验准备 配置文件 首先在配置文件中配置jdbcTemplate和事务管理器&#xff0c;并且需要开启事务的注解EnableTransactionManagement以及Configuration注解 ComponentScan("com.lyd") EnableTransactionManagement Configuration public class ApplicationConfig …

【C++从练气到飞升】07---内存管理

&#x1f388;个人主页&#xff1a;库库的里昂 ✨收录专栏&#xff1a;C从练气到飞升 &#x1f389;鸟欲高飞先振翅&#xff0c;人求上进先读书。 目录 一、 C/C内存分布 二、 C语言中动态内存管理方式 三、 C中动态内存管理 1. new/delete操作内置类型 2. new和delete操作…

LM-Net

Squeeze-and-Excitation (SE) 作者未提供代码

用海外云手机高效率运营TikTok!

很多做国外社媒运营的公司&#xff0c;想要快速引流&#xff0c;往往一个账号是不够的&#xff0c;多数都是矩阵养号的方式&#xff0c;运营多个TikToK、Facebook、Instagram等账号&#xff0c;慢慢沉淀流量变现&#xff0c;而他们都在用海外云手机这款工具&#xff01; 海外云…

汽车零部件制造迎来智能化升级,3D视觉定位系统助力无人化生产线建设

随着新能源汽车市场的蓬勃发展&#xff0c;汽车零部件制造行业正面临着前所未有的机遇与挑战。为了提高产能和产品加工精度&#xff0c;某专业铝合金汽车零部件制造商决定引进智能生产线&#xff0c;其中&#xff0c;对成垛摆放的变速箱壳体进行机床上料成为关键一环。 传统的上…

CentOS7.9下载及安装教程

1. 下载CentOS7.9 CentOS用的最多的是7.6&#xff0c;7.9是7里面最新的&#xff0c;至于8以上的版本听说没有维护和更新了&#xff0c;这里以7.9为例。 下载&#xff1a;https://mirrors.aliyun.com/centos/7.9.2009/isos/x86_64/ 2. 新建虚拟机 新建虚拟机–>典型(推荐…

Clustering and Projected Clustering with Adaptive Neighbors 论文阅读

1 Abstract 许多聚类方法基于输入数据的相似性矩阵对数据组进行划分。因此&#xff0c;聚类结果高度依赖于数据相似性学习。由于相似性度量和数据聚类通常是分两步进行的&#xff0c;学习到的数据相似性可能不是数据聚类的最佳选择&#xff0c;从而导致次优结果。在本文中&…

LeetCode 面试经典150题 202.快乐数

题目&#xff1a; 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。然后重复这个过程直到这个数变为 1&#xff0c;也可能是 无限循环 但始终变不到 1。如果这个过程 结…

Nacos源码分析,Nacos gRPC服务端通信渠道是如何启动的?

作为SpringCloudAlibaba微服务架构实战派上下册和RocketMQ消息中间件实战派上下册的作者胡弦&#xff0c;我来给大家带来Nacos源码分析的技术文章。 Nacos默认会启动两个gRPC服务端通信渠道&#xff0c;一个用于Nacos集群节点之间的交互&#xff08;GrpcClusterServer&#xf…

Python爬虫之实践(!福利!动态IP免费送!)

Python爬虫是一种强大的工具&#xff0c;它允许我们自动从互联网上收集数据。通过编写Python脚本&#xff0c;我们可以模拟浏览器的行为&#xff0c;发送HTTP请求&#xff0c;获取网页内容&#xff0c;并提取所需的数据。本文将指导你如何进行Python爬虫&#xff0c;包括准备环…

雨天充电桩使用攻略:雨中电动汽车充电必看!

随着电动车的普及&#xff0c;雨天使用充电桩已成为常态。 然而&#xff0c;在恶劣天气条件下充电需格外谨慎&#xff0c;否则可能会带来安全隐患。以下是使用充电桩的安全须知和操作技巧&#xff0c;让您在雨天充电时更加安心&#xff1a; 警惕水患风险&#xff1a;避免在积水…

Python高质量函数编写指南

The Ultimate Guide to Writing Functions 1.视频 https://www.youtube.com/watch?vyatgY4NpZXE 2.代码 https://github.com/ArjanCodes/2022-funcguide Python高质量函数编写指南 1. 一次做好一件事 from dataclasses import dataclass from datetime import datetimedatacl…

Python-VBA函数之旅-classmethod函数

目录 一、装饰器的定义&#xff1a; 二、装饰器类型&#xff1a; 三、装饰器的主要用途&#xff1a; 四、classmethod常用场景&#xff1a; 1、classmethod函数&#xff1a; 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、相关文章&#xff1a; classmethod是 Pyth…

MySQL查询重复数据获取最新数据

方法一&#xff1a; 1055 - Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column ‘se_jck的博客-CSDN博客 这个错误是由于 MySQL 的新版本中默认开启了ONLY_FULL_GROUP_BY模式&#xff0c;即在 GROUP BY 语句中的 SELECT 列表中&…