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

内存泄露定位手段(c语言hook malloc相关方式)

如何确定有内存泄露问题,如何定位到内存泄露位置,如何写一个内存泄漏检测工具?

1:概述

内存泄露本质:其实就是申请调用malloc/new,但是释放调用free/delete有遗漏,或者重复释放的问题。

内存泄露会导致的现象:作为一个服务器,长时间运行,内存泄露会导致进程虚拟内存被占用完,导致进程崩溃吧。(堆上分配的内存)

如何规避或者发现内存泄露呢?

===》1:如何检测有内存泄露?(除了内存监控工具htop,耗时,效果不明显)

===》2:如何定位内存泄露的代码问题(少量代码可以阅读代码排除,线上版本呢?)

=====》引入gc

=====》少量代码可以通过排查代码进行定位

=====》已经确定代码有内存泄露,可以用过valgrind/mtrace等市场上已有的一些工具

=====》本质是malloc和free的次数不一致导致,我们通过hook的方式,对malloc和free次数进行统计

2:通过hook的方式检测,定位内存泄露(四种方法)

在生产环境重定位内存泄露的问题,我们可以在产品中增加这些定位手段,通过配置文件开关控制其打开,方便内存泄露定位。

几种不同的方式本质:都是对malloc和free进行hook,增加一些处理进行检测。

2.1:测试代码描述内存泄露

如下代码,从代码看,明显可以看到是有内存泄露的,但是如果看不到代码,或者代码量过多,从运行现象上我们就很难发现了。

#include <stdio.h>
#include <stdlib.h>int main()
{void * ptr1 = malloc(10);void * ptr2 = malloc(20);free(ptr1);void * ptr3 = malloc(30);free(ptr3);return 0;
}//代码运行是没有问题,也没有报错的,但是明显可以看到ptr2是没有内存释放的,如果是服务器有这种代码,长时间运行会有严重问题的。

2.2:通过dlsym库函数对malloc/free进行hook

我在 Linux/unix系统编程手册 这本书中了解相关dlsym函数的使用

要想知道有内存泄露,或者直接定位内存泄露的代码位置,本质还是对调用的malloc/free进行hook, 对调用malloc/free分别增加监控来分析。

使用dlsym库函数,获取malloc/free函数的地址,通过RTLD_NEXT进行比标记(这个标记适用于在其他地方定义的函数同名的包装函数,如在主程序中定义的malloc,代替系统的malloc),实现用我们主程序中malloc代替系统调用malloc.

相关视频推荐

4种内存泄漏的解决方案,每一种背后都有哪些隐藏技术

庞杂的内存问题,如何理出自己的思路出来,让你开发与面试双丰收

2023年最新技术图谱,c++后端的8个技术维度,助力你快速成为大牛

免费学习地址:c/c++ linux服务器开发/后台架构师

需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

 

2.2.1:第一版试着

在调用malloc和free前,使用dlsym函数和RTLD_NEXT标记,获取系统库malloc/free地址,以及用本地定义的malloc/free代替系统调用。

//1:使用void * dlsym(void* handle, char* symbool)函数和handle为RTLD_NEXT标记,对malloc/free进行hook
//2:RTLD_NEXT标记 需要在本地实现 symbool同名函数达到hook功能,即这里要实现malloc/free功能
//3:dlsym()返回的是symbool 参数对应的函数的地址,在同名函数中用该地址实现真正的调用//RTLD_NEXT 是dlsym() 库中的伪句柄,定义_GNU_SOURCE宏才能识别
//可以通过 man dlsym
//测试发现 :必须放在最顶部,不然编译报 RTLD_NEXT没有定义 
#define _GNU_SOURCE
#include <dlfcn.h>  //对应的头文件//第一步功能,确定hook成功,先在我们的hook函数中增加一些打印信息验证
#include <stdio.h>
#include <stdlib.h>//定义相关全局变量,获取返回的函数地址,进行实际调用
typedef void *(*malloc_t)(size_t size);
malloc_t malloc_f = NULL;typedef void (*free_t)(void* p);
free_t free_f = NULL;//要hook的同名函数
void * malloc(size_t size){printf("exec malloc \n");return malloc_f(size);
}void free(void * p){printf("exec free \n");free_f(p);
}
//通过dlsym 对malloc和free使用前进行hook
static void init_malloc_free_hook(){//只需要执行一次if(malloc_f == NULL){malloc_f = dlsym(RTLD_NEXT, "malloc"); //除了RTLD_NEXT 还有一个参数RTLD_DEFAULT}if(free_f == NULL){free_f =  dlsym(RTLD_NEXT, "free");}return ;
}int main()
{init_malloc_free_hook(); //执行一次void * ptr1 = malloc(10);void * ptr2 = malloc(20);free(ptr1);void * ptr3 = malloc(30);free(ptr3);return 0;
}

上述代码是有问题的,现象及定位问题:

hlp@ubuntu:~/mem_test$ gcc dlsym_hook.c -o dlsym_hook -ldl
hlp@ubuntu:~/mem_test$ ./dlsym_hook 
Segmentation fault (core dumped)#使用gdb对问题进行定位 
hlp@ubuntu:~/mem_test$ gdb ./dlsym_hook 
(gdb) b 54    #加断点
Breakpoint 1 at 0x400729: file dlsym_hook.c, line 54.
(gdb) b 28	  #加断点
Breakpoint 2 at 0x400682: file dlsym_hook.c, line 28.
(gdb) r       #开始运行
Starting program: /home/hlp/mem_test/dlsym_hook 
Breakpoint 1, main () at dlsym_hook.c:54
54	    void * ptr1 = malloc(10);
(gdb) c		#单步执行
Continuing.
Breakpoint 2, malloc (size=10) at dlsym_hook.c:28   #第一个mallocy已经执行
28	    printf("exec malloc \n");	
(gdb) c
Continuing.
Breakpoint 2, malloc (size=1024) at dlsym_hook.c:28  #这里的1024不是我们代码里面的,
28	    printf("exec malloc \n");
(gdb) c
Continuing.Breakpoint 2, malloc (size=1024) at dlsym_hook.c:28    #发现malloc 1024一直循环执行 怀疑是printf中会调用malloc,
28	    printf("exec malloc \n");
(gdb) c
Continuing.Breakpoint 2, malloc (size=1024) at dlsym_hook.c:28
28	    printf("exec malloc \n");
(gdb) #通过gdb进行定位时,可以确定,我们hook malloc函数内部调用printf,printf底层其实是有调用malloc,从而printf内部成为递归,一直调用了。
#所以我们需要规避这种现象,让hook函数内部其他业务只执行一次,不要因为第三方库内部机制导致类似问题

增加特定标识,优化上述代码:

//使用标识,使hook的函数内部只执行一次,不因为第三方库原因导致递归现象
#define _GNU_SOURCE
#include <dlfcn.h>  //对应的头文件
#include <stdio.h>
#include <stdlib.h>typedef void *(*malloc_t)(size_t size);
malloc_t malloc_f = NULL;
typedef void (*free_t)(void* p);
free_t free_f = NULL;//定义一个hook函数的标志 使内部逻辑只执行一次
int enable_malloc_hook = 1;
int enable_free_hook = 1;//要hook的同名函数
void * malloc(size_t size){if(enable_malloc_hook) //对第三方调用导致的递归进行规避{enable_malloc_hook = 0;printf("exec malloc \n");enable_malloc_hook = 1;}return malloc_f(size);
}void free(void * p){if(enable_free_hook){enable_free_hook = 0;printf("exec free \n");enable_free_hook = 1;}free_f(p);
}//通过dlsym 对malloc和free使用前进行hook
static void init_malloc_free_hook(){//只需要执行一次if(malloc_f == NULL){malloc_f = dlsym(RTLD_NEXT, "malloc"); //除了RTLD_NEXT 还有一个参数RTLD_DEFAULT}if(free_f == NULL){free_f =  dlsym(RTLD_NEXT, "free");}return ;
}
int main()
{init_malloc_free_hook(); //执行一次void * ptr1 = malloc(10);void * ptr2 = malloc(20);free(ptr1);void * ptr3 = malloc(30);free(ptr3);return 0;
}

上述代码执行成功,现象如下:

hlp@ubuntu:~/mem_test$ gcc dlsym_hook_ok.c -o dlsym_hook_ok -ldl -g
hlp@ubuntu:~/mem_test$ ./dlsym_hook_ok 
exec malloc 
exec malloc 
exec free 
exec malloc 
exec free
#对比执行的 malloc和free次数 可以确定有内存泄露

如何增加行号标识呢?让我们确定到代码位置?

如何确定有内存泄露呢?直接通过代码,识别到malloc/free的对应次数,定位到有代码问题的位置。

2.2.2:能识别到行号,以及有问题代码位置

//我们知道,一般可以通过__LINE__ 标识日志当前行号位置,但是这里不适用
//可以通过 __builtin_return_address 获取上级调用的退出的地址,可以设置时1级,也可以设置是2级别...// 增加打印调用malloc和free位置的信息。 这里打印地址  通过addr2line进行地址和行号转换
#define _GNU_SOURCE
#include <dlfcn.h>  //对应的头文件
#include <stdio.h>
#include <stdlib.h>typedef void *(*malloc_t)(size_t size);
malloc_t malloc_f = NULL;
typedef void (*free_t)(void* p);
free_t free_f = NULL;int enable_malloc_hook = 1;
int enable_free_hook = 1;void * malloc(size_t size){if(enable_malloc_hook) //对第三方调用导致的递归进行规避{enable_malloc_hook = 0;//打印上层调用的地址void *carrer = __builtin_return_address(0);printf("exec malloc [%p ]\n", carrer );enable_malloc_hook = 1;}return malloc_f(size);
}void free(void * p){if(enable_free_hook){enable_free_hook = 0;void *carrer = __builtin_return_address(0);printf("exec free [%p]\n", carrer);enable_free_hook = 1;}free_f(p);
}//通过dlsym 对malloc和free使用前进行hook
static void init_malloc_free_hook(){//只需要执行一次if(malloc_f == NULL){malloc_f = dlsym(RTLD_NEXT, "malloc"); //除了RTLD_NEXT 还有一个参数RTLD_DEFAULT}if(free_f == NULL){free_f =  dlsym(RTLD_NEXT, "free");}return ;
}
int main()
{init_malloc_free_hook(); //执行一次void * ptr1 = malloc(10);void * ptr2 = malloc(20);free(ptr1);void * ptr3 = malloc(30);free(ptr3);return 0;
}

执行结果及查找对应行数:

#执行结果如下
hlp@ubuntu:~/mem_test$ gcc dlsym_hook_addr.c -o dlsym_hook_addr -ldl
hlp@ubuntu:~/mem_test$ ./dlsym_hook_addr 
exec malloc [0x400797 ]
exec malloc [0x4007a5 ]
exec free [0x4007b5]
exec malloc [0x4007bf ]
exec free [0x4007cf]
#可以通过addr2line 获取到对应的代码行数 编译的时候要带 -g
hlp@ubuntu:~/mem_test$ addr2line -fe ./dlsym_hook_addr -a 0x400797
0x0000000000400797
main
/home/hlp/mem_test/dlsym_hook_addr.c:57

2.2.3:通过策略,查找有问题的代码

从上文可以知道,我们通过对malloc和free的hook,可以获得各自malloc和hook的次数。

以及我们可以通过__builtin_return_address 接口获取到实际调用malloc/free的位置。

除此之外,malloc之间有所关联的是申请内存的地址,

汇总:

===》可以思考,通过malloc和free关联的地址作为标识,对malloc和free的次数进行统计即可。

===》可以设计数据结构,对不同地址,malloc的地址和free的地址进行保存,malloc的次数和free的次数进行控制判断

===》这里根据老师的逻辑,用文件的方式进行控制。

测试代码如下:

// malloc和free 之间的关联是申请内存的地址,以该地址作为基准
// malloc时写入一个文件,打印行数等必要信息  free时删除这个文件 通过有剩余文件判断内存泄露
#define _GNU_SOURCE
#include <dlfcn.h>  //对应的头文件
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>typedef void *(*malloc_t)(size_t size);
malloc_t malloc_f = NULL;
typedef void (*free_t)(void* p);
free_t free_f = NULL;int enable_malloc_hook = 1;
int enable_free_hook = 1;#define MEM_FILE_LENGTH 40
void * malloc(size_t size){if(enable_malloc_hook) //对第三方调用导致的递归进行规避{enable_malloc_hook = 0;//实际的内存申请,根据该地址写文件和free 相互关联void *ptr =malloc_f(size);//打印上层调用的地址void *carrer = __builtin_return_address(0);printf("exec malloc [%p ]\n", carrer );//通过写入文件的方式 对malloc和free进行关联  malloc时写入文件char file_buff[MEM_FILE_LENGTH] = {0};sprintf(file_buff, "./mem/%p.mem", ptr);//打开文件写入必要信息 使用前创建目录级别FILE *fp = fopen(file_buff, "w");fprintf(fp, "[malloc addr : +%p ] ---->mem:%p  size:%lu \n",carrer, ptr, size);fflush(fp); //刷新写入文件enable_malloc_hook = 1;return ptr;}else{return malloc_f(size);}
}void free(void * p){if(enable_free_hook){enable_free_hook = 0;void *carrer = __builtin_return_address(0);//free时删除文件  根据剩余文件判断内存泄露char file_buff[MEM_FILE_LENGTH] = {0};sprintf(file_buff, "./mem/%p.mem", p);//删除文件 根据malloc对应的指针if(unlink(file_buff) <0){printf("double free: %p, %p \n", p, carrer);}//这里的打印实际就没意义了printf("exec free [%p]\n", carrer);free_f(p);enable_free_hook = 1;}else{free_f(p);}
}//通过dlsym 对malloc和free使用前进行hook
static void init_malloc_free_hook(){//只需要执行一次if(malloc_f == NULL){malloc_f = dlsym(RTLD_NEXT, "malloc"); //除了RTLD_NEXT 还有一个参数RTLD_DEFAULT}if(free_f == NULL){free_f =  dlsym(RTLD_NEXT, "free");}return ;
}
int main()
{init_malloc_free_hook(); //执行一次void * ptr1 = malloc(10);void * ptr2 = malloc(20);free(ptr1);void * ptr3 = malloc(30);free(ptr3);return 0;
}

执行结果:

# 这里的打印只是为了理解  没有多大意义,真正的分析还得依靠文件目录
hlp@ubuntu:~/mem_test$ mkdir mem
hlp@ubuntu:~/mem_test$ gcc dlsym_hook_file.c -o dlsym_hook_file -ldl -g
hlp@ubuntu:~/mem_test$ ./dlsym_hook_file 
exec malloc [0x400ad5 ]
exec malloc [0x400ae3 ]
exec free [0x400af3]
exec malloc [0x400afd ]
exec free [0x400b0d]
hlp@ubuntu:~/mem_test$ cd mem/# 这里在我们的目标目录下  看到有文件存在,说明存在内存泄露
hlp@ubuntu:~/mem_test/mem$ ls
0xe38680.mem
# 通过文件中的日志信息,对其进行分析,找到问题代码位置
hlp@ubuntu:~/mem_test/mem$ cat 0xe38680.mem 
[malloc addr : +0x400ae3 ] ---->mem:0xe38680  size:20 
hlp@ubuntu:~/mem_test/mem$ cd ../
#通过地址转换 找到我们有问题代码 没有释放的代码位置 
hlp@ubuntu:~/mem_test$ addr2line -fe ./dlsym_hook_file 0x400ae3
main
/home/hlp/mem_test/dlsym_hook_file.c:85

2.3:通过宏定义的方式对malloc/free进行hook

本质其实就是对系统调用的malloc/free进行替换,调用我们的目标方法,可以通过hook或者重载的方法实现。

使用宏定义的方式,实现malloc/free的替换。

#include <stdio.h>
#include <stdlib.h>//不能放在这里  放在这里  会对malloc_hook 和 free_hook 内部实际调用的也替换,就形成的递归调用了 并且无法规避
//#define malloc(size) 	malloc_hook(size, __FILE__, __LINE__)
//#define free(p) 		free_hook(p,  __FILE__, __LINE__)#define MEM_FILE_LENGTH 40
//实现目标函数
void *malloc_hook(size_t size, const char* file, int line)
{//这里还是通过文件的方式进行识别void *ptr =malloc(size);char file_name_buff[MEM_FILE_LENGTH] = {0};sprintf(file_name_buff, "./mem/%p.mem", ptr);//打开文件写入必要信息 使用前创建目录级别FILE *fp = fopen(file_name_buff, "w");fprintf(fp, "[file:%s  line:%d ] ---->mem:%p  size:%lu \n",file, line, ptr, size);fflush(fp); //刷新写入文件printf("exec malloc [%p:%lu], file: %s, line:%d \n", ptr, size, file, line );return ptr;
}void free_hook(void *p, const char* file, int line)
{char file_name_buff[MEM_FILE_LENGTH] = {0};sprintf(file_name_buff, "./mem/%p.mem", p);if(unlink(file_name_buff) <0){printf("double free: %p, file: %s. line :%d \n", p, file, line);}//这里的打印实际就没意义了printf("exec free [%p], file: %s line:%d \n", p, file, line);free(p);
}
//宏定义实现代码中调用malloc/free时调用我们目标函数
#define malloc(size) 	malloc_hook(size, __FILE__, __LINE__)
#define free(p) 		free_hook(p,  __FILE__, __LINE__)int main()
{//init_malloc_free_hook(); //执行一次void * ptr1 = malloc(10);void * ptr2 = malloc(20);free(ptr1);void * ptr3 = malloc(30);free(ptr3);return 0;
}

代码执行如下:

hlp@ubuntu:~/mem_test$ gcc define_hook.c -o define_hook
hlp@ubuntu:~/mem_test$ ./define_hook 
exec malloc [0x1f91010:10], file: define_hook.c, line:47 
exec malloc [0x1f92680:20], file: define_hook.c, line:48 
exec free [0x1f91010], file: define_hook.c line:50 
exec malloc [0x1f938e0:30], file: define_hook.c, line:52 
exec free [0x1f938e0], file: define_hook.c line:53 
#通过目录下文件 以及文件内容  可以分析到代码有问题的行号
#注意  测试前最好清空目录下的文件 
hlp@ubuntu:~/mem_test$ ls ./mem
0x1f92680.mem
hlp@ubuntu:~/mem_test$ cat ./mem/0x1f92680.mem 
[file:define_hook.c  line:48 ] ---->mem:0x1f92680  size:20

2.4:_libc_malloc

对malloc进行劫持 使用实际的内存申请_libc_malloc 进行申请内存以及其他控制

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//实际内存申请的函数
extern void *__libc_malloc(size_t size);
int enable_malloc_hook = 1;extern void __libc_free(void* p);
int enable_free_hook = 1;// func --> malloc() { __builtin_return_address(0)}
// callback --> func --> malloc() { __builtin_return_address(1)}
// main --> callback --> func --> malloc() { __builtin_return_address(2)}//calloc, realloc
void *malloc(size_t size) {if (enable_malloc_hook) {enable_malloc_hook = 0;void *p = __libc_malloc(size); //重载达到劫持后 实际内存申请void *caller = __builtin_return_address(0); // 0char buff[128] = {0};sprintf(buff, "./mem/%p.mem", p);FILE *fp = fopen(buff, "w");fprintf(fp, "[+%p] --> addr:%p, size:%ld\n", caller, p, size);fflush(fp);//fclose(fp); //freeenable_malloc_hook = 1;return p;} else {return __libc_malloc(size);}return NULL;
}void free(void *p) {if (enable_free_hook) {enable_free_hook = 0;char buff[128] = {0};sprintf(buff, "./mem/%p.mem", p);if (unlink(buff) < 0) { // no existprintf("double free: %p\n", p);}__libc_free(p);// rm -rf p.memenable_free_hook = 1;} else {__libc_free(p);}
}int main()
{//init_malloc_free_hook(); //执行一次void * ptr1 = malloc(10);void * ptr2 = malloc(20);free(ptr1);void * ptr3 = malloc(30);free(ptr3);return 0;
}

代码运行及分析如下:

hlp@ubuntu:~/mem_test$ gcc libc_hook.c -o libc_hook -g
hlp@ubuntu:~/mem_test$ ./libc_hook 
#通过查看目录下生成的文件   可以知道有内存泄露 通过日志内部信息  使用addr2line 通过打印地址找到对应的代码位置
hlp@ubuntu:~/mem_test$ cat ./mem/0x733270.mem 
[+0x4009f7] --> addr:0x733270, size:20
hlp@ubuntu:~/mem_test$ addr2line -fe ./libc_hook 0x4009f7
main
/home/hlp/mem_test/libc_hook.c:69   #找到问题代码位置

2.5:mem_trace

原理:glic提供__malloc_hook, __realloc_hook, __free_hook可以实现hook自定义malloc/free函数

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <malloc.h>/* #include <malloc.h>void *(*__malloc_hook)(size_t size, const void *caller);void *(*__realloc_hook)(void *ptr, size_t size, const void *caller);void *(*__memalign_hook)(size_t alignment, size_t size,const void *caller);void (*__free_hook)(void *ptr, const void *caller);void (*__malloc_initialize_hook)(void);void (*__after_morecore_hook)(void);*/
//#include <mcheck.h>typedef void *(*malloc_hook_t)(size_t size, const void *caller);
typedef void (*free_hook_t)(void *p, const void *caller);malloc_hook_t 	old_malloc_f = NULL;
free_hook_t 	old_free_f = NULL;
int replaced = 0;void mem_trace(void);
void mem_untrace(void);void *malloc_hook_f(size_t size, const void *caller) {mem_untrace();void *ptr = malloc(size);//printf("+%p: addr[%p]\n", caller, ptr);char buff[128] = {0};sprintf(buff, "./mem/%p.mem", ptr);FILE *fp = fopen(buff, "w");fprintf(fp, "[+%p] --> addr:%p, size:%ld\n", caller, ptr, size);fflush(fp);fclose(fp); //freemem_trace();return ptr;
}void *free_hook_f(void *p, const void *caller) {mem_untrace();//printf("-%p: addr[%p]\n", caller, p);char buff[128] = {0};sprintf(buff, "./mem/%p.mem", p);if (unlink(buff) < 0) { // no existprintf("double free: %p\n", p);return NULL;}free(p);mem_trace();
}
//对__malloc_hook 和__free_hook 重赋值 
void mem_trace(void) { //mtracereplaced = 1;      old_malloc_f = __malloc_hook; //malloc --> old_free_f = __free_hook;__malloc_hook = malloc_hook_f;__free_hook = free_hook_f;
}//还原 __malloc_hook 和__free_hook
void mem_untrace(void) {__malloc_hook = old_malloc_f;__free_hook = old_free_f;replaced = 0;
}int main()
{mem_trace();  //mtrace();    //进行hook劫持void * ptr1 = malloc(10);void * ptr2 = malloc(20);free(ptr1);void * ptr3 = malloc(30);free(ptr3);mem_untrace();  //muntrace(); //取消劫持return 0;
}

这里编译的时候有一些警告,但是编译成功了,

除此之外 相关资料可以用 man __malloc_hook 去了解

hlp@ubuntu:~/mem_test$ gcc 3.c -o 3 -g
hlp@ubuntu:~/mem_test$ ./3
hlp@ubuntu:~/mem_test$ cat mem/0x1789030.mem 
[+0x400ab2] --> addr:0x1789030, size:20
hlp@ubuntu:~/mem_test$ addr2line -fe ./3 0x400ab2
main
#可以确定问题代码位置
/home/hlp/mem_test/3.c:79

3:总结:

内存泄露的本质是,在堆上分配内存,使用malloc/calloc/realloc 以及内存的释放free不匹配导致的。

怎么检测,定位内存泄露?:

===》实际上对内存管理的相关函数进行劫持(hook),增加一些必要信息供我们分析。

===》不同的方案,其实就是劫持(hook的方式不一样)

===》可以使用重载,宏定义,操作系统提供的hook方式(__malloc_hook)等不通的方案

===》在hook的基础上,要进行分析,需要用相关的策略,这里用的多个文件,可以用数据结构管理。

===》除此之外,__builtin_return_address函数可以获取函数调用地址,以及addr2line命令对地址和代码行数进行转换,确定问题代码位置。

===》编译的时候,要加-g,addr2line才可用。

这里只是简单的内存泄露hook的一些方案demo,有关多线程等细节再实际项目中也需要考虑。

相关文章:

内存泄露定位手段(c语言hook malloc相关方式)

如何确定有内存泄露问题&#xff0c;如何定位到内存泄露位置&#xff0c;如何写一个内存泄漏检测工具&#xff1f; 1&#xff1a;概述 内存泄露本质&#xff1a;其实就是申请调用malloc/new&#xff0c;但是释放调用free/delete有遗漏&#xff0c;或者重复释放的问题。 内存…...

STM32 CAN波特率计算

STM32 CAN波特率计算简介CAN总线收发&#xff0c;中断方式接收配置代码部分reference简介 CAN通信帧共分为数据帧、远程帧、错误帧、过载帧和帧间隔&#xff0c;本文这里以数据帧为例。 显性电平对应逻辑0&#xff0c;CAN_H和CAN_L之差为2.5V左右。而隐性电平对应逻辑1&#x…...

C/C++ 中#define 的妙用,让代码更美一些

C/C 中#define 的妙用&#xff0c;让代码更美一些 flyfish 1 数值类型输出易读的字符串形式 例如使用enum定义一些错误值&#xff0c;想要将数值类型的错误&#xff0c;输出易读的字符串形式 重要的一句代码 #define MAKE_PAIR(val) std::make_pair(val, #val)可以看到 #va…...

Linux文件系统操作与磁盘管理

查看磁盘和目录的容量 使用 df 命令查看磁盘的容量 df在实验楼的环境中你将看到如下的输出内容&#xff1a; 但在实际的物理主机上会更像这样&#xff1a; 物理主机上的 /dev/sda2 是对应着主机硬盘的分区&#xff0c;后面的数字表示分区号&#xff0c;数字前面的字母 a 表示…...

【Python】批量采集原神表情包~

嗨害大家好鸭~我是小熊猫(✿◡‿◡) 最近迷上了原神&#xff0c; 不自觉中就很喜欢保存广大旅行者制作的表情包~ 真的很有意思诶~ 源码资料电子书:点击此处跳转文末名片获取 一个个保存的话&#xff0c;好像效率很低嘛… 那我就发挥我小熊猫的老本行直接给把他们全部采集下…...

C语言基本语法注释类型关键字

C 基本语法 标识符 给变量所取的名字叫变量名&#xff0c;定义变量的名字需要遵循标识符的命名规则。 标识符是用来标识变量、符号常量、数组、函数、文件等名字的有效字符序列。 标识符的命名规则&#xff1a; 1.只能由字母、数字和下划线组成&#xff08;例如&#xff1a…...

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

C入门&#xff08;二&#xff09; 作者&#xff1a;小卢 专栏&#xff1a;《C》 喜欢的话&#xff1a;世间因为少年的挺身而出&#xff0c;而更加瑰丽。 ——《人民日报》 1.引用 1.1.引用的概念及应用 引用&#xff08;&&#xff09; 引用不是新定义一个变量&#xff0…...

qt qchart学习

Qt Charts主要由QChartView、QChart、QLegend图例、坐标轴(由QAbstractAxis子类实现)、**数据源(由QAbstractSeries子类实现)**等组成使用QChart的前期准备1. Qt5.9及以上版本&#xff1b;2. .pro文件中添加QT charts3. 在使用QChart的各个控件之前&#xff0c;引用头文件并必…...

手工布署 java 项目

新建一个java springboot项目 maven 这是一个非常简易的 springBoot 的项目 使用 maven 的 package 工具进行打包 把包上传到 linux 的机器上&#xff0c; 确保 linux 机器上安装了 java jdk工具&#xff0c; 并且配置好了 JAVA_HOME 注意&#xff0c;helloworld 默认的是要使…...

《设计模式》观察者模式

《设计模式》观察者模式 观察者模式是一种行为型设计模式&#xff0c;它定义了一种一对多的依赖关系&#xff0c;让多个观察者对象可以同时监听和相应被观察者对象的状态变化&#xff0c;以达到解耦和复用的目的。观察者模式的优点如下&#xff1a; 解耦&#xff1a;观察者模…...

基于SpringBoot的外卖项目(详细开发过程)

基于SpringBootMyBatisPlus的外卖项目1、软件开发整体介绍软件开发流程角色分工2、外卖项目介绍项目介绍产品展示后台系统管理移动端技术选型功能结构角色3、开发环境的搭建开发环境说明建库建表Maven项目搭建项目的目录结构pom.xmlapplication.ymlReggieApplication启动类配置…...

ChatGPT 研发传言席卷互联网公司,这会是一门好生意吗?

ChatGPT&#xff08;也称GPT-3&#xff09;是一种基于人工智能的自然语言生成模型&#xff0c;由OpenAI团队开发。它是GPT系列模型的最新版本&#xff0c;于2020年6月发布。ChatGPT的由来GPT-1是在2018年发布的第一个版本&#xff0c;使用了12亿个参数。随后&#xff0c;GPT-2在…...

获取servlet转发和响应重定向的方式是什么?

&#xff08;1&#xff09; 重定向和转发的区别 1&#xff09;重定向是浏览器发送请求并受到响应以后再次向一个新地址发请求&#xff1b;转发是服务器受到请求后为了完成响应转到一个新的地址。 2&#xff09;重定向中有两次请求对象&#xff0c;不共享数据&#xff1b;转发…...

jvm知识点

jvm面试总结 类加载机制? 如何把类加载到jvm中 ? 装载–>链接–>初始化–>使用–>卸载 装载: ClassFile–>字节流–>类加载器将字节流所代表的静态结构转化为方法区的运行时数据结构在我们的堆中生成一个代表这个类的java.lang.Class对象 链接: 验证–…...

MoveIT Noetic控制真实机械臂

文章目录 环境概述配置修改编写Action Server执行问题故障解决参考接前几篇: ROS MoveIT1(Noetic)安装总结 Solidworks导出为URDF用于MoveIT总结(带prismatic) MoveIT1 Assistant 总结 MoveIT Rviz和Gazebo联合仿真 环境 Ubuntu20.04;ROS1 Noetic;VMware...

如何快速入门编程

最近回答了很多小伙伴的问题&#xff0c;讲到如何快速入门编程&#xff1f;如何更好地学习视觉编程&#xff1f;如何提高编程技能&#xff1f;下面就和你聊聊&#xff0c;要做到这些&#xff0c;应该从哪些方面入手&#xff1f;询问他人我问过工程师们这些最基础的问题&#xf…...

java的反射Reflect

文章目录定义classClass获取一个类的类对象反射的具体步骤1.加载类类API2.实例化3.获取1)获取类中方法2)获取构造方法3)获取当前类的属性4.方法调用应用1.遍历对象属性&#xff0c;进行赋值定义 反射是操作其属性和方法从编码期决定转为在运行期决定 编码期决定&#xff1a;创…...

常用设计模式总结

复习到设计模式的时候写的一些demo代码 回头可以看看 单例的几种比较简单就没写了&#xff0c;专栏有 目录 观察者&#xff08;发布--订阅模式&#xff09;模式&#xff0c;多个对象依赖于一个对象&#xff0c;或者多对多 工厂模式&#xff1a;主要是封装了对象的创建&…...

【算法基础】一维前缀和 + 二维前缀和

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;【C/C】算法 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵 希望大佬指点一二 如果文章对你有…...

Kafka消费分组和分区分配策略

Kafka消费分组&#xff0c;消息消费原理 同一个消费组里的消费者不能消费同一个分区&#xff0c;不同消费组的消费组可以消费同一个分区 &#xff08;即同一个消费组里面的消费者只能在一个分区中&#xff09; Kafka分区分配策略 问题 用过 Kafka 的同学用过都知道&#xf…...

犹太教、基督教、伊斯兰教的区别与联系

一、犹太教、基督教、伊斯兰教的简明关系图二、犹太教、基督教、伊斯兰教的主要区别注&#xff1a;弥赛亚&#xff08;希伯莱语&#xff09;就是基督&#xff08;希腊语&#xff09;&#xff0c;意思是“救世主”。注&#xff1a;伊斯兰教的观点是&#xff1a;穆罕默德不是伊斯…...

华为OD机试 - 打印文件(Python) | 机试题+算法思路+考点+代码解析 【2023】

打印文件 题目 有 5 台打印机打印文件,每台打印机有自己的待打印队列。 因为打印的文件内容有轻重缓急之分,所以队列中的文件有1~10不同的优先级,其中数字越大优先级越高。 打印机会从自己的待打印队列中选择优先级最高的文件来打印。 如果存在两个优先级一样的文件,则选…...

网络工程师必备知识点

作为网络工程师&#xff0c;您将负责设计、部署和维护计算机网络系统。这包括构建、配置和管理网络设备&#xff0c;如交换机、路由器、防火墙等&#xff0c;并确保网络系统能够高效地运行。您需要了解计算机网络的各个层次、协议、标准和技术&#xff0c;包括TCP/IP、DNS、HTT…...

数据在内存中的存储【下篇】

文章目录⚙️3.浮点型在内存中的存储&#x1f529;3.1.一个例子&#x1f529;3.2.浮点数的存储规则&#x1f529;3.3.例题解析⚙️3.浮点型在内存中的存储 &#x1f529;3.1.一个例子 &#x1f534;浮点数存储的例子&#xff1a;&#x1f447; int main() {int n 9;float* …...

前端开发项目规范写法介绍

1. 基本原则 结构、样式、行为分离 尽量确保文档和模板只包含 HTML 结构,样式都放到样式表里,行为都放到脚本里。 缩进 统一两个空格缩进(总之缩进统一即可),不要使用 Tab 或者 Tab、空格混搭。 文件编码 使用不带 BOM 的 UTF-8 编码。 在 HTML中指定编码 <meta c…...

百万医疗险是什么

一、百万医疗险是什么 从名字可以看出&#xff0c;这是一款医疗险。因为保额高&#xff0c;最高能报销百万&#xff0c;所以叫百万医疗险。 二、百万医疗险有什么用 可以报销被保险人因意外伤害和疾病导致的医疗费用 三、如何挑选 虽然高达几百万的保额&#xff0c;但保额却并非…...

矩阵中的路径 AcWing (JAVA)

请设计一个函数&#xff0c;用来判断在一个矩阵中是否存在一条路径包含的字符按访问顺序连在一起恰好为给定字符串。 路径可以从矩阵中的任意一个格子开始&#xff0c;每一步可以在矩阵中向左&#xff0c;向右&#xff0c;向上&#xff0c;向下移动一个格子。 如果一条路径经过…...

使用终端工具给你的电脑发送弹窗提醒

大家好&#xff0c;我是良许。 现在人手一部智能手机&#xff0c;这些智能手机都有个非常实用的功能&#xff0c;那就是弹窗提醒。当我们收到短信&#xff0c;或者微信信息时&#xff0c;手机就会弹窗显示信息的大致内容。有了这个功能你就不会错过重要信息了。 电脑上也有类…...

SpringCloud Alibaba 之Nacos集群部署-高可用保证

文章目录Nacos集群部署Linux部署docker部署&#xff08;参考待验证&#xff09;Nacos 集群的工作原理Nacos 集群中 Leader 节点是如何产生的Nacos 节点间的数据同步过程官方推荐用户把所有服务列表放到一个vip下面&#xff0c;然后挂到一个域名下面。http://nacos.com:port/ope…...

Scala集合详解(第七章:集合、数组、列表、set集合、map集合、元组、队列、并行)(尚硅谷笔记)

集合第七章:集合7.1 集合简介7.1.1 不可变集合继承图7.1.2 可变集合继承图7.2 数组7.2.1 不可变数组7.2.2 可变数组7.2.3 不可变数组与可变数组的转换7.2.4 多维数组7.3 列表 List7.3.1 不可变 List7.3.2 可变 ListBuffer7.4 Set 集合7.4.1 不可变 Set7.4.2 可变 mutable.Set7.…...