C++系统编程层(System Programming Layer)

news2024/7/7 16:29:09

1. C++ 中内存分配情况

内存分配的总体顺序

加载时:

  1. 代码区
  2. 全局/静态存储区
  3. 常量存储区

运行时:

  1. :随着函数调用和返回动态分配和释放。
  2. :程序运行过程中根据需要动态分配和释放。
  • 特性:动态申请的内存区(手动管理,内存较大,程序运行时动态分配,手动释放)。
  • 特性:局部变量和函数参数,局部常量(编译器自动管理,内存较小,函数调用时分配,函数返回时自动释放)。
全局/静态存储区
  • 特性:全局变量和静态变量(程序启动前分配,整个程序运行期间存在,程序终止时由操作系统回收)。
常量存储区
  • 特性:常量数据,字面符常量(如 "Hello, World!"),数值常量(如 23)等(只读,编译期分配内存,程序加载时分配,程序终止时由操作系统回收)。
代码区
  • 特性:程序的二进制代码,也就是可执行指令(只读,操作系统和编译器操作,程序加载时分配,程序终止时由操作系统回收)。
堆栈的区别:

 栈是由编译器和操作系统自动管理内存分配和释放,采用先进后出(LIFO)原则,从高地址向低地址增长,一般用于存储局部常量和局部变量。栈的内存较小,分配和释放速度较快,并且内存是连续的。

而堆由程序员手动管理内存分配和释放,内存分配可能是不连续的,内存空间较大,一般通过 mallocnew 进行内存分配。

类内存布局大小
  • 空类的大小:1字节,为了确保每个实例都有唯一的地址。
  • 含有虚函数的类的大小:包含一个指向虚函数表的指针(vptr),其大小在32位系统中为4字节,在64位系统中为8字节。
  • 含有静态数据成员的类的大小:静态成员变量不影响实例大小,大小仍为1字节。
  • 含有非静态数据成员的类的大小:大小等于非静态成员变量的总大小。
  • 含有静态和非静态数据成员的类的大小:静态成员变量不影响实例大小,大小等于非静态成员变量的总大小。
#include <iostream>

int main() {
    // 示例1: 空类
    class A1 {};
    std::cout << "sizeof(A1) = " << sizeof(A1) << " // 空类的大小为1,因为实例化时需要一个独一无二的地址" << std::endl;

    // 示例2: 含有虚函数的类
    class A2 {
    public:
        virtual void Fun() {}
    };
    std::cout << "sizeof(A2) = " << sizeof(A2) << " // 含有虚函数的类的大小为4(32位)或8(64位),因为有一个指向虚函数表的指针(vptr)" << std::endl;

    // 示例3: 含有静态数据成员的类
    class A3 {
    public:
        static int a;
    };
    std::cout << "sizeof(A3) = " << sizeof(A3) << " // 含有静态成员的类的大小为1,因为静态成员不影响实例大小" << std::endl;

    // 示例4: 含有非静态数据成员的类
    class A4 {
    public:
        int a;
    };
    std::cout << "sizeof(A4) = " << sizeof(A4) << " // 含有一个int非静态成员的类的大小为4(32位和64位系统中int的大小)" << std::endl;

    // 示例5: 含有静态和非静态数据成员的类
    class A5 {
    public:
        static int a;
        int b;
    };
    std::cout << "sizeof(A5) = " << sizeof(A5) << " // 含有一个静态成员和一个int非静态成员的类的大小为4,因为静态成员不影响实例大小" << std::endl;

    return 0;
}

2.符号表:

符号表(Symbol Table)是编译器在编译过程中维护的一种关键数据结构,

用于存储程序中各种符号(如变量、函数、对象等)的相关信息。

  1. 符号存储:符号表记录程序中所有的符号,包括变量名、函数名、类名等。每个符号都对应一个条目,条目中保存了该符号的详细信息。

  2. 符号信息:每个符号的相关信息通常包括:

    • 名称:符号的名称,即标识符。
    • 类型:符号的数据类型,例如整数、浮点数、字符、数组、函数等。
    • 作用域:符号的作用域范围,决定了符号在哪些代码段中可见。例如,局部变量的作用域仅限于函数内部,而全局变量在整个程序中都可见。
    • 内存地址:符号在内存中的存储位置,编译器在生成目标代码时为每个符号分配内存地址。
    • 其他属性:包括符号的初始值、参数个数(对于函数)、大小(对于数组和结构体)等。
  3. 作用域管理:符号表支持多层次的作用域管理。通常,符号表以嵌套的形式组织,形成一个层次结构,以便编译器能够处理不同层次的符号作用域。例如,当进入一个新的函数或代码块时,编译器会创建一个新的作用域,并在退出时销毁该作用域。

  4. 语义检查:在编译过程中,符号表用于语义分析阶段,编译器通过查询符号表来验证变量和函数的使用是否合法。例如,检查变量是否已声明、函数调用的参数类型是否匹配等。

  5. 代码生成:在代码生成阶段,编译器使用符号表为每个符号分配内存地址,并在目标代码中插入正确的内存访问指令。

符号表的实现通常采用散列表(Hash Table)、链表(Linked List)、树(Tree)等数据结构,以便高效地插入、删除和查找符号信息。

符号表内容

​
#include <stdio.h>

// 符号表条目:globalVar
// 名称:globalVar
// 类型:int
// 作用域:全局
// 内存地址:假设为 0x100
// 初始值:10
int globalVar = 10;

// 符号表条目:foo
// 名称:foo
// 类型:void
// 作用域:全局
// 参数:param
void foo(int param) {
    // 符号表条目:param
    // 名称:param
    // 类型:int
    // 作用域:foo 函数内部
    // 内存地址:栈帧中的偏移量为 4

    // 符号表条目:localVar
    // 名称:localVar
    // 类型:int
    // 作用域:foo 函数内部
    // 内存地址:栈帧中的偏移量为 8
    int localVar = 20;

    // 语义检查:验证 globalVar、param 和 localVar 是否已声明
    // 全局变量 globalVar 已声明,参数 param 已声明,局部变量 localVar 已声明
    // 更新 globalVar 的值
    globalVar = globalVar + param + localVar;
}

// 符号表条目:main
// 名称:main
// 类型:int
// 作用域:全局
int main() {
    // 符号表条目:foo 函数调用
    foo(5);

    // 输出 globalVar 的值
    printf("%d\n", globalVar);

    return 0;
}

​

代码生成(作为注释放在代码中)

; 假设 globalVar 的内存地址为 0x100
; 假设 param 在栈帧中的偏移量为 4
; 假设 localVar 在栈帧中的偏移量为 8

foo:
    push ebp            ; 保存旧的基址指针
    mov ebp, esp        ; 设置新的基址指针
    sub esp, 4          ; 为 localVar 分配空间
    mov dword ptr [ebp-8], 20  ; localVar = 20

    mov eax, dword ptr [0x100] ; 将 globalVar 的值加载到 eax
    add eax, dword ptr [ebp+4] ; 将 param 的值加到 eax
    add eax, dword ptr [ebp-8] ; 将 localVar 的值加到 eax
    mov dword ptr [0x100], eax ; 将结果存回 globalVar

    mov esp, ebp        ; 恢复栈指针
    pop ebp             ; 恢复基址指针
    ret                 ; 返回

main:
    ; 准备调用 foo(5)
    push 5              ; 压入参数 5
    call foo            ; 调用 foo 函数
    add esp, 4          ; 清理栈

    ; 调用 printf 函数
    push dword ptr [0x100] ; 压入 globalVar 的值
    push offset format     ; 压入格式化字符串地址
    call printf            ; 调用 printf 函数
    add esp, 8             ; 清理栈

    ; 返回 0
    mov eax, 0
    ret

符号表的存储位置

编译阶段的符号表

  1. 编译时符号表
    • 存储位置:在编译阶段,符号表通常存储在编译器的内部数据结构中,存储在编译器进程的内存中。
    • 作用:用于记录源代码中的变量名、函数名、作用域、类型信息等。编译器利用符号表进行语法和语义分析,检查变量是否已声明、类型是否匹配等。

链接阶段的符号表

  1. 链接时符号表
    • 存储位置:在链接阶段,符号表信息会存储在目标文件(如 .obj.o 文件)和可执行文件(如 .exe 文件)的文件头中,或在调试信息段中。
    • 作用:链接器使用符号表信息来解析符号引用,链接不同的目标文件和库文件,确保函数调用和变量引用能够正确匹配。

运行时的符号表

  1. 运行时符号表
    • 存储位置:在程序运行时,符号表信息通常不会加载到内存中,除非是在调试模式下运行。调试器(如 gdb)可以通过读取可执行文件中的调试信息段(如 DWARF 或 PDB 格式)来构建符号表,以便程序员在调试时查看变量名、函数名等信息。
    • 作用:调试器利用运行时符号表信息来提供断点、变量查看、堆栈跟踪等调试功能。

3.静态联编和动态联编:

静态联编和动态联编

  • 静态联编:在编译阶段确定函数调用所使用的具体代码块。

    • C语言:通过函数名直接实现。
    • C++:通过函数名和函数参数实现。
  • 动态联编:在程序运行时确定函数调用所使用的具体代码块。

    • C++:通过虚函数实现。程序在运行时选择正确的虚方法,需要跟踪基类指针或引用指向的对象,因此有额外的处理开销。

4.生成可执行代码的四个阶段

预处理、编译、汇编和链接

预处理阶段

预处理器读取源代码文件(如 hello.c),根据以 # 开头的预处理指令对源代码进行修改。预处理器的主要任务包括:

  • 处理 #include 指令,将头文件的内容插入到当前文件中。
  • 处理 #define 指令,进行宏替换。
  • 处理条件编译指令(如 #if#ifdef#endif 等),决定哪些部分代码会被编译。 预处理的输出通常是一个扩展名为 .i 的文件,例如 hello.i
编译阶段

编译器将预处理后的源文件(如 hello.i)翻译成汇编语言文件(如 hello.s)。这个阶段的主要任务是:

  • 语法分析:检查代码的语法是否正确。
  • 语义分析:检查代码的语义是否正确,如类型检查。
  • 代码优化:优化代码以提高执行效率。
  • 代码生成:生成对应的汇编代码。 汇编语言是介于高级语言和机器语言之间的一种语言,每条汇编指令对应于一条机器指令。
汇编阶段

汇编器将汇编语言文件(如 hello.s)翻译成机器语言指令,并生成目标文件(如 hello.o)。这个阶段的主要任务是:

  • 将汇编指令转换为机器指令。
  • 将符号(如变量名、函数名)转换为具体的内存地址或偏移量。
  • 生成二进制代码,保存到目标文件中。 目标文件是一个包含机器指令的二进制文件,可以被计算机直接执行。
链接阶段

链接器将一个或多个目标文件(如 hello.oprintf.o)合并,生成一个最终的可执行文件(如 hello)。这个阶段的主要任务是:

  • 符号解析:将所有的外部符号(如函数名、变量名)解析为具体的内存地址。
  • 符号重定位:调整目标文件中的地址信息,使得所有模块能正确地相互调用。
  • 库文件链接:将标准库或用户库中的代码合并到最终的可执行文件中。 最终生成的可执行文件包含了所有需要的代码和数据,可以在操作系统下直接运行。

总结

预处理、编译、汇编和链接四个阶段依次对源代码进行处理,最终生成可执行的目标二进制代码。各阶段的具体任务如下:

编译过程通常包括两个主要阶段:编译和汇编

完整的编译过程

  1. 预处理(Preprocessing):源程序(如 .c.cpp 文件)首先经过预处理,生成预处理文件(通常以 .i 为后缀)。

    • 预处理命令:
      gcc -E hello.c -o hello.i
  2. 编译(Compilation):预处理后的文件(.i 文件)被编译器编译成汇编代码文件(通常以 .s 为后缀)。

    • 编译命令:
      gcc -S hello.i -o hello.s
  3. 汇编(Assembly):汇编器将汇编代码文件(.s 文件)汇编成目标文件(通常以 .o.obj 为后缀)。

    • 汇编命令:
      gcc -c hello.s -o hello.o
  4. 链接(Linking):链接器将一个或多个目标文件(.o 文件)和库文件链接成一个最终的可执行文件。

    • 链接命令:
      gcc hello.o -o hello
  5. 预处理

    • 将源程序中的宏、头文件展开,生成 .i 文件。
  6. 编译

    • 将预处理后的文件编译成汇编代码,生成 .s 文件。
  7. 汇编

    • 将汇编代码转换为机器指令,生成目标文件 .o.obj
  8. 链接

    • 将目标文件和库文件链接在一起,生成可执行文件。

5.语言特性

(1)内存对齐

1.结构体内存对齐规则(编译器自动处理)

  1. 成员对齐
    • 第一个成员位于偏移为 0 的位置。
    • 后续每个成员的偏移量必须是 min(#pragma pack()指定的数, 该数据成员自身长度) 的倍数。(偏移量是指结构体成员在内存中的位置相对于结构体起始地址的距离)
  2. 结构体整体对齐
    • 结构体的总长度必须是 min(#pragma pack()指定的数, 最长数据成员的长度) 的倍数。
#pragma pack(4)
struct Example {
    char a;     // 1 字节
    int b;      // 4 字节
    short c;    // 2 字节
};
内存布局分析
成员 a:

类型:char
大小:1 字节
对齐要求:1 字节
偏移量:0
下一个成员的偏移量起始地址:1
成员 b:

类型:int
大小:4 字节
对齐要求:4 字节(因为 #pragma pack(4))
当前偏移量:1
为了对齐到4字节,需要填充3个字节
偏移量:4
下一个成员的偏移量起始地址:8
成员 c:

类型:short
大小:2 字节
对齐要求:2 字节(因为 #pragma pack(4))
当前偏移量:8
已经对齐到2字节,无需填充
偏移量:8
下一个成员的偏移量起始地址:10
结构体整体对齐:

结构体当前大小:10 字节
对齐要求:4 字节
为了对齐到4字节,需要填充2个字节
结构体最终大小:12 字节

 结果:

Offset of a: 0
Offset of b: 4
Offset of c: 8
Size of Example: 12 bytes

2.内存对齐的作用与生动比喻

提高CPU的内存访问速度

概念

  • 内存读取粒度:CPU在读取内存时,是按块(通常为2、4、8、16字节)的方式读取的,这些块的大小称为内存读取粒度。
  • 对齐数据的好处:如果数据是对齐的,CPU可以一次性读取整个块,无需额外操作。
  • 未对齐数据的影响:如果数据未对齐,CPU需要多次读取并合并数据,这会导致性能下降。

比喻

想象你在一个大仓库里取货,每次你能拿一个箱子里的货物,这个箱子恰好能装下4个物品。

  • 对齐数据的情况: 货物整齐排列(数据对齐),你一次打开一个箱子,拿走4个物品,效率非常高。就像CPU一次性读取对齐的内存块。

  • 未对齐数据的情况: 货物不整齐排列(数据未对齐),一个物品跨了两个箱子。你必须先打开一个箱子拿走部分物品,再打开另一个箱子拿走剩下的部分,然后把不需要的物品剔除,效率很低。这类似于CPU需要多次读取和合并未对齐的内存数据。

具体例子:
  • 读取一个4字节数据,从地址0开始: CPU一次性读取4个字节,就像你一次拿走4个物品。

  • 读取一个4字节数据,从地址1开始: CPU需要读取0-3字节和4-7字节,并剔除不需要的字节,就像你要从两个箱子里分别拿取部分物品,再把不需要的部分剔除。

平台移植性

概念

  • 特定地址读取特定数据:某些硬件平台只能在特定地址读取特定类型的数据,不对齐的数据可能导致硬件异常。
  • 对齐数据的好处:对齐数据有助于代码在不同平台上的移植和运行。

比喻

想象不同的仓库有不同的取货规则。有的仓库规定,只能在特定的位置拿特定数量的货物。如果你不按照规则摆放货物,取货的时候就会出错,甚至可能拿不到任何物品。

  • 对齐数据的情况: 货物按规则摆放(数据对齐),在任何仓库都能顺利取货,方便移植。类似于对齐的数据可以在不同硬件平台上顺利运行。

  • 未对齐数据的情况: 货物不按规则摆放(数据未对齐),在某些仓库可能会取货失败,甚至发生错误。就像未对齐的数据在某些硬件平台上可能会导致异常。

(2)内存泄漏

定义

内存泄漏是指程序在运行过程中申请了内存但未能在使用完毕后释放掉,从而导致这些内存块无法被系统回收和再利用。通常,内存泄漏会导致以下现象:

  • 程序运行时间越长,占用的内存越多。
  • 最终会耗尽系统的全部内存,导致系统崩溃或程序崩溃。
  • 泄漏的内存块没有任何指针指向它,因此无法被访问或释放。

如何检测内存泄漏

  1. 观察系统内存使用情况

    • 在 Linux 中,可以使用 swap 命令观察可用的交换空间。在一两分钟内多次输入该命令,查看可用的交换区是否在减少。
    • 如果可用交换空间持续减少,可能存在内存泄漏。
  2. 使用系统工具

    • 使用 /usr/bin/stat 工具如 netstatvmstat 等,监控内存使用情况。
    • 如果发现某个进程的内存分配持续增加且从未释放,这可能是内存泄漏的迹象。
  3. 专业内存调试工具

    • 使用内存调试和泄漏检测工具如 Valgrind
    • Valgrind 可以检测内存泄漏并提供详细报告,帮助开发者定位和修复问题。

如何避免内存泄漏

  1. 良好的内存管理习惯

    • 每次动态分配内存后,确保在适当的时候释放它。
    • 避免不必要的内存分配,简化内存管理。
  2. 使用智能指针(适用于C++):

    • 使用 std::unique_ptrstd::shared_ptr 等智能指针自动管理内存,确保在指针超出作用域时释放内存。
    • 避免使用裸指针进行手动内存管理。
  3. 代码审查和测试

    • 定期进行代码审查,确保所有动态分配的内存都有相应的释放操作。
    • 编写单元测试,特别是对内存密集型操作进行测试,检查内存分配和释放是否匹配。
  4. 内存调试工具

    • 使用工具如 ValgrindAddressSanitizer 在开发和测试阶段检测内存泄漏。
    • 这些工具可以提供详细的内存使用报告,帮助快速发现和修复内存泄漏。

(3)双重释放

定义

  • 双重释放是指对同一块内存进行两次释放操作。它可能会导致程序崩溃或产生其他未定义行为。

例子

int* p = new int(10);
delete p; 
delete p; // 未定义行为,可能导致程序崩溃

解决方法

  • 在释放内存后,将指针设置为 nullptr,以防止重复释放。
  • 使用智能指针自动管理内存。

(4)内存越界

定义

  • 内存越界是指在分配的内存块范围之外访问内存,导致未定义行为,可能会破坏其他内存数据或引发安全漏洞。

例子

int arr[10];
arr[10] = 5;  // 超出数组范围,未定义行为

解决方法

  • 使用标准库容器(如 std::vector)来管理动态数组。
  • 检查数组索引范围,确保不越界。

(5)内存碎片

定义

  • 内存碎片是指由于频繁的内存分配和释放,导致内存块被分散在不同位置,难以找到足够连续的内存块来满足大块内存的分配需求。

解决方法

  • 使用内存池(Memory Pool)技术来管理内存。
  • 尽量减少频繁的小块内存分配和释放操作。

(6) 内存访问冲突

  • 内存访问冲突是指程序尝试访问未授权的内存区域,导致访问冲突错误(如段错误)。

例子

int* p = nullptr;
*p = 10; // 访问冲突,试图访问空指针

生动例子: 想象你有一个信箱(指针),但这个信箱没有放在任何地方(空指针)。你试图往这个不存在的信箱里放信(写数据),结果发现这个信箱根本不存在,导致你手中的信掉到了地上(段错误)。

解决方法

  • 检查指针是否为空或无效,确保访问合法内存区域。
  • 使用现代C++特性(如智能指针)来管理内存。

(7) 内存泄漏与资源管理

定义

  • 除了内存泄漏,资源泄漏也会导致问题,如文件句柄、网络连接等未能正确关闭和释放。

例子

FILE* file = fopen("example.txt", "r");
if (file == nullptr) {
    return;
}
// 使用文件
// 忘记关闭文件,导致资源泄漏

生动例子: 想象你打开了一个水龙头(文件句柄),但忘记关上它(释放资源),结果水一直流(资源泄漏),最终浪费了大量的水(系统资源)。

解决方法

  • 使用RAII(资源获取即初始化)原则,通过类的构造函数获取资源,在析构函数中释放资源。
  • 使用智能指针(如 std::unique_ptrstd::shared_ptr)管理动态资源。

(8) 内存池(Memory Pool)管理问题

定义

  • 内存池管理是指预先分配一大块内存,按需分配和释放小块内存,以减少内存碎片和提高分配效率。错误的内存池管理可能导致内存泄漏或碎片化。

生动例子: 想象你有一个大冰箱(内存池),里面放了很多小盒子(小块内存)。如果你不小心管理这些小盒子,有些盒子就会一直占用空间(内存泄漏),或者放得很乱,导致你无法再放入新的盒子(碎片化)。

解决方法

  • 实现和使用内存池时,确保正确的分配和释放逻辑,避免内存泄漏。
  • 使用现有的内存池管理库,如 Boost.Pool,确保可靠性和性能。

(9) 临时对象和返回值优化(RVO)

定义

  • 临时对象是指程序中短暂存在的对象,过多的临时对象会导致内存和性能问题。返回值优化(RVO)是编译器优化,减少临时对象的创建。

例子

class Example {
public:
    Example() {}
    Example(const Example&) {
        // 拷贝构造函数
    }
};

Example createExample() {
    return Example();  // 可能产生临时对象
}

生动例子: 想象你在厨房做菜(创建对象),每次需要一个碗(临时对象)来装菜。频繁地使用临时碗会让厨房变得很乱(性能问题)。如果你能直接把菜放进盘子里(RVO),就不需要那么多临时碗了。

解决方法

  • 使用 std::movestd::forward 避免不必要的拷贝。
  • 依赖编译器的返回值优化(RVO)和命名返回值优化(NRVO)来减少临时对象。

(10) 共享资源的并发访问

定义

  • 多线程程序中,多个线程同时访问和修改共享资源可能导致数据竞争和内存一致性问题。

例子

int sharedData = 0;

void threadFunc() {
    sharedData++;
}

std::thread t1(threadFunc);
std::thread t2(threadFunc);
t1.join();
t2.join();

生动例子: 想象两个小孩(线程)同时去抓一块蛋糕(共享资源)。如果没有人管,他们可能会打架(数据竞争),甚至把蛋糕弄掉(数据一致性问题)。如果有一个大人(互斥锁)管理,就可以让他们有序地拿蛋糕。

解决方法

  • 使用互斥锁(std::mutex)保护共享资源,确保线程安全。
  • 使用原子操作(std::atomic)简化并发访问的实现。

(11) 栈溢出(Stack Overflow)

定义

  • 栈溢出是指程序使用了超过栈大小限制的内存,通常由于过深的递归调用或过大的局部变量。

例子

void recursiveFunction() {
    int largeArray[100000];  // 大数组占用大量栈空间
    recursiveFunction();     // 递归调用导致栈溢出
}

int main() {
    recursiveFunction();
    return 0;
}

生动例子: 想象你在一个房间里(栈),不停地往里面放东西(递归调用和局部变量)。当房间装满后(超过栈大小),再放东西就会导致房间爆炸(栈溢出)。

解决方法

  • 避免深递归,使用循环或尾递归优化。
  • 将大数组或大对象放在堆上而不是栈上。

(12) 缓冲区溢出(Buffer Overflow)

定义

  • 缓冲区溢出是指程序写入数据超出分配的缓冲区范围,可能导致内存破坏和安全漏洞。

例子

char buffer[10];
strcpy(buffer, "This is a very long string");  // 缓冲区溢出

生动例子: 想象你有一个小水杯(缓冲区),你试图往里面倒很多水(数据)。结果水溢出来了,弄得到处都是(内存破坏和安全漏洞)。

解决方法

  • 使用安全的函数(如 strncpy)来防止缓冲区溢出。
  • 检查数据的长度,确保不会超出缓冲区范围。

(13) 双重指针和指针数组

定义

  • 双重指针和指针数组涉及更复杂的内存管理,容易导致内存泄漏和悬空指针。

例子

int** p = new int*[10];
for (int i = 0; i < 10; ++i) {
    p[i] = new int[10];
}

// 使用后忘记释放内存,导致内存泄漏

生动例子: 想象你在一个大盒子里(双重指针)放了很多小盒子(指针数组),每个小盒子里又放了很多糖果(数据)。如果你不小心管理这些盒子和糖果,有些糖果就会丢失(内存泄漏),或者你会找不到糖果放在哪(悬空指针)。

解决方法

  • 在动态分配内存后,确保正确释放内存,避免泄漏。
  • 使用智能指针(如 std::unique_ptr)管理动态数组。

4o

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

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

相关文章

在windows上安装objection

安装命令pip install objection -i https://mirrors.aliyun.com/pypi/simple hook指定进程 objection -g 测试 explore 进程名不定是包名&#xff0c;也可能是app名字&#xff0c;如“测试”就是app的名字 若出现如下错误&#xff0c;说明python 缺少setuptools 直接安装setu…

编程语言中浅拷贝(Shallow Copy)和深拷贝(Deep Copy)

编程语言中浅拷贝&#xff08;Shallow Copy&#xff09;和深拷贝&#xff08;Deep Copy&#xff09; 编程语言中浅拷贝&#xff08;Shallow Copy&#xff09;和深拷贝&#xff08;Deep Copy&#xff09;概念及JavaScript、Python、C、Java深拷贝和浅拷贝情况介绍。 浅拷贝和深拷…

【Redis】三大Redis内存分析工具介绍(Redisinsight、RDR、RMA)

目录 一、RedisInsight工具介绍 1、工具概述 2、关键特性 3、版本与服务 4、实际应用 二、Redis Data Reveal工具介绍 1、简介 2、主要特性 3、使用方法 4、注意事项 5、总结 三、Redis Memory Analyzer (RMA)工具介绍 1、工具概述 2、技术特点 3、应用场景 4、…

day02-统计数据

numpy统计学 1.求平均值[数组名.mean()/np.mean(数组名)] m1 np.arange(20).reshape((4,5))m1.mean() #9.5若想要求某一维的平均值&#xff0c;设置axis参数&#xff0c;多维数组元素指定&#xff1a; axis 0&#xff0c;将从上往下计算。axis 1&#xff0c;将从左往右计算…

使用getline()从文件中读取一行字符串

我们知道&#xff0c;getline() 方法定义在 istream 类中&#xff0c;而 fstream 和 ifstream 类继承自 istream 类&#xff0c;因此 fstream 和 ifstream 的类对象可以调用 getline() 成员方法。 当文件流对象调用 getline() 方法时&#xff0c;该方法的功能就变成了从指定文件…

最新抖音极速版双红包雨掘金助手

项目介绍&#xff1a; 抖音极速版目前小说里有双红包雨&#xff0c;单广告2000&#xff0c;金币1万比1&#xff0c;脚本自动看广告 设备需求&#xff1a; 安卓手机&#xff08;最高支持安卓13.0版本&#xff09; 购买后包含月卡脚本详细使用教程 百度网盘 请输入提取码百度…

SpringMVC 的工作流程和详细解释

Spring MVC&#xff08;Model-View-Controller&#xff09;框架是基于经典的 MVC 设计模式构建的&#xff0c;用于开发 Web 应用程序。下面是 Spring Boot MVC 的工作流程和详细解释&#xff1a; 1.客户端发起请求 1.客户端&#xff08;通常是浏览器&#xff09;发起 HTTP 请求…

中序遍历的两种实现——二叉树专题复习

递归实现&#xff1a; /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNode right)…

K8s 集群(kubeadm) CA 证书过期解决方案

Author&#xff1a;Arsen Date&#xff1a;2024/07/04 目录 一、现象描述二、解决方案三、集群验证 一、现象描述 之前有篇文章《K8s Token 过期解决方案&#xff08;Kubeadm&#xff09;》提到了默认生成的 Token 有效期只有 24 小时&#xff0c;过期后 Token 将不可用&#…

Robust Test-Time Adaptation in Dynamic Scenarios--论文阅读

论文笔记 资料 1.代码地址 https://github.com/BIT-DA/RoTTA 2.论文地址 https://arxiv.org/abs/2303.13899 3.数据集地址 coming soon 1论文摘要的翻译 测试时间自适应(TTA)旨在使预先7训练的模型适用于仅具有未标记测试数据流的测试分布。大多数以前的TTA方法已经在…

实现统计n个数以下质数的个数

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h>int main() {int n 0;scanf("%d", &n);int sum 0;for (int i 1; i < n; i){for (int j 2; j < i; j) {if (i % j 0){sum;break;}}}printf("%d", n - sum-1);return 0; } n为输…

数字媒体技术基础之:DNG 文件

DNG&#xff08;Digital Negative&#xff09;文件是一种用于存储原始图像数据的文件格式&#xff0c;由 Adobe Systems 于2004年开发并推广。DNG 是一种开放的、非专利的原始图像格式&#xff0c;旨在为不同相机制造商提供一个统一的存储格式。DNG 文件保存了原始的、未处理的…

【C语言】刷题笔记 Day2

【笔记】 【1】局部变量不初始化&#xff0c;默认放的随机值。 1 int n0; 2 scanf("%d",&n); //13.141 【2】这里虽然输入的是一个浮点数&#xff0c;但是只取整数部分。 【3】3.156e7 表示的是3.156*10的7次方。 【4】多组输入&#xff0c;保存和不保存…

Studying-代码随想录训练营day29| 134. 加油站、135. 分发糖果、860.柠檬水找零、406.根据身高重建队列

第29天&#xff0c;贪心part03&#xff0c;快过半了(ง •_•)ง&#x1f4aa;&#xff0c;编程语言&#xff1a;C 目录 134.加油站 135. 分发糖果 860.柠檬水找零 406.根据身高重建队列 134.加油站 文档讲解&#xff1a;代码随想录加油站 视频讲解&#xff1a;手撕加油站…

2.2 ROS2话题通信

场景 话题通信是ROS中使用频率最高的一种通信模式&#xff0c;话题通信是基于发布订阅模式的&#xff0c;也即&#xff1a;一个节点发布消息&#xff0c;另一个节点订阅该消息。话题通信的应用场景也极其广泛&#xff0c;比如如下场景&#xff1a; 机器人在执行导航功能&#…

5个文章生成器免费版,自动写作文章更轻松

在这个信息如洪流般涌动的时代&#xff0c;写作所具有的重要性不言而喻。不管是学生需要完成的作业&#xff0c;还是职场人士得提交的报告&#xff0c;亦或是自媒体创作者必须输出的内容&#xff0c;都迫切要求我们具备一定的写作技能。然而&#xff0c;写作对很多人来说&#…

基于 STM32 的智能睡眠呼吸监测系统设计

本设计的硬件构成&#xff1a; STM32F103C8T6单片机最小系统板&#xff08;包含3.3V稳压电路时钟晶振电路复位电路&#xff08;上电自复位&#xff0c;手动复位&#xff09;&#xff09;&#xff0c;心率传感器、气压传感器、液晶显示、按键、蜂鸣器、LED灯、蓝牙模块组合而成…

Nettyの网络聊天室扩展序列化算法

1、网络聊天室综合案例 客户端初始代码&#xff1a; Slf4j public class ChatClient {public static void main(String[] args) {NioEventLoopGroup group new NioEventLoopGroup();LoggingHandler LOGGING_HANDLER new LoggingHandler(LogLevel.DEBUG);MessageCodecSharabl…

2024-07-04 base SAS programming学习笔记8(HTML)

当使用ODS来进行结果或数据集输出的时候&#xff0c;可以同时设置多个ODS 命令&#xff0c;同时输出到多个不同的文件。使用_ALL_ 表示关闭所有的ODS输出窗口&#xff0c;比如&#xff1a; ods html file(body)"html-file-pathname"; ods html file"pdf-file-pa…

【Ubuntu24.04无显示器远控】【Todesk远程桌面黑屏】【Linux虚拟显示器】解决方案

1️⃣版本 Ubuntu 24.04Todesk 4.7.2.0xserver-xorg-video-dummy 1:0.4.0-1build1 2️⃣安装配置虚拟显示器 sudo apt install xserver-xorg-video-dummy编辑/etc/gdm3/custom.conf&#xff0c;关闭Ubuntu24.04Wayland切换为X11 WaylandEnablefalse /usr/share/X11/xorg.con…