深入探秘:Linux内存管理与泄漏检测

目录

1. 朋友,了解一下Linux的内存工作原理吧!

1.1. 这张图展示的是一个Linux进程的虚拟内存结构

2. 内存分配与回收:让你的程序跑得更稳健

2.1. 内存分配与内存泄漏

3. 内存泄漏检测代码分析

3.1. 预处理宏替换方法

3.2. 动态链接库挂钩方法

3.3. 代码具体解析

4. 动态链接库挂钩的具体操作

4.1. 创建一个动态链接库用于挂钩 malloc 和 free

4.2. 编写主程序

4.3. 运行程序并加载挂钩库

5. 两种方法的综合比较

6. 总结与建议


1. 朋友,了解一下Linux的内存工作原理吧!

朋友,你有没有遇到过这样的情况?你的Linux程序运行得挺顺利,但突然间发现内存占用飙升,系统变得卡顿,甚至有时候还会崩溃?别担心,今天我们就来聊聊Linux内存的工作原理,以及如何管理和监控内存,防止那些讨厌的内存泄漏。听起来有点复杂?别急,我会用最简单的方式,帮你把这些概念搞清楚。

1.1. 这张图展示的是一个Linux进程的虚拟内存结构

首先,让我们看看一个Linux进程的虚拟内存结构。内存被划分为多个区域,每个区域都有不同的用途。从下到上依次是:

  1. 代码段 (.text)

    • 这里保存了程序的可执行代码。当程序加载时,代码从磁盘读入并开始执行。
    • 所有进程共享相同的代码,通常是只读的,确保了代码的安全性和一致性。
  2. 已初始化数据段 (.data)

    • 这个区域存放已初始化的全局变量和静态变量。
    • 比如说,你在程序中定义的全局数组或静态计数器,就会被存放在这里。
  3. 未初始化数据段 (.bss)

    • 这里保存未初始化的全局变量和静态变量,初始值为0。
    • 运行时会动态分配内存给这些变量,确保它们在使用前被正确初始化。
  4. 运行时堆 (heap)

    • 堆区是用来动态分配内存的地方。每当你调用 malloccallocrealloc 时,内存就会从这里分配出来。
    • brk 指向堆的顶部,随着堆的扩展而增长。
    • 内存泄漏问题通常发生在这里。如果程序分配了内存却没有正确释放,堆区会不断增大,导致内存资源浪费。
  5. 共享库的内存映射区域

    • 这个区域用来存放共享库(比如 .so 文件)的内存映射。
    • 多个进程可以共享相同的内存区域,节省了系统资源。
  6. 用户栈 (stack)

    • 栈区用来存储函数调用时的局部变量和函数参数。
    • 每次函数调用都会在栈中压入一个新的帧,函数返回时弹出这个帧。
    • 栈是从高地址向低地址增长的,内存分配是自动的,由系统管理。
  7. 物理内存、内核代码和数据

    • 这是系统保留的区域,包括物理内存和与进程相关的数据结构(如页表、任务描述符等)。
    • 这些区域对用户进程是不可见的,确保了系统的稳定性和安全性。
  8. 内核虚拟内存

    • 这个区域保存了与内核相关的虚拟内存,供系统操作使用。
    • 它与用户空间是分离的,保证了内核操作的高效和安全。

2. 内存分配与回收:让你的程序跑得更稳健

内存分配与回收是程序运行过程中至关重要的部分。合理的内存管理不仅能提升程序的性能,还能防止那些讨厌的内存泄漏问题。让我们来看看内存分配与回收的基本流程吧!

2.1. 内存分配与内存泄漏

堆区是内存分配的核心。程序员可以通过调用 malloccallocrealloc 等函数,动态地分配和释放内存。然而,问题就出在这里——如果分配了内存却没有释放,或者在程序退出时忘记释放内存,这就会导致内存泄漏。

内存泄漏的危害

  • 内存堆逐渐增大:未释放的内存会让堆区不断膨胀,系统的可用内存减少。
  • 性能下降:内存资源被浪费,程序运行效率降低。
  • 系统崩溃:长时间运行的程序可能耗尽系统内存,导致程序或整个系统崩溃。

相比之下,栈区的内存分配是自动的,由系统管理。每次函数调用时分配内存,函数返回时自动释放。因此,栈区不会出现内存泄漏的问题。但要注意,栈溢出也是一个潜在的问题,尤其是在递归调用过多的情况下。

3. 内存泄漏检测代码分析

要确保你的程序不会出现内存泄漏,检测和监控内存的分配与释放是必不可少的。代码实现了两种内存监控方式,帮助我们检测内存泄漏:

  1. 预处理宏替换(通过宏定义替换 mallocfree
  2. 动态链接库挂钩(通过 dlsym 挂钩 mallocfree

让我们一起来看看这两种方法是如何工作的,以及它们各自的优缺点。

3.1. 预处理宏替换方法

实现原理

通过预处理宏,将代码中的 mallocfree 函数替换为自定义的 nMallocnFree 函数。这种方法在编译阶段完成替换,所有对 mallocfree 的调用都会被替换为带有额外参数(如文件名、函数名、行号)的自定义函数。

代码片段

#if 0void *nMalloc(size_t size, const char *filename, const char *funcname, int line) {void *ptr = malloc(size);char buff[128] = {0};snprintf(buff, 128, "./block/%p.mem", ptr);FILE* fp = fopen(buff, "w");if (!fp) {free(ptr);return NULL;}fprintf(fp, "[+][%s:%s:%d] %p: %ld malloc\n", filename, funcname, line, ptr, size);fflush(fp);fclose(fp);return ptr;
}void nFree(void *ptr, const char *filename, const char *funcname, int line) {char buff[128] = {0};snprintf(buff, 128, "./block/%p.mem", ptr);if (unlink(buff) < 0) { // 文件不存在printf("double free: %p at %s:%s:%d\n", ptr, filename, funcname, line);return;}return free(ptr);
}#define malloc(size) nMalloc(size, __FILE__, __func__, __LINE__)
#define free(ptr)     nFree(ptr, __FILE__, __func__, __LINE__)#endif

工作流程

  1. 替换调用

    • 所有代码中的 malloc(size) 被替换为 nMalloc(size, __FILE__, __func__, __LINE__)
    • 所有代码中的 free(ptr) 被替换为 nFree(ptr, __FILE__, __func__, __LINE__)
  2. 记录分配

    • nMalloc 调用原始的 malloc 分配内存。
    • 创建一个以指针地址命名的文件(例如 ./block/0x7f8c8b.mem),记录分配信息(包括文件名、函数名、行号、指针地址和分配大小)。
  3. 记录释放

    • nFree 尝试删除对应的 .mem 文件。
    • 如果文件不存在,说明可能存在双重释放或释放未分配的内存,输出警告信息。
  4. 内存泄漏检测

    • 程序结束后,检查 ./block/ 目录中是否存在未删除的 .mem 文件,这些文件对应未释放的内存块,表示存在内存泄漏。

优缺点

  • 优点

    • 实现简单,易于理解和维护。
    • 能够在编译时捕获内存分配和释放的调用,记录详细的源代码位置信息,有助于定位内存泄漏的源头。
  • 缺点

    • 需要修改源码,并且在所有使用 mallocfree 的地方生效。
    • 无法监控通过第三方库分配的内存,因为这些库的代码不会被预处理宏替换。
    • 性能开销较大,频繁的文件操作会显著降低程序的运行效率。
    • 不适用于多线程环境,缺乏线程安全机制。
3.2. 动态链接库挂钩方法

实现原理

利用动态链接库的特性,通过 dlsym 函数获取原始的 mallocfree 地址,重定义这两个函数,实现函数挂钩(Hooking)。这种方法不需要修改源代码,可以在运行时拦截所有对 mallocfree 的调用,包括第三方库。

代码片段

// 钩子函数部分typedef void *(*malloc_t)(size_t size);
typedef void (*free_t)(void *ptr);malloc_t malloc_f = NULL;
free_t free_f = NULL;int enable_malloc = 1;
int enable_free = 1;// 将地址转换为符号
void *TranslateToSymbol(void *addr) {Dl_info info;struct link_map *link;dladdr1(addr, &info, (void *)&link, RTLD_DL_LINKMAP);return (void *)(addr - link->l_addr);
}// 重定义 malloc
void *malloc(size_t size) {if (!malloc_f) {malloc_f = (malloc_t)dlsym(RTLD_NEXT, "malloc");}void *ptr = NULL;if (enable_malloc) {enable_malloc = 0;ptr = malloc_f(size);void *caller = __builtin_return_address(0);char buff[128] = {0};snprintf(buff, 128, "./block/%p.mem", ptr);FILE* fp = fopen(buff, "w");if (!fp) {free_f(ptr);return NULL;}fprintf(fp, "[+][%p] %p: %ld malloc\n", TranslateToSymbol(caller), ptr, size);fflush(fp);enable_malloc = 1;} else {ptr = malloc_f(size);}return ptr;
}// 重定义 free
void free(void *ptr) {if (!free_f) {free_f = (free_t)dlsym(RTLD_NEXT, "free");}char buff[128] = {0};snprintf(buff, 128, "./block/%p.mem", ptr);if (unlink(buff) < 0) { // 文件不存在printf("double free or invalid free: %p\n", ptr);return;}return free_f(ptr);
}

工作流程

  1. 获取原始函数地址

    • 使用 dlsym(RTLD_NEXT, "malloc") 获取原始的 malloc 函数地址,并存储在 malloc_f 中。
    • 使用 dlsym(RTLD_NEXT, "free") 获取原始的 free 函数地址,并存储在 free_f 中。
    • RTLD_NEXT 确保获取的是下一个符号,即原始的标准库函数,避免递归调用。
  2. 重定义 malloc

    • 在重定义的 malloc 函数中,调用原始的 malloc 分配内存。
    • 获取调用者的返回地址 caller,通过 __builtin_return_address(0) 实现。
    • 使用 TranslateToSymbol 将调用地址转换为符号地址,便于定位调用源。
    • 创建一个以指针地址命名的 .mem 文件,记录分配信息(包括调用符号、指针地址和分配大小)。
  3. 重定义 free

    • 在重定义的 free 函数中,尝试删除对应的 .mem 文件。
    • 如果文件不存在,说明可能存在双重释放或释放未分配的内存,输出警告信息。
    • 如果文件存在,调用原始的 free 释放内存。
  4. 内存泄漏检测

    • 程序运行结束后,检查 ./block/ 目录中是否存在未删除的 .mem 文件,这些文件对应未释放的内存块,表示存在内存泄漏。

优缺点

  • 优点

    • 无侵入性:无需修改源代码,通过动态链接库即可拦截所有对 mallocfree 的调用,包括第三方库。
    • 全面性:能够监控整个进程中的内存分配和释放,覆盖所有模块。
    • 灵活性:可以在运行时启用或禁用监控,适用于多种运行环境。
  • 缺点

    • 复杂性:实现较为复杂,需要处理函数挂钩、符号解析等问题。
    • 性能开销:同样存在频繁的文件操作带来的性能影响,尤其是在高频率的内存分配和释放场景下。
    • 多线程安全:当前实现缺乏线程同步机制,在多线程环境中可能出现竞态条件,导致数据不一致或文件操作错误。
    • 错误处理:对一些异常情况(如内存分配失败、文件操作失败等)的处理较为简单,可能需要更健壮的错误处理机制。
3.3. 代码具体解析

让我们深入了解一下代码中的关键部分,看看这些函数是如何协同工作的。

#define _GNU_SOURCE
#include <dlfcn.h>
#include <link.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>// #define malloc(size) nMalloc(size, __FILE__, __func__, __LINE__)
// #define free(ptr)     nFree(ptr, __FILE__, __func__, __LINE__)// 预处理宏替换部分(被 #if 0 包含,未启用)#if 0
// ...预处理宏替换的实现...
#else
// 动态链接库挂钩部分typedef void *(*malloc_t)(size_t size);
typedef void (*free_t)(void *ptr);malloc_t malloc_f = NULL;
free_t free_f = NULL;int enable_malloc = 1;
int enable_free = 1;// 将地址转换为符号
void *TranslateToSymbol(void *addr) {Dl_info info;struct link_map *link;dladdr1(addr, &info, (void *)&link, RTLD_DL_LINKMAP);return (void *)(addr - link->l_addr);
}// 重定义 malloc
void *malloc(size_t size) {if (!malloc_f) {malloc_f = (malloc_t)dlsym(RTLD_NEXT, "malloc");}void *ptr = NULL;if (enable_malloc) {enable_malloc = 0;ptr = malloc_f(size);void *caller = __builtin_return_address(0);char buff[128] = {0};snprintf(buff, 128, "./block/%p.mem", ptr);FILE* fp = fopen(buff, "w");if (!fp) {malloc_f(ptr); // 应该使用 malloc_f 而不是 free_freturn NULL;}fprintf(fp, "[+][%p] %p: %ld malloc\n", TranslateToSymbol(caller), ptr, size);fflush(fp);enable_malloc = 1;} else {ptr = malloc_f(size);}return ptr;
}// 重定义 free
void free(void *ptr) {if (!free_f) {free_f = (free_t)dlsym(RTLD_NEXT, "free");}char buff[128] = {0};snprintf(buff, 128, "./block/%p.mem", ptr);if (unlink(buff) < 0) { // 文件不存在printf("double free or invalid free: %p\n", ptr);return;}return free_f(ptr);
}#endifint main() {size_t size = 5;void *p1 = malloc(size);void *p2 = malloc(size * 2);void *p3 = malloc(size * 3);free(p1);free(p3);
}

关键部分解析

  1. 宏定义部分

    • #if 0 包含了预处理宏替换的方法,该部分代码被禁用。
    • #else 部分启用了动态链接库挂钩的方法。
  2. 函数指针定义

    • 定义了函数指针 malloc_ffree_f,用于存储原始的 mallocfree 函数地址。
  3. TranslateToSymbol 函数

    • 使用 dladdr1 将调用地址转换为符号地址,以便记录调用源。
    • dladdr1 返回包含符号信息的 Dl_info 结构和链接映射 link_map
  4. 重定义 malloc 函数

    • 检查 malloc_f 是否已初始化,若未初始化,则使用 dlsym 获取原始的 malloc 函数地址。
    • 使用 __builtin_return_address(0) 获取调用者的返回地址。
    • 调用原始的 malloc 分配内存。
    • 创建一个以指针地址命名的 .mem 文件,记录分配信息(调用符号、指针地址、分配大小)。
    • 通过 enable_malloc 标志防止递归调用 malloc(因为 fopen 可能内部调用 malloc)。
  5. 重定义 free 函数

    • 检查 free_f 是否已初始化,若未初始化,则使用 dlsym 获取原始的 free 函数地址。
    • 尝试删除对应的 .mem 文件。
    • 如果文件不存在,输出双重释放或无效释放的警告。
    • 调用原始的 free 释放内存。
  6. 主函数

    • 分配了三块内存 p1p2p3,并释放了 p1p3
    • 运行结束后,./block/ 目录中应存在 p2 对应的 .mem 文件,提示内存泄漏。

4. 动态链接库挂钩的具体操作

为了更清晰地理解“无需修改源码,只需在运行时加载自定义的动态链接库”,让我们通过一个实际的例子来说明。

4.1. 创建一个动态链接库用于挂钩 mallocfree

文件名memhook.c

#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>// 定义函数指针类型
typedef void *(*malloc_t)(size_t size);
typedef void (*free_t)(void *ptr);// 存储原始函数地址
malloc_t original_malloc = NULL;
free_t original_free = NULL;// 重定义 malloc
void *malloc(size_t size) {if (!original_malloc) {original_malloc = (malloc_t)dlsym(RTLD_NEXT, "malloc");if (!original_malloc) {fprintf(stderr, "Error in `dlsym`: %s\n", dlerror());exit(EXIT_FAILURE);}}void *ptr = original_malloc(size);printf("malloc(%zu) = %p\n", size, ptr); // 监控输出return ptr;
}// 重定义 free
void free(void *ptr) {if (!original_free) {original_free = (free_t)dlsym(RTLD_NEXT, "free");if (!original_free) {fprintf(stderr, "Error in `dlsym`: %s\n", dlerror());exit(EXIT_FAILURE);}}printf("free(%p)\n", ptr); // 监控输出original_free(ptr);
}

编译动态链接库

gcc -shared -fPIC -o memhook.so memhook.c -ldl
4.2. 编写主程序

文件名main.c

#include <stdlib.h>
#include <stdio.h>int main() {size_t size1 = 10;size_t size2 = 20;void *p1 = malloc(size1);void *p2 = malloc(size2);free(p1);free(p2);return 0;
}

编译主程序

gcc -o main main.c
4.3. 运行程序并加载挂钩库

使用 LD_PRELOAD 加载挂钩库

LD_PRELOAD=./memhook.so ./main

输出

malloc(10) = 0x55f8d6b0e2a0
malloc(20) = 0x55f8d6b0e2c0
free(0x55f8d6b0e2a0)
free(0x55f8d6b0e2c0)

运行时加载动态链接库是指在程序启动或运行过程中,通过操作系统的动态链接机制加载额外的库文件,这些库文件可以覆盖或扩展现有的函数实现。

  • LD_PRELOAD 环境变量
    • 这是一个在程序启动时告诉动态链接器(ld.so)优先加载某些动态链接库的机制。
    • 当你设置 LD_PRELOAD=./memhook.so 时,memhook.so 中的函数会被优先加载和绑定。
    • 如果 memhook.so 中定义了与标准库相同的函数(如 mallocfree),它们会覆盖标准库中的实现。

操作步骤

  1. 编写并编译动态链接库:包含你想要挂钩的函数实现。
  2. 使用 LD_PRELOAD:在运行程序时,通过环境变量指定要预加载的动态链接库。
  3. 程序运行时:动态链接器按顺序加载库,优先加载 LD_PRELOAD 指定的库,从而拦截和替换标准库函数。

5. 两种方法的综合比较

让我们来做一个小对比,看看预处理宏替换方法和动态链接库挂钩方法各自的特点:

特性预处理宏替换方法动态链接库挂钩方法
实现方式编译时通过宏替换 mallocfree运行时通过动态链接库挂钩 mallocfree
代码侵入性需要修改源码,所有 mallocfree 调用都被替换无需修改源码,适用于现有二进制文件
覆盖范围仅覆盖被宏替换的代码,无法监控第三方库全局覆盖,监控所有 mallocfree 调用
性能开销高,频繁的文件操作和宏替换带来的开销高,同样存在频繁的文件操作带来的开销
多线程支持缺乏线程安全机制缺乏线程安全机制
灵活性需要重新编译代码,灵活性较低高,可以在不修改源码的情况下进行监控
易用性实现相对简单,但需要源码支持实现较为复杂,但不需要源码支持
错误处理简单,依赖文件系统操作复杂,需要处理动态链接库的相关问题
扩展性受限于宏定义的功能更易扩展,可以添加更多的监控逻辑

6. 总结与建议

通过今天的分享,我们深入了解了Linux内存的工作原理、内存分配与回收的基本流程,以及如何通过两种不同的方法来检测内存泄漏。每种方法都有其独特的优势和不足,关键在于根据你的具体需求来选择最合适的方案。

综合建议

  1. 根据需求选择方法

    • 如果你需要详细追踪特定模块或源码级的内存操作,且能够修改源码,预处理宏替换方法是一个不错的选择。
    • 如果你需要对整个进程进行全面监控,且无法修改源码,动态链接库挂钩方法更为适合。
  2. 优化实现

    • 减少文件操作的频率:采用内存数据结构记录内存分配和释放信息,提升性能。
    • 引入线程同步机制:确保在多线程环境下的安全性,避免竞态条件。
    • 自动生成内存泄漏报告:在程序结束时自动扫描未释放的内存块,生成详细的报告,提升使用体验。
  3. 结合使用成熟工具

    • ValgrindAddressSanitizer 等成熟的内存检测工具功能更强大,经过广泛验证,能够提供更准确和全面的内存分析报告。
    • 在开发和测试阶段,结合使用这些工具,可以大大提升内存管理的准确性和效率,帮助你更快地定位和解决内存问题。
  4. 代码维护与扩展

    • 确保代码的可维护性:处理各种异常情况,提升监控系统的稳定性和可靠性。
    • 逐步扩展监控功能:如记录内存分配堆栈、统计内存使用量等,根据实际需求,逐步增强监控系统的功能。

 参考:

0voice · GitHub

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

2024华为杯E题成品文章已出!

E题高速公路应急车道紧急启用模型 点击链接加入群聊【2024华为杯数学建模助攻资料】&#xff1a;http://qm.qq.com/cgi-bin/qm/qr?_wv1027&kxtS4vwn3gcv8oCYYyrqd0BvFc7tNfhV7&authKeyedQFZne%2BzvEfLEVg2v8FOm%2BWNg1V%2Fiv3H4tcE6X%2FW6lCmkhaSaZV4PwQ%2FOVPDtF%2B&…

kismet和war driving具体准备(仅供无线安全学习)

war driving准备 一台笔记本 一个最好是双频的网卡&#xff0c;单频搜集信号少 我自己买的是http://e.tb.cn/h.grI4EmkDLOqQXHG?tkKZ5g3RVeH6f 如果经济条件允许可以去买大功率天线&#xff08;我买的车载的 大概40db这样子 范围广&#xff09; http://e.tb.cn/h.grCM0CQ6L…

Python Appium自动化操作抖音

1、功能介绍 使用Python和Appium给手机抖音上的同城模块自动评论&#xff0c;主要是通过模拟用户在抖音同城模块的操作&#xff0c;实现自动发送评论的功能。具体步骤如下&#xff1a; - 安装并配置好Python环境&#xff1b; - 安装Appium库&#xff0c;用于自动化操作手机应…

【CSS in Depth 2 精译_038】6.2 CSS 定位技术之:绝对定位

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第一章 层叠、优先级与继承&#xff08;已完结&#xff09;第二章 相对单位&#xff08;已完结&#xff09;第三章 文档流与盒模型&#xff08;已完结&#xff09;第四章 Flexbox 布局&#xff08;已…

大模型深入行业,正从“星星之火”走向“燎原之势”

2024年&#xff0c;当越来越多的企业从赶大模型的潮流与炫大模型的参数规模开始转移到行业落地时&#xff0c;华为携生态伙伴用大模型深耕行业的成果俨然遍地开花。 在9月19日华为全联接大会2024大会上同期举办的华为云AI用户峰会上&#xff0c;华为云为28个创新项目颁发了“A…

应用密码学第一次作业(9.23)

一、Please briefly describe the objectives of information and network security,such as confidentiality, integrity, availability , authenticity , and accountability The objectives of information and network security include: Confidentiality: Protecting se…

快手旗下——Kolors模型部署与使用指南

以下是按照要求重写后的 Kolors 模型部署与使用指南&#xff0c;文章风格偏技术性&#xff0c;但保持简洁和易懂的特点&#xff1a; Kolors 模型部署与使用指南 一、Kolors 简介 Kolors 是由快手 Kolors 团队开发的文本到图像生成模型&#xff0c;基于大规模的潜在扩散技术。…

vue-animate-onscroll动画库(可来回触发动画)

效果展示 ①触发一次动画 触发一次 ②触发多次动画 触发多次 1.什么是vue-animate-onscroll 它是一个 Vue 插件&#xff0c;用于在滚动时触发动画效果。它可以帮助开发者在用户滚动页面时&#xff0c;逐渐展示元素&#xff0c;增强用户体验。基本用法是通过在元素上添加特定的指…

Soul APP创始人张璐团队探讨新世代婚恋观:基于兴趣爱好的“轻相亲”正逐渐流行

近年来,随着社会经济的快速发展和文化观念的不断演变,婚恋观念正在经历显著变化。为深入了解当代年轻人对婚恋的态度与趋势,Soul APP创始人张璐团队与上海大学社会学青年研究团队合作,联合发布了《2024年青年婚恋观念及趋势调查报告》(以下简称“报告”)。该报告基于Soul APP用…

qml PathView入门

PathView是一个用于在用户界面中沿着定义的路径显示和滚动项目的视图组件。它提供了丰富的定制选项&#xff0c;允许开发者创建复杂的动画效果和自定义的滚动行为&#xff0c;特别适用于需要展示非线性排列项目的场景&#xff0c;如图片轮播、自定义滚动菜单等。 一、主要属性 …

[教程]如何在iPhone上启用中国移动/联通/电信RCS消息

目前 苹果已经在 iOS 18 中带来 RCS 富媒体消息的支持&#xff0c;该消息基于网络传递&#xff0c;用户可以通过 RCS 免费将消息发送到其他 iPhone 或 Android 设备。在苹果面向测试版用户推出的 iOS 18.1 Beta 版中&#xff0c;中国网络运营商包括中国移动、中国联通、中国电信…

JavaSE - 面向对象编程05

01 正则表达式 【1】概念&#xff1a;正则表达式是由一些特定字符组成的&#xff0c;代表的是一个规则。 【2】可以用来做什么&#xff1f; ① 用于校验数据格式的合法性 ② 用于在文本中爬取满足要求的内容 ③ 用于String类的replace方法&#xff0c;split方法的替换和分割 …

【学习笔记】Linux系统基础知识3 —— cd命令详解

一、前期准备 1.已经正确安装并成功进入Linux系统 说明&#xff1a;本实验采用的 Redhat 系统&#xff08;因系统不一致&#xff0c;可能部分显示存在差异&#xff09; 二、学习内容 提示&#xff1a;学习Linux系统基础命令 cd 命令详解 1、cd命令 1. 功能说明 cd 命令用…

Simple Calculator(算法初阶,代码基础,“纯”手撕)

简单计算器&#xff1a;仅适用无括号加减乘除&#xff0c;算法初阶&#xff0c;代码基础&#xff0c;不调库或模块“纯”手撕。 (笔记模板由python脚本于2024年09月22日 12:08:02创建&#xff0c;本篇笔记适合喜欢用python解决实际问题的coder翻阅) 【学习的细节是欢悦的历程】…

Qt中多语言的操作(以QtCreator为例)

1、首先&#xff0c;我们在代码中与文本相关的且需要支持多语言的地方&#xff0c;用tr来包含多语言key&#xff08;多语言key是我们自己定义的&#xff09;&#xff0c;如下 //举例 QPushButton* btnnew QPushButton(this); btn->move(20,20); btn->resize(100,50); //…

在 deepin 上除了 Steam,还能怎么玩游戏?

查看原文 前段时间&#xff0c;很多朋友在 deepin 23 上实现了《黑神话&#xff1a;悟空》的通关&#xff0c;那么除了通过 Steam 玩 Windows 游戏之外&#xff0c;还有其他可以使用的游戏平台吗&#xff1f; 回答&#xff0c;当然是可以哒&#xff01; 游戏平台介绍 今天介…

RHCSA认证-Linux(RHel9)-Linux入门

文章目录 概要一、创建、查看和编辑⽂本1.1 输出重定向1.2 vim编辑器1.3 shell 变量1.5 获取帮助 二、管理本地用户和组2.1 描述用户2.2 切换用户和赋权2.3 用户管理2.4 用户组管理2.5 密码策略 三、控制文件访问3.1 列出文件和文件权限3.2 更改文件权限和拥有者3.3 控制默认权…

昆明理工大学MBA工商管理上课方式

--昆工MBA考研、管理与经济学院、125100工商管理、125602项目管理、199管理类综合能力、F009 政治、F008政治项目管理概论

有关在.Net Core中以TEXT类型将Json格式字段存到数据库的学习

导言 在写个值日接口时发现值日表中的值日时段是可以分多段的&#xff0c;想了想可以使用Json类型来存&#xff0c;不过之前没接触过在后端操作Json格式存到数据库的情况&#xff0c;之后学也了解到了一下方法来实现&#xff0c;故记录一下。 过程 从前端到后端再到数据库的 JS…

千亿大数据平台——MySQL大数据优化

一、自增量的作用 1. 唯一性标识 - 为表中的每一行数据提供一个唯一的、自动生成的标识符&#xff0c;确保数据的唯一性和准确性。 2. 简化数据关联 - 在多个表之间进行关联操作时&#xff0c;自增量字段可以作为便捷的关联键&#xff0c;方便建立和维护表之间的关系…