【C语言回顾】动态内存管理

  • 前言
  • 1. 动态内存管理初步概述
  • 2. malloc
  • 3. calloc
  • 4. realloc
  • 5. free
  • 6. 常见的动态内存错误
  • 7. 柔性数组
  • 8. 程序内存区域划分
  • 结语

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

#include<GUIQU.h>
int main
{
上期回顾: 【C语言回顾】联合和枚举
个人主页:C_GUIQU
专栏:【C语言学习】
return 一键三连;
}

在这里插入图片描述

前言

各位小伙伴大家好!上期小编给大家讲解了C语言中的联合和枚举,接下来我们讲解一下动态内存管理!

1. 动态内存管理初步概述

在C语言中,动态内存管理指的是在程序运行时向操作系统请求和释放内存的过程。C语言提供了几个标准库函数来支持动态内存管理,这些函数定义在头文件stdlib.h中。

以下是动态内存管理的关键函数及其用途:

  1. malloc() - 分配指定大小的内存块,返回一个指向void类型的指针,因此需要类型转换。如果分配失败,返回NULL。
    int *ptr = (int*)malloc(n * sizeof(int)); // 分配n个整数的空间
    if (ptr == NULL) {
        // 处理内存分配失败的情况
    }
    
  2. calloc() - 类似于malloc,但它会清除分配的内存,将其初始化为0。它接受两个参数,分别是元素的数量和每个元素的大小。
    int *ptr = (int*)calloc(n, sizeof(int)); // 分配并初始化n个整数
    if (ptr == NULL) {
        // 处理内存分配失败的情况
    }
    
  3. realloc() - 用于调整之前分配的内存块的大小。它接受两个参数,一个是原始指针,另一个是新的大小。如果新的内存分配失败,realloc会返回NULL,但原始的数据不会被释放。
    int *new_ptr = (int*)realloc(ptr, new_size * sizeof(int)); // 调整内存大小
    if (new_ptr == NULL) {
        // 处理内存分配失败的情况
        // 注意:此时ptr仍然包含有效的数据
    } else {
        ptr = new_ptr; // 如果成功,更新指针
    }
    
  4. free() - 释放之前分配的内存。这是一个非常重要的函数,因为如果不释放不再使用的内存,会导致内存泄漏。
    free(ptr); // 释放内存
    ptr = NULL; // 将指针设置为NULL,避免悬空指针
    

动态内存管理的使用需要注意以下几点:

  • 内存分配失败的处理:当内存分配失败时,程序应该有适当的错误处理机制。
  • 指针类型转换:从malloc、calloc或realloc返回的指针需要转换为正确的类型。
  • 内存泄漏:使用free函数释放不再需要的内存,以避免内存泄漏。
  • 内存越界:避免访问分配的内存之外的地址,这可能导致未定义的行为。
  • 内存碎片:频繁的分配和释放可能导致内存碎片,这可以通过内存池等技术来减少。

2. malloc

malloc 是 C 语言标准库中的一个函数,用于在堆上动态分配指定大小的内存块。它是最常用的动态内存分配函数之一。malloc 函数的原型定义在 <stdlib.h> 头文件中,如下所示:

void *malloc(size_t size);

参数 size 表示要分配的内存字节数。malloc 函数返回一个指向 void 类型的指针,因此在使用时通常需要进行类型转换,以匹配所需的数据类型。如果内存分配失败,malloc 返回 NULL 指针。

下面是 malloc 函数的一个示例:

#include <stdio.h>
#include <stdlib.h>
int main() {
    int *array;
    int size = 10; // 假设我们想要一个包含10个整数的数组
    // 分配内存
    array = (int*)malloc(size * sizeof(int));
    if (array == NULL) {
        fprintf(stderr, "内存分配失败\n");
        exit(EXIT_FAILURE);
    }
    // 使用分配的内存
    for (int i = 0; i < size; i++) {
        array[i] = i;
    }
    // 打印数组元素
    for (int i = 0; i < size; i++) {
        printf("%d ", array[i]);
    }
    printf("\n");
    // 释放内存
    free(array);
    array = NULL; // 避免悬空指针
    return 0;
}

在这个示例中,我们使用 malloc 分配了一个足够大的内存块来存储 10 个整数。然后,我们检查 malloc 是否返回了 NULL,如果是,则打印错误消息并退出程序。接下来,我们初始化数组并打印其内容。最后,我们使用 free 函数释放内存,并将指针设置为 NULL,以避免悬空指针的问题。

使用 malloc 时应该注意以下几点:

  • 总是检查 malloc 的返回值是否为 NULL,以处理内存分配失败的情况。
  • malloc 返回的指针进行正确的类型转换。
  • 分配的内存未经初始化,可能包含随机数据。如果需要初始化,可以使用 calloc 函数,或者手动初始化内存。
  • 使用 free 函数释放不再需要的内存,以避免内存泄漏。
  • 分配的内存应该在同一个作用域内释放,或者在需要的情况下,确保在适当的时机释放。

3. calloc

calloc 是 C 语言标准库中的一个函数,用于在堆上动态分配指定数量的元素大小的内存块,并将分配的内存初始化为 0。calloc 函数的原型定义在 <stdlib.h> 头文件中,如下所示:

void *calloc(size_t num, size_t size);

calloc 函数接受两个参数:

  • num:要分配的元素数量。
  • size:每个元素的大小(以字节为单位)。
    calloc 函数返回一个指向 void 类型的指针,因此在使用时通常需要进行类型转换,以匹配所需的数据类型。如果内存分配失败,calloc 返回 NULL 指针。

下面是 calloc 函数的一个示例:

#include <stdio.h>
#include <stdlib.h>
int main() {
    int *array;
    int size = 10; // 假设我们想要一个包含10个整数的数组
    // 分配并初始化内存
    array = (int*)calloc(size, sizeof(int));
    if (array == NULL) {
        fprintf(stderr, "内存分配失败\n");
        exit(EXIT_FAILURE);
    }
    // 使用分配的内存
    for (int i = 0; i < size; i++) {
        printf("%d ", array[i]); // 由于calloc初始化为0,这里将打印0
    }
    printf("\n");
    // 释放内存
    free(array);
    array = NULL; // 避免悬空指针
    return 0;
}

在这个示例中,我们使用 calloc 分配了一个足够大的内存块来存储 10 个整数,并将内存初始化为 0。然后,我们检查 calloc 是否返回了 NULL,如果是,则打印错误消息并退出程序。接下来,我们打印数组的内容,由于 calloc 已经将内存初始化为 0,所以这里将打印出一串 0。最后,我们使用 free 函数释放内存,并将指针设置为 NULL,以避免悬空指针的问题。

使用 calloc 时应该注意以下几点:

  • 总是检查 calloc 的返回值是否为 NULL,以处理内存分配失败的情况。
  • calloc 返回的指针进行正确的类型转换。
  • 使用 free 函数释放不再需要的内存,以避免内存泄漏。
  • 分配的内存已经在 calloc 中初始化为 0,这对于某些需要清零内存的应用场景是非常有用的。

4. realloc

realloc 是 C 语言标准库中的一个函数,用于调整之前通过 malloccallocrealloc 分配的内存块的大小。realloc 函数可以增加或减少内存块的大小,并且可以选择性地复制旧数据到新位置。realloc 函数的原型定义在 <stdlib.h> 头文件中,如下所示:

void *realloc(void *ptr, size_t size);

realloc 函数接受两个参数:

  • ptr:指向之前分配的内存块的指针,或者如果是第一次分配,则为 NULL
  • size:新内存块的大小(以字节为单位)。
    realloc 函数返回一个指向 void 类型的指针,因此在使用时通常需要进行类型转换,以匹配所需的数据类型。如果内存分配失败,realloc 返回 NULL 指针。如果 ptr 参数为 NULL,则 realloc 的行为类似于 malloc,分配一个新的内存块。

下面是 realloc 函数的一个示例:

#include <stdio.h>
#include <stdlib.h>
int main() {
    int *array;
    int old_size = 5;
    int new_size = 10;
    // 分配内存
    array = (int*)malloc(old_size * sizeof(int));
    if (array == NULL) {
        fprintf(stderr, "初始内存分配失败\n");
        exit(EXIT_FAILURE);
    }
    // 使用分配的内存
    for (int i = 0; i < old_size; i++) {
        array[i] = i;
    }
    // 调整内存大小
    array = (int*)realloc(array, new_size * sizeof(int));
    if (array == NULL) {
        fprintf(stderr, "内存调整失败\n");
        exit(EXIT_FAILURE);
    }
    // 使用新分配的内存
    for (int i = old_size; i < new_size; i++) {
        array[i] = i;
    }
    // 打印数组元素
    for (int i = 0; i < new_size; i++) {
        printf("%d ", array[i]);
    }
    printf("\n");
    // 释放内存
    free(array);
    array = NULL; // 避免悬空指针
    return 0;
}

在这个示例中,我们首先使用 malloc 分配了一个包含 5 个整数的内存块。然后,我们使用 realloc 将内存块的大小调整为 10 个整数。如果 realloc 成功,它会返回一个指向新内存块的指针,这个新内存块可能位于原内存块的位置,也可能是一个完全不同的位置。因此,重要的是更新原始指针 array 以指向新内存块的地址。如果 realloc 失败,它会返回 NULL,并且原始数据仍然有效。最后,我们使用 free 函数释放内存,并将指针设置为 NULL,以避免悬空指针的问题。

使用 realloc 时应该注意以下几点:

  • 总是检查 realloc 的返回值是否为 NULL,以处理内存分配失败的情况。
  • 如果 realloc 失败,原始数据仍然有效,因此需要适当处理这种情况。
  • 在调整内存大小时,可能会发生数据复制,因此 realloc 可能会复制旧数据到新位置,这可能会导致性能开销。
  • 使用 realloc 时,确保所有指向原始内存块的指针都被更新为新地址。
  • 使用 free 函数释放不再需要的内存,以避免内存泄漏。

5. free

free 是 C 语言标准库中的一个函数,用于释放之前通过 malloccallocrealloc 分配的动态内存。这个函数非常重要,因为它允许程序返回不再使用的内存给操作系统,从而避免内存泄漏,这是动态内存管理中的一个常见问题。
free 函数的原型定义在 <stdlib.h> 头文件中,如下所示:

void free(void *ptr);

free 函数接受一个参数:

  • ptr:指向要释放的内存块的指针。这个指针必须是之前由 malloccallocrealloc 返回的,并且尚未被 free 函数释放。

下面是 free 函数的一个示例:

#include <stdio.h>
#include <stdlib.h>
int main() {
    int *ptr = (int*)malloc(10 * sizeof(int)); // 分配内存
    if (ptr == NULL) {
        fprintf(stderr, "内存分配失败\n");
        return 1;
    }
    // 使用分配的内存
    for (int i = 0; i < 10; i++) {
        ptr[i] = i;
    }
    // 释放内存
    free(ptr);
    ptr = NULL; // 将指针设置为 NULL,避免悬空指针
    // 继续执行程序的其他部分
    // ...
    return 0;
}

在这个示例中,我们首先使用 malloc 分配了一个包含 10 个整数的内存块。然后,我们使用这个内存块,并将每个元素初始化为它的索引。最后,我们使用 free 函数释放这个内存块,并将指针 ptr 设置为 NULL,以避免它成为悬空指针。

使用 free 函数时应该注意以下几点:

  • 只能释放由 malloccallocrealloc 分配的内存。
  • 释放内存后,应该将指向该内存的指针设置为 NULL,以避免悬空指针。
  • 重复释放同一块内存会导致未定义行为,这是危险的。
  • 释放内存后,不应该再访问该内存,因为它可能已经被重新分配给其他用途。

6. 常见的动态内存错误

在 C 语言中,动态内存管理是强大的,但同时也是容易出错的。

以下是一些常见的动态内存错误:

  1. 内存泄漏:忘记释放已分配的内存。这会导致程序随着时间的推移消耗越来越多的内存,最终可能导致程序崩溃或系统资源耗尽。
    int *ptr = malloc(sizeof(int) * 10);
    if (ptr == NULL) {
        // 处理错误
    }
    // ... 使用 ptr ...
    // 忘记释放内存
    // free(ptr); // 应该调用 free
    
  2. 悬空指针:在释放内存后,仍然使用指向该内存的指针。这可能导致未定义的行为,因为释放后的内存可能已经被其他数据覆盖或重新分配。
    int *ptr = malloc(sizeof(int));
    if (ptr == NULL) {
        // 处理错误
    }
    *ptr = 42;
    free(ptr);
    // 悬空指针
    printf("%d\n", *ptr); // 未定义行为
    
  3. 野指针:使用未初始化的指针进行内存访问。这通常发生在指针声明后未赋予有效的内存地址就进行访问。
    int *ptr; // 未初始化
    *ptr = 42; // 野指针,可能导致程序崩溃
    
  4. 越界访问:访问动态分配的内存块之外的地址。这可能导致数据损坏、程序崩溃或安全漏洞。
    int *ptr = malloc(sizeof(int) * 10);
    if (ptr == NULL) {
        // 处理错误
    }
    for (int i = 0; i <= 10; i++) {
        ptr[i] = i; // 越界访问,ptr[10] 是未分配的
    }
    
  5. 使用释放后的内存:在调用 free 后,再次尝试访问或释放同一块内存。这可能导致未定义的行为或程序崩溃。
    int *ptr = malloc(sizeof(int));
    if (ptr == NULL) {
        // 处理错误
    }
    free(ptr);
    // 使用释放后的内存
    *ptr = 42; // 未定义行为
    
  6. 错误的大小传递:向 malloccallocrealloc 传递错误的大小参数,可能导致分配的内存不足或浪费。
    int *ptr = malloc(-1); // 错误的大小,可能导致未定义行为
    
  7. 分配失败处理不当:在 malloccallocrealloc 返回 NULL 时,没有正确处理内存分配失败的情况。
    int *ptr = malloc(0); // 可能分配失败
    if (ptr == NULL) {
        // 应该处理错误
    }
    
  8. 多次释放:多次调用 free 释放同一块内存。这可能导致程序崩溃或未定义行为。
    int *ptr = malloc(sizeof(int));
    if (ptr == NULL) {
        // 处理错误
    }
    free(ptr);
    free(ptr); // 多次释放,未定义行为
    

避免这些错误需要仔细的编程和对动态内存管理函数的正确使用。使用工具如静态分析器、动态分析器(如 Valgrind)和运行时检查(如 AddressSanitizer)可以帮助检测和预防这些错误。

7. 柔性数组

在 C99 标准中,结构体的一种特殊用法被称为“柔性数组”(flexible array member),它允许结构体中最后一个成员是一个未知大小的数组。这种特性在处理变长数据时非常有用,因为它允许结构体的大小适应其包含的数据。
柔性数组的定义要求结构体中的最后一个成员必须是一个数组,且该数组的长度在结构体定义时未知。柔性数组前面可以有一个或多个其他成员。由于数组的大小在编译时未知,因此结构体本身的大小只包括其他成员的大小,而不包括柔性数组的大小。

下面是一个柔性数组的示例:

#include <stdio.h>
#include <stdlib.h>
typedef struct {
    int length;
    double values[]; // 柔性数组
} FlexArray;
int main() {
    FlexArray *array = malloc(sizeof(FlexArray) + sizeof(double) * 10);
    if (array == NULL) {
        return 1;
    }
    array->length = 10;
    for (int i = 0; i < array->length; i++) {
        array->values[i] = i * 1.0;
    }
    for (int i = 0; i < array->length; i++) {
        printf("%f ", array->values[i]);
    }
    printf("\n");
    free(array);
    return 0;
}

在这个示例中,FlexArray 结构体包含一个 int 类型的成员 length 和一个柔性数组 values。我们使用 malloc 分配足够的内存来存储结构体和一个包含 10 个 double 类型元素的数组。然后,我们可以像使用普通数组一样使用 array->values

使用柔性数组时需要注意以下几点:

  • 柔性数组必须作为结构体的最后一个成员。
  • 柔性数组的大小在结构体定义时必须是未知的,通常在运行时根据需要动态分配。
  • 柔性数组前面可以有其他成员,这些成员的大小在编译时是已知的。
  • 柔性数组的存在使得结构体的大小可以动态调整,以适应不同大小的数据。
    柔性数组提供了一种方便的方式来处理结构体中的变长数据,而不需要为可能的最大大小分配内存。这在内存受限的应用程序中特别有用,因为它可以减少不必要的内存分配。

8. 程序内存区域划分

在程序的执行过程中,内存被划分为多个不同的区域,每个区域有不同的用途和属性。这些区域可以分为以下几类:

  1. 栈(Stack)
    • 每个函数调用都会创建一个新的栈帧,用于存储函数的参数、局部变量和返回地址。
    • 栈是线性的、连续的内存区域,具有固定的大小。
    • 栈的大小通常是固定的,由编译器或操作系统决定。
    • 栈操作是自动的,不需要程序员显式管理。
  2. 堆(Heap)
    • 堆用于动态内存分配,程序员可以请求任意大小的内存块。
    • 堆的大小通常不固定,可以根据需要动态扩展。
    • 程序员需要手动管理堆上的内存,使用 malloccallocreallocfree 函数。
    • 堆操作是非线性的,可能涉及更多的内存碎片。
  3. 数据段(Data Segment)
    • 包含程序的全局变量和静态变量。
    • 数据段在程序启动时分配,并持续存在,直到程序结束。
    • 数据段的大小在编译时确定,但在运行时不可变。
  4. 代码段(Text Segment)
    • 包含程序的代码(指令和常量)。
    • 代码段在程序启动时加载到内存,并一直存在直到程序结束。
    • 代码段的大小在编译时确定,但在运行时不可变。
  5. BSS 段(Block Started by Symbol)
    • 包含未初始化的全局变量和静态变量。
    • BSS 段在程序启动时被初始化为 0。
    • BSS 段的大小在编译时确定,但在运行时不可变。
  6. 未使用段(Unused Segment)
    • 包含程序未使用的内存区域。
    • 这些区域可能因为编译器或操作系统的原因而存在,但通常不包含有效的数据。
      每个操作系统和编译器可能会有不同的内存区域划分和实现细节,但上述分类提供了一个通用的框架。在 C 语言中,动态内存管理主要发生在堆上,而静态内存管理则发生在栈、数据段、代码段和 BSS 段上。

结语

以上就是小编对动态内存管理的详细讲解。
如果觉得小编讲的还可以,还请一键三连。互三必回!
持续更新中~!

在这里插入图片描述

在这里插入图片描述

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

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

相关文章

win32-鼠标消息、键盘消息、计时器消息、菜单资源

承接前文&#xff1a; win32窗口编程windows 开发基础win32-注册窗口类、创建窗口win32-显示窗口、消息循环、消息队列 本文目录 键盘消息键盘消息的分类WM_CHAR 字符消息 鼠标消息鼠标消息附带信息 定时器消息 WM_TIMER创建销毁定时器 菜单资源资源相关菜单资源使用命令消息的…

远动通讯屏具体干啥作用

远动通讯屏具体干啥作用 远动通讯屏主要用于电力系统中的各类发电厂、变电站、光伏电站、开闭所、配电房等&#xff0c;具有实时传输数据和远程控制功能。它的主要作用包括&#xff1a; 数据采集&#xff1a;远动通讯屏能够采集各种模拟量、开关量和数字量等信息&#xff0c…

python查找内容在文件中的第几行(利用了滑动窗口)

def find_multiline_content(file_path, multiline_content):with open(file_path, r) as file:# 文件内容file_lines file.readlines()# 待检测内容multiline_lines multiline_content.strip().split(\n)# 待检测内容总行数num_multiline_lines len(multiline_lines)matchi…

【CALayer-CALayer的transform属性 Objective-C语言】

一、接下来,我们来说的是这个,transform的属性 1.layer的transform属性, 把最后一份代码command + C、command + V、一份儿,改个名字, Name:04-CALayer的transform属性, 我们把这个代码稍微修改一下, 我们先添加了一个layer,到控制器的view上, 然后呢,这两句话不…

Tina-Linux -- 5. 网络通信(有线网络,无线网络,SSH链接)

有线网络 bash 指令 ifconfig eth0 192.168.2.222 netmask 255.255.255.0 up route add default gw 192.168.2.1开机自启 修改网络设置文件 /etc/init.d/S40network #!/bin/sh # # Start the network.... ## Debian ifupdown needs the /run/network lock directory mkdir …

Vue3实现简单的瀑布流效果,可抽离成组件直接使用

先来看下效果图&#xff1a; 瀑布流中的内容可进行自定义&#xff0c;这里的示例图是通过不同背景颜色的展示进行区分&#xff0c;每个瀑布流中添加了自定义图片和文字描述。 实现方式&#xff1a; 1.建立子组件&#xff08;可单独抽离&#xff09;写出瀑布流的样式 文件名为…

C++-逻辑语句

if语句 基本格式&#xff1a; 只有判断结果为true&#xff0c;才会执行后续{}内的代码 if (要执行的判断&#xff0c;结果需是bool型) {判断结果true&#xff0c;才会执行的代码; }if (条件判断) { 如果判断结果为true&#xff0c;会执行的代码; }else{如果判断结果为false…

【工具】AFL+Unicorn|二进制程序模糊测试工具 AFL 和 Unicorn 的前世今生、安装以及 Python 使用实例

文章目录 【工具】AFLUnicorn&#xff5c;二进制程序模糊测试基础工具&#xff08;AFLUnicorn&#xff09;写在最前1. 系统环境2. 软件版本3. 背景知识3.1 AFL vs AFLplusplus3.2 QEMU vs Unicorn3.3 Unicorn vs UnicornAFL 4. 工具安装4.1 Ubuntu184.2 Ubuntu 20~224.3 收尾 5…

分布式事务——9种解决方案的原理与分类

目录 一、概要1. 分布式事务的概念2. 分布式事务解决方案分类 二、常见的分布式事务解决方案1. 基础的 2PC&#xff08;二阶段提交&#xff09;1.1 核心思想1.2 简介1.3 主要特点1.3.1 优点1.3.2 缺点 2. 基础的 3PC&#xff08;三阶段提交&#xff09;2.1 核心思想2.2 简介2.3…

【系统架构师】-案例篇(十五)SOA、微服务与数据库

1、可复用构件应具备哪些属性 可用性&#xff1a;构件必须易于理解和使用。 质量&#xff1a;构件及其变形必须能正确工作。 适应性&#xff1a;构件应该易于通过参数化等方式在不同语境中进行配置。 可移植性&#xff1a;构件应能在不同的硬件运行平台和软件环境中工作。 可变…

# 全面解剖 消息中间件 RocketMQ-(2)

全面解剖 消息中间件 RocketMQ-&#xff08;2&#xff09; 一、RocketMQ – RocketMQ 各角色介绍 1、RocketMQ 各角色介绍 Producer : 消息的发送者; 举例:发信者。Consumer : 消息接收者; 举例:收信者。Broker : 暂存和传输消息; 举例:邮局。NameServer : 管理 Broker; 举例…

全网最全爬取-b站爬取弹幕+评论之js逆向与xml降本增效

&#x1f31f; ❤️ 作者&#xff1a;yueji0j1anke 首发于公号&#xff1a;剑客古月的安全屋 字数&#xff1a;801 阅读时间: 10min 声明&#xff1a;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及…

K-means聚类模型

目录 1.定义 2.K-means聚类模型的优点 3.K-means聚类模型的缺点 4.K-means聚类模型的应用场景 5.对K-means聚类模型未来的展望 6.小结 1.定义 什么是 K-means 聚类模型&#xff1f;K-means 聚类模型是一种无监督学习算法&#xff0c;用于将数据划分为不同的组或簇&#…

概念+bug

模型 迭代模型和增量模型 增量模型是将一个大的需求变成小的功能&#xff0c;然后将每个功能逐个开发几乎完整再上线。 迭代模型会上线一个基础版本&#xff0c;但是基础版本所有的功能都有但是非常简陋&#xff0c;之后再迭代优化上线。 一般都是两个一起用&#xff0c;不…

微服务Day7学习

文章目录 数据聚合聚合分类 自动补全DSL实现Bucket聚合DSL实现Metrics聚合RestAPI实现聚合多条件聚合对接前端接口拼音分词器自定义分词器自动补全查询实现酒店搜索框自动补全 数据同步数据同步思路分析利用mq实现mysql与elasticsearch数据同步 集群介绍搭建ES集群 数据聚合 聚…

T01.如何寻找隐藏目录usr?

当我们打开Mac自带的Shell或者另外安装的zsh等工具时&#xff0c;我们所在的默认目录为用户目录usr。 Mac的这个bin目录&#xff0c;是一个已经包含在环境变量里的目录&#xff0c;程序放在里面或者链接到里面就可以在终端里直接执行。Mac的usr/bin目录是不允许增删文件的&…

tomcat三级指导

版本 ./catalina.sh linux version.bat win 1.确认是否使用了tomcat管理后台 我们先找到配置文件&#xff1a;tomcat主目录下/conf/server.xml 可以查看到连接端口&#xff0c;默认为8080 然后查看manager-gui管理页面配置文件&#xff0c;是否设置了用户登录 配置文件…

Obsidian Git 多端同步

2023年6月&#xff0c;某云笔记限制了免费用户最多同时登录 2 台设备&#xff0c;想要增加设备数量需要付费开通会员。之后我一直想找一款合适的笔记本软件&#xff0c;年底尝试了Obsidian&#xff0c;断断续续摸索了好几天终于成功了。将那时的笔记拿来分享一下。 相关地址&am…

Java轻松转换Markdown文件到Word和PDF文档

Markdown 凭借其简洁易用的特性&#xff0c;成为创建和编辑纯文本文档的常用选择。但某些时候我们需要更加精致的展示效果&#xff0c;例如在专业分享文档或打印成离线使用的纸质版时&#xff0c;就需要将Markdown文件以其他固定的文档格式呈现。通过将 Markdown 转换为 Word 和…

错误代码126:加载x3daudio1_7.dll失败的几种修复方法分享

在使用电脑玩游戏或者工作的过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中最常见的就是“错误代码[126]:加载x3daudio1_7.dll失败&#xff0c;该文件缺失或损坏!”&#xff0c;那么&#xff0c;什么是x3daudio17.dll文件&#xff1f;它为什么会丢失&#xff1…