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

Java转C之内存管理

从 Java 转向 C 语言开发,内存管理和指针是两个核心且关键的概念。这些概念在 Java 中被抽象和自动化处理,而在 C 语言中则需要开发者手动管理和操作。


目录

  1. 内存管理概述
  2. 指针基础
    • 指针的定义与初始化
    • 地址运算符 & 和解引用运算符 *
    • 指针运算
  3. 动态内存分配
    • malloc()
    • calloc()
    • realloc()
    • free()
  4. 指针与数组
  5. 多级指针
  6. 函数指针
  7. 内存管理与指针的常见错误
  8. 总结与最佳实践
  9. 全面示例

内存管理概述

Java vs. C 语言的内存管理

特性JavaC 语言
内存管理方式自动垃圾回收(GC)手动分配与释放(使用 malloc()free()
内存安全性高(自动管理内存,减少内存泄漏和越界)低(需要开发者自行管理,容易出错)
指针使用无裸指针,使用引用进行对象访问裸指针,可直接操作内存地址
内存分配方式堆(Heap)和栈(Stack),对象在堆上分配堆(使用 malloc() 等)和栈(自动管理)
内存泄漏风险低(GC 自动回收不再使用的对象)高(开发者需要确保释放所有分配的内存)
多线程内存管理内置支持,线程安全的数据结构和同步机制需要手动实现线程安全,使用库如 POSIX Threads

总结:Java 的内存管理相对简单和安全,而 C 语言提供了更高的灵活性和控制力,但也增加了出错的风险。


指针基础

指针的定义与初始化

指针是存储内存地址的变量。在 C 语言中,指针用于直接操作内存,提供了极大的灵活性和控制力,但也增加了出错的风险。

指针的定义
type *pointerName;
  • type:指针所指向的数据类型。
  • *:表示这是一个指针变量。

示例

int *p;          // 指向整数的指针
double *d;       // 指向双精度浮点数的指针
char *c;         // 指向字符的指针
指针的初始化

指针可以指向一个已存在变量的地址,或通过动态内存分配函数获取。

示例

int a = 10;
int *p = &a;     // p 指向 a 的地址

地址运算符 & 和解引用运算符 *

  • 地址运算符 &:用于获取变量的内存地址。
  • 解引用运算符 *:用于访问指针指向的内存位置的值。

示例

#include <stdio.h>int main() {int a = 10;int *p = &a;     // 使用 & 获取 a 的地址并赋给指针 pprintf("Value of a: %d\n", a);        // 输出: 10printf("Address of a: %p\n", (void*)&a); // 输出: a 的地址printf("Value via pointer: %d\n", *p); // 使用 * 访问 p 指向的值,输出: 10*p = 20;                              // 通过指针修改 a 的值printf("New value of a: %d\n", a);    // 输出: 20return 0;
}

输出

Value of a: 10
Address of a: 0x7ffeefbff5ac
Value via pointer: 10
New value of a: 20

指针运算

指针可以进行算术运算,如加减,用于遍历数组等。

示例

#include <stdio.h>int main() {int arr[] = {10, 20, 30, 40, 50};int *p = arr; // 等同于 int *p = &arr[0];for(int i = 0; i < 5; i++) {printf("Element %d: %d\n", i, *(p + i));}return 0;
}

输出

Element 0: 10
Element 1: 20
Element 2: 30
Element 3: 40
Element 4: 50

注意

  • 指针运算基于指针所指向数据类型的大小。例如,p + 1 实际上增加了 sizeof(int) 字节。

动态内存分配

在 C 语言中,动态内存分配允许在程序运行时根据需要分配和释放内存。主要通过以下函数实现:

malloc()

功能:分配一块指定字节数的内存,返回指向该内存的指针。

原型

void* malloc(size_t size);

示例

#include <stdio.h>
#include <stdlib.h>int main() {int *arr = (int*)malloc(5 * sizeof(int)); // 分配存储5个整数的内存if (arr == NULL) {fprintf(stderr, "Memory allocation failed\n");return 1;}// 使用内存for(int i = 0; i < 5; i++) {arr[i] = i * 10;}// 打印结果for(int i = 0; i < 5; i++) {printf("%d ", arr[i]);}printf("\n");// 释放内存free(arr);return 0;
}

输出

0 10 20 30 40 

calloc()

功能:分配内存并初始化为零,适用于分配数组。

原型

void* calloc(size_t num, size_t size);

示例

#include <stdio.h>
#include <stdlib.h>int main() {int *arr = (int*)calloc(5, sizeof(int)); // 分配并初始化存储5个整数的内存if (arr == NULL) {fprintf(stderr, "Memory allocation failed\n");return 1;}// 打印初始化结果(全部为0)for(int i = 0; i < 5; i++) {printf("%d ", arr[i]);}printf("\n");// 释放内存free(arr);return 0;
}

输出

0 0 0 0 0 

realloc()

功能:调整之前分配的内存块的大小,可以扩大或缩小。

原型

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

示例

#include <stdio.h>
#include <stdlib.h>int main() {int *arr = (int*)malloc(3 * sizeof(int));if (arr == NULL) {fprintf(stderr, "Initial allocation failed\n");return 1;}// 初始化for(int i = 0; i < 3; i++) {arr[i] = i + 1;}// 调整大小int *new_arr = (int*)realloc(arr, 5 * sizeof(int));if (new_arr == NULL) {fprintf(stderr, "Reallocation failed\n");free(arr); // 仍需释放原内存return 1;}// 初始化新增部分for(int i = 3; i < 5; i++) {new_arr[i] = i + 1;}// 打印结果for(int i = 0; i < 5; i++) {printf("%d ", new_arr[i]);}printf("\n");// 释放内存free(new_arr);return 0;
}

输出

1 2 3 4 5 

free()

功能:释放之前分配的内存,避免内存泄漏。

原型

void free(void* ptr);

示例

#include <stdlib.h>// 示例见上述 malloc 和 calloc 示例

指针与数组

在 C 语言中,数组名可以视为指向第一个元素的指针。这种关系使得指针和数组在很多情况下可以互换使用,但也存在一些细微差别。

示例

#include <stdio.h>int main() {int arr[3] = {1, 2, 3};int *p = arr; // 或者 int *p = &arr[0];printf("First element: %d\n", *p);         // 输出: 1printf("Second element: %d\n", *(p + 1));  // 输出: 2// 使用数组索引printf("Third element: %d\n", p[2]);       // 输出: 3return 0;
}

输出

First element: 1
Second element: 2
Third element: 3

注意事项

  • 数组名是常量指针,不能被重新赋值为其他地址。
  • 指针可以被重新赋值,指向不同的内存地址。

多级指针

指针可以指向另一个指针,形成多级指针。这在处理复杂数据结构(如二维数组、链表)或需要修改指针本身的函数时非常有用。

示例

#include <stdio.h>int main() {int a = 5;int *p = &a;        // p 是指向 a 的指针int **pp = &p;      // pp 是指向 p 的指针printf("Value of a: %d\n", a);           // 输出: 5printf("Value via p: %d\n", *p);         // 输出: 5printf("Value via pp: %d\n", **pp);      // 输出: 5// 修改 a 的值通过 pp**pp = 10;printf("New value of a: %d\n", a);       // 输出: 10return 0;
}

输出

Value of a: 5
Value via p: 5
Value via pp: 5
New value of a: 10

用途

  • 动态二维数组:通过多级指针实现动态二维数组。
  • 修改指针本身:在函数中修改传入的指针,使其指向新的内存地址。

函数指针

函数指针是指向函数的指针,可以用来调用函数或实现回调机制。这种特性使得 C 语言在设计灵活的程序结构时非常有用。

示例

#include <stdio.h>// 定义一个函数
int add(int a, int b) {return a + b;
}int main() {// 定义函数指针int (*func_ptr)(int, int) = add;// 使用函数指针调用函数int result = func_ptr(3, 4);printf("Result: %d\n", result); // 输出: 7return 0;
}

输出

Result: 7

注意事项

  • 确保函数指针类型与被指向函数的签名匹配。
  • 函数指针可以用于实现回调、动态函数调用等高级用法。

内存管理与指针的常见错误

在 C 语言中,错误的内存管理和指针操作是导致程序崩溃和安全漏洞的主要原因。以下表格列出了常见的错误类型及其描述和解决方案。

错误类型描述解决方案
内存泄漏分配的内存未被释放,导致内存无法重用。每次 malloc()/calloc() 后使用 free() 释放内存。
悬挂指针指针指向已释放的内存,继续使用导致未定义行为。释放内存后,将指针设置为 NULL
双重释放对同一内存块调用多次 free(),导致程序崩溃。确保每块内存只被释放一次,释放后将指针置 NULL
越界访问通过指针访问超出分配内存范围的地址,可能破坏数据。确保指针操作在合法范围内,使用安全的函数如 strncpy()
未初始化指针指针未被赋值直接使用,指向未知地址,导致程序崩溃。初始化指针,或在分配内存前将其设置为 NULL
不匹配的类型转换指针类型与数据类型不匹配,导致错误的数据访问。确保指针类型与所指向的数据类型一致,必要时使用强制类型转换。
忘记返回的内存指针动态分配内存但未保存返回的指针,导致内存无法访问和释放。始终保存 malloc()/calloc() 的返回值,确保后续访问和释放。

总结与最佳实践

内存管理最佳实践

最佳实践说明
始终检查内存分配函数的返回值确保 malloc()calloc() 等成功分配内存,返回值不为 NULL
每次分配内存后立即初始化使用 calloc() 或在分配后手动初始化,确保内存中没有未定义的数据。
使用完内存后立即释放避免内存泄漏,释放不再需要的内存。
释放内存后将指针置为 NULL防止悬挂指针,避免误操作。
避免使用不安全的字符串函数使用 strncpy()snprintf() 等安全函数,防止缓冲区溢出。
小心指针运算与数组索引确保指针操作在合法范围内,避免越界访问。
使用工具检测内存错误使用 valgrindAddressSanitizer 等工具检测内存泄漏与错误。
注释清晰,文档充分清楚地标注指针的用途、内存管理逻辑,方便后续维护。
避免复杂的多级指针多级指针容易出错,尽量简化指针使用,提升代码可读性。
使用结构体封装相关数据与指针提升代码组织性,减少全局变量,降低错误率。

指针最佳实践

最佳实践说明
初始化指针声明指针时将其初始化为 NULL 或指向有效内存。
限制指针的作用范围尽量将指针的使用限制在必要的范围内,减少全局指针的使用。
避免裸指针操作使用高级数据结构或封装指针操作,减少直接操作裸指针的次数。
使用指针时保持清晰的思路明确指针指向的数据类型和用途,避免混淆。
谨慎处理函数返回的指针确保函数返回的指针有效且符合预期,避免返回局部变量的指针。

全面示例

示例 1:动态二维数组

这个示例展示了如何使用指针和动态内存分配来创建和操作一个二维数组。

#include <stdio.h>
#include <stdlib.h>// 函数声明
int** create_2d_array(int rows, int cols);
void free_2d_array(int **array, int rows);
void print_2d_array(int **array, int rows, int cols);int main() {int rows = 3;int cols = 4;// 创建二维数组int **array = create_2d_array(rows, cols);if (array == NULL) {fprintf(stderr, "Failed to create 2D array.\n");return EXIT_FAILURE;}// 初始化数组for(int i = 0; i < rows; i++) {for(int j = 0; j < cols; j++) {array[i][j] = i * cols + j;}}// 打印数组print_2d_array(array, rows, cols);// 释放内存free_2d_array(array, rows);return EXIT_SUCCESS;
}// 创建二维数组
int** create_2d_array(int rows, int cols) {// 分配指针数组,每个指针将指向一行int **array = (int**)malloc(rows * sizeof(int*));if (array == NULL) return NULL;// 分配每行的内存for(int i = 0; i < rows; i++) {array[i] = (int*)malloc(cols * sizeof(int));if (array[i] == NULL) {// 释放已分配的行for(int j = 0; j < i; j++) {free(array[j]);}free(array);return NULL;}}return array;
}// 打印二维数组
void print_2d_array(int **array, int rows, int cols) {printf("2D Array:\n");for(int i = 0; i < rows; i++) {for(int j = 0; j < cols; j++) {printf("%d ", array[i][j]);}printf("\n");}
}// 释放二维数组内存
void free_2d_array(int **array, int rows) {for(int i = 0; i < rows; i++) {free(array[i]);}free(array);
}

程序说明

  1. 创建二维数组

    • create_2d_array 函数首先为行指针数组分配内存。
    • 然后为每一行分配内存,形成一个二维数组。
    • 如果任何一步失败,确保释放已分配的内存,避免内存泄漏。
  2. 初始化数组

    • 使用双重循环为每个元素赋值。
  3. 打印数组

    • 使用 print_2d_array 函数遍历并打印二维数组。
  4. 释放内存

    • free_2d_array 函数首先释放每一行的内存,然后释放行指针数组本身。

示例 2:使用指针操作字符串

这个示例展示了如何使用指针操作字符串,包括字符串复制、拼接和比较。

#include <stdio.h>
#include <string.h>int main() {char src[] = "Hello, ";char dest[20];// 使用指针复制字符串char *pSrc = src;char *pDest = dest;while(*pSrc != '\0') {*pDest = *pSrc;pSrc++;pDest++;}*pDest = '\0'; // 添加字符串结束符printf("Copied string: %s\n", dest); // 输出: Hello,// 拼接另一个字符串char *addition = "World!";pSrc = addition;pDest = dest;// 移动指针到 dest 的末尾while(*pDest != '\0') {pDest++;}// 复制 addition 到 dest 的末尾while(*pSrc != '\0') {*pDest = *pSrc;pSrc++;pDest++;}*pDest = '\0';printf("Concatenated string: %s\n", dest); // 输出: Hello, World!// 比较字符串if(strcmp(dest, "Hello, World!") == 0) {printf("Strings match.\n");} else {printf("Strings do not match.\n");}return 0;
}

输出

Copied string: Hello, 
Concatenated string: Hello, World!
Strings match.

注意事项

  • 字符串结束符:确保在字符串末尾添加 '\0',否则可能导致未定义行为。
  • 缓冲区大小:确保目标数组有足够的空间存储复制或拼接后的字符串,避免缓冲区溢出。
  • 使用安全函数:可以使用 strncpy()strncat() 等函数限制复制或拼接的长度,提高安全性。

示例 3:函数指针与回调

这个示例展示了如何使用函数指针来实现回调机制,类似于 Java 中的接口回调。

#include <stdio.h>// 定义一个函数类型
typedef int (*operation_func)(int, int);// 函数实现
int add(int a, int b) {return a + b;
}int multiply(int a, int b) {return a * b;
}// 接受函数指针的函数
int compute(int a, int b, operation_func op) {return op(a, b);
}int main() {int x = 5, y = 3;// 使用 add 函数作为回调int sum = compute(x, y, add);printf("Sum: %d\n", sum); // 输出: Sum: 8// 使用 multiply 函数作为回调int product = compute(x, y, multiply);printf("Product: %d\n", product); // 输出: Product: 15return 0;
}

输出

Sum: 8
Product: 15

注意事项

  • 函数指针类型匹配:确保函数指针的类型与被指向函数的签名一致。
  • 避免使用未初始化的函数指针:在使用前确保函数指针已经指向有效的函数。

示例 4:内存管理与指针的综合应用

这个综合示例展示了如何在 C 语言中进行动态内存分配、指针操作、字符串处理和错误管理。程序将用户输入的若干字符串存储在动态数组中,并在程序结束前释放所有分配的内存。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>// 定义最大字符串长度
#define MAX_STR_LEN 100int main() {int num_strings;printf("Enter number of strings to store: ");if (scanf("%d", &num_strings) != 1 || num_strings <= 0) {fprintf(stderr, "Invalid number of strings.\n");return EXIT_FAILURE;}// 清除输入缓冲区中的残留字符int c;while ((c = getchar()) != '\n' && c != EOF);// 动态分配指针数组,每个指针将指向一个字符串char **strings = (char**)malloc(num_strings * sizeof(char*));if (strings == NULL) {fprintf(stderr, "Memory allocation for strings array failed.\n");return EXIT_FAILURE;}// 读取每个字符串并动态分配内存存储for(int i = 0; i < num_strings; i++) {char buffer[MAX_STR_LEN];printf("Enter string %d: ", i + 1);if (fgets(buffer, sizeof(buffer), stdin) == NULL) {fprintf(stderr, "Error reading string.\n");// 释放已分配的字符串for(int j = 0; j < i; j++) {free(strings[j]);}free(strings);return EXIT_FAILURE;}// 移除换行符buffer[strcspn(buffer, "\n")] = '\0';// 分配内存存储字符串strings[i] = (char*)malloc((strlen(buffer) + 1) * sizeof(char));if (strings[i] == NULL) {fprintf(stderr, "Memory allocation for string %d failed.\n", i + 1);// 释放已分配的字符串for(int j = 0; j < i; j++) {free(strings[j]);}free(strings);return EXIT_FAILURE;}// 复制字符串到分配的内存strcpy(strings[i], buffer);}// 打印所有存储的字符串printf("\nStored Strings:\n");for(int i = 0; i < num_strings; i++) {printf("String %d: %s\n", i + 1, strings[i]);}// 释放所有分配的内存for(int i = 0; i < num_strings; i++) {free(strings[i]);}free(strings);return EXIT_SUCCESS;
}

程序说明

  1. 读取用户输入

    • 程序首先提示用户输入要存储的字符串数量 num_strings
    • 使用 scanf() 读取整数,并检查输入的有效性。
    • 清除输入缓冲区中的残留字符,避免后续 fgets() 读取到多余的换行符。
  2. 动态分配指针数组

    • 使用 malloc() 为指向字符串的指针数组分配内存。每个元素将是一个 char*,指向一个字符串。
  3. 读取并存储字符串

    • 通过循环读取每个字符串。
    • 使用 fgets() 安全地读取字符串,防止缓冲区溢出。
    • 移除字符串末尾的换行符 \n
    • 为每个字符串动态分配内存,大小为输入字符串的长度加一(用于存储 '\0')。
    • 使用 strcpy() 将输入的字符串复制到分配的内存中。
  4. 打印存储的字符串

    • 遍历指针数组,打印每个存储的字符串。
  5. 释放内存

    • 通过循环释放每个分配的字符串内存。
    • 最后,释放指针数组本身。

注意事项

  • 输入安全:使用 fgets() 代替 scanf("%s", ...) 可以防止缓冲区溢出。
  • 内存分配检查:每次内存分配后都检查返回值,确保分配成功。若失败,及时释放已分配的内存,避免内存泄漏。
  • 内存释放顺序:先释放每个字符串,再释放指针数组本身。

示例 5:指针与函数的结合使用

这个示例展示了如何将指针作为参数传递给函数,以修改函数外部的变量值。

#include <stdio.h>// 函数声明,使用指针参数
void increment(int *num);int main() {int a = 10;printf("Before increment: %d\n", a); // 输出: 10increment(&a); // 传递 a 的地址printf("After increment: %d\n", a);  // 输出: 11return 0;
}// 函数定义,使用指针参数
void increment(int *num) {(*num)++; // 通过指针修改变量的值
}

输出

Before increment: 10
After increment: 11

注意事项

  • 传递地址:使用 & 运算符传递变量的地址,使函数能够通过指针修改变量。
  • 解引用操作:在函数内部使用 * 运算符访问和修改指针指向的值。

内存管理与指针的常见错误

以下表格总结了在内存管理和指针操作中常见的错误类型、描述以及解决方案,帮助您在编程过程中快速查找和避免这些错误。

错误类型描述解决方案
内存泄漏分配的内存未被释放,导致内存无法重用。每次 malloc()/calloc() 后使用 free() 释放内存。
悬挂指针指针指向已释放的内存,继续使用导致未定义行为。释放内存后,将指针设置为 NULL
双重释放对同一内存块调用多次 free(),导致程序崩溃。确保每块内存只被释放一次,释放后将指针置 NULL
越界访问通过指针访问超出分配内存范围的地址,可能破坏数据。确保指针操作在合法范围内,使用安全的函数如 strncpy()
未初始化指针指针未被赋值直接使用,指向未知地址,导致程序崩溃。初始化指针,或在分配内存前将其设置为 NULL
不匹配的类型转换指针类型与数据类型不匹配,导致错误的数据访问。确保指针类型与所指向的数据类型一致,必要时使用强制类型转换。
忘记返回的内存指针动态分配内存但未保存返回的指针,导致内存无法访问和释放。始终保存 malloc()/calloc() 的返回值,确保后续访问和释放。

详细说明

  1. 内存泄漏

    • 描述:分配的内存未被释放,随着程序运行,未释放的内存不断积累,最终可能耗尽系统内存。
    • 解决方案:每次使用 malloc()calloc()realloc() 分配内存后,确保在不需要时调用 free() 释放内存。
  2. 悬挂指针

    • 描述:指针指向已释放的内存区域,继续使用会导致未定义行为,如程序崩溃或数据损坏。
    • 解决方案:在调用 free() 后,将指针设置为 NULL。例如:
      free(p);
      p = NULL;
      
  3. 双重释放

    • 描述:对同一块内存调用 free() 多次,可能导致程序崩溃或不可预测的行为。
    • 解决方案:确保每块内存只被释放一次。释放后将指针置 NULL,防止再次释放。
  4. 越界访问

    • 描述:通过指针访问超出分配内存范围的地址,可能导致数据损坏或程序崩溃。
    • 解决方案:确保指针操作在合法范围内,使用安全的函数如 strncpy()snprintf() 限制操作的长度。
  5. 未初始化指针

    • 描述:指针在使用前未被初始化,指向未知内存地址,使用会导致程序崩溃或数据损坏。
    • 解决方案:在声明指针时将其初始化为 NULL 或指向有效内存。
      int *p = NULL;
      
  6. 不匹配的类型转换

    • 描述:指针类型与数据类型不匹配,导致错误的数据访问和操作。
    • 解决方案:确保指针类型与所指向的数据类型一致,必要时使用强制类型转换。
      double d = 3.14;
      int *p = (int*)&d; // 不推荐,可能导致未定义行为
      
  7. 忘记返回的内存指针

    • 描述:动态分配内存但未保存返回的指针,导致内存无法访问和释放,形成内存泄漏。
    • 解决方案:始终保存 malloc()/calloc() 的返回值,确保后续访问和释放。
      int *p = malloc(sizeof(int));
      if (p != NULL) {// 使用 pfree(p);
      }
      

全面示例

示例:动态字符串数组

这个示例展示了如何使用指针和动态内存分配来创建一个能够存储任意数量字符串的数组,并展示了如何安全地进行内存管理。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>// 定义最大字符串长度
#define MAX_STR_LEN 100int main() {int num_strings;printf("Enter number of strings to store: ");if (scanf("%d", &num_strings) != 1 || num_strings <= 0) {fprintf(stderr, "Invalid number of strings.\n");return EXIT_FAILURE;}// 清除输入缓冲区中的残留字符int c;while ((c = getchar()) != '\n' && c != EOF);// 动态分配指针数组,每个指针将指向一个字符串char **strings = (char**)malloc(num_strings * sizeof(char*));if (strings == NULL) {fprintf(stderr, "Memory allocation for strings array failed.\n");return EXIT_FAILURE;}// 读取每个字符串并动态分配内存存储for(int i = 0; i < num_strings; i++) {char buffer[MAX_STR_LEN];printf("Enter string %d: ", i + 1);if (fgets(buffer, sizeof(buffer), stdin) == NULL) {fprintf(stderr, "Error reading string.\n");// 释放已分配的字符串for(int j = 0; j < i; j++) {free(strings[j]);}free(strings);return EXIT_FAILURE;}// 移除换行符buffer[strcspn(buffer, "\n")] = '\0';// 分配内存存储字符串strings[i] = (char*)malloc((strlen(buffer) + 1) * sizeof(char));if (strings[i] == NULL) {fprintf(stderr, "Memory allocation for string %d failed.\n", i + 1);// 释放已分配的字符串for(int j = 0; j < i; j++) {free(strings[j]);}free(strings);return EXIT_FAILURE;}// 复制字符串到分配的内存strcpy(strings[i], buffer);}// 打印所有存储的字符串printf("\nStored Strings:\n");for(int i = 0; i < num_strings; i++) {printf("String %d: %s\n", i + 1, strings[i]);}// 释放所有分配的内存for(int i = 0; i < num_strings; i++) {free(strings[i]);}free(strings);return EXIT_SUCCESS;
}

程序说明

  1. 读取用户输入

    • 程序提示用户输入要存储的字符串数量 num_strings
    • 使用 scanf() 读取整数,并检查输入的有效性。
    • 清除输入缓冲区中的残留字符,避免后续 fgets() 读取到多余的换行符。
  2. 动态分配指针数组

    • 使用 malloc() 为指向字符串的指针数组分配内存。每个元素将是一个 char*,指向一个字符串。
  3. 读取并存储字符串

    • 通过循环读取每个字符串。
    • 使用 fgets() 安全地读取字符串,防止缓冲区溢出。
    • 移除字符串末尾的换行符 \n
    • 为每个字符串动态分配内存,大小为输入字符串的长度加一(用于存储 '\0')。
    • 使用 strcpy() 将输入的字符串复制到分配的内存中。
  4. 打印存储的字符串

    • 遍历指针数组,打印每个存储的字符串。
  5. 释放内存

    • 通过循环释放每个分配的字符串内存。
    • 最后,释放指针数组本身。

注意事项

  • 输入安全:使用 fgets() 代替 scanf("%s", ...) 可以防止缓冲区溢出。
  • 内存分配检查:每次内存分配后都检查返回值,确保分配成功。若失败,及时释放已分配的内存,避免内存泄漏。
  • 内存释放顺序:先释放每个字符串,再释放指针数组本身。

示例 6:指针与函数结合使用

这个示例展示了如何将指针作为参数传递给函数,以修改函数外部的变量值。

#include <stdio.h>// 函数声明,使用指针参数
void increment(int *num);int main() {int a = 10;printf("Before increment: %d\n", a); // 输出: 10increment(&a); // 传递 a 的地址printf("After increment: %d\n", a);  // 输出: 11return 0;
}// 函数定义,使用指针参数
void increment(int *num) {(*num)++; // 通过指针修改变量的值
}

输出

Before increment: 10
After increment: 11

注意事项

  • 传递地址:使用 & 运算符传递变量的地址,使函数能够通过指针修改变量。
  • 解引用操作:在函数内部使用 * 运算符访问和修改指针指向的值。

内存管理与指针的常见错误详解

内存管理与指针错误

错误类型描述解决方案
内存泄漏分配的内存未被释放,导致内存无法重用。每次 malloc()/calloc() 后使用 free() 释放内存。
悬挂指针指针指向已释放的内存,继续使用导致未定义行为。释放内存后,将指针设置为 NULL
双重释放对同一内存块调用多次 free(),导致程序崩溃。确保每块内存只被释放一次,释放后将指针置 NULL
越界访问通过指针访问超出分配内存范围的地址,可能破坏数据。确保指针操作在合法范围内,使用安全的函数如 strncpy()
未初始化指针指针未被赋值直接使用,指向未知地址,导致程序崩溃。初始化指针,或在分配内存前将其设置为 NULL
不匹配的类型转换指针类型与数据类型不匹配,导致错误的数据访问。确保指针类型与所指向的数据类型一致,必要时使用强制类型转换。
忘记返回的内存指针动态分配内存但未保存返回的指针,导致内存无法访问和释放。始终保存 malloc()/calloc() 的返回值,确保后续访问和释放。

详细说明

  1. 内存泄漏

    • 描述:程序在运行时分配了内存,但未释放,导致内存无法重用。
    • 示例
      #include <stdlib.h>int main() {int *p = malloc(sizeof(int));if (p != NULL) {*p = 10;// 忘记调用 free(p);}return 0;
      }
      
    • 解决方案
      每次使用 malloc()calloc() 分配内存后,确保在不需要时调用 free() 释放内存。
      free(p);
      p = NULL;
      
  2. 悬挂指针

    • 描述:指针指向已释放的内存,继续使用会导致未定义行为。
    • 示例
      #include <stdlib.h>int main() {int *p = malloc(sizeof(int));if (p != NULL) {*p = 10;free(p);// p 现在是悬挂指针*p = 20; // 未定义行为}return 0;
      }
      
    • 解决方案
      释放内存后,将指针设置为 NULL,避免指针悬挂。
      free(p);
      p = NULL;
      
  3. 双重释放

    • 描述:对同一内存块调用多次 free(),可能导致程序崩溃或其他未定义行为。
    • 示例
      #include <stdlib.h>int main() {int *p = malloc(sizeof(int));if (p != NULL) {free(p);free(p); // 双重释放,未定义行为}return 0;
      }
      
    • 解决方案
      确保每块内存只被释放一次,释放后将指针置 NULL
      free(p);
      p = NULL;
      
  4. 越界访问

    • 描述:通过指针访问超出分配内存范围的地址,可能导致数据损坏或程序崩溃。
    • 示例
      #include <stdio.h>
      #include <stdlib.h>int main() {int *arr = malloc(3 * sizeof(int));if (arr != NULL) {arr[0] = 1;arr[1] = 2;arr[2] = 3;arr[3] = 4; // 越界访问,未定义行为free(arr);}return 0;
      }
      
    • 解决方案
      确保指针操作在合法范围内,使用安全的函数如 strncpy()snprintf() 限制操作的长度。
  5. 未初始化指针

    • 描述:指针未被赋值直接使用,指向未知内存地址,导致程序崩溃或数据损坏。
    • 示例
      #include <stdio.h>int main() {int *p; // 未初始化*p = 10; // 未定义行为printf("%d\n", *p);return 0;
      }
      
    • 解决方案
      初始化指针,或在分配内存前将其设置为 NULL
      int *p = NULL;
      // 或
      int a = 10;
      int *p = &a;
      
  6. 不匹配的类型转换

    • 描述:指针类型与数据类型不匹配,导致错误的数据访问和操作。
    • 示例
      #include <stdio.h>int main() {double d = 3.14;int *p = (int*)&d; // 不匹配,可能导致错误的数据访问printf("%d\n", *p);return 0;
      }
      
    • 解决方案
      确保指针类型与所指向的数据类型一致,必要时使用强制类型转换,但应谨慎。
      double *p = &d; // 正确
      
  7. 忘记返回的内存指针

    • 描述:动态分配内存但未保存返回的指针,导致内存无法访问和释放,形成内存泄漏。
    • 示例
      #include <stdlib.h>void allocate_memory() {malloc(100 * sizeof(int)); // 返回值被忽略,内存泄漏
      }int main() {allocate_memory();return 0;
      }
      
    • 解决方案
      始终保存 malloc()/calloc() 的返回值,确保后续访问和释放。
      int *p = malloc(100 * sizeof(int));
      if (p != NULL) {// 使用 pfree(p);
      }
      

总结与最佳实践

内存管理最佳实践

最佳实践说明
始终检查内存分配函数的返回值确保 malloc()calloc() 等成功分配内存,返回值不为 NULL
每次分配内存后立即初始化使用 calloc() 或在分配后手动初始化,确保内存中没有未定义的数据。
使用完内存后立即释放避免内存泄漏,释放不再需要的内存。
释放内存后将指针置为 NULL防止悬挂指针,避免误操作。
避免使用不安全的字符串函数使用 strncpy()snprintf() 等安全函数,防止缓冲区溢出。
小心指针运算与数组索引确保指针操作在合法范围内,避免越界访问。
使用工具检测内存错误使用 valgrindAddressSanitizer 等工具检测内存泄漏与错误。
注释清晰,文档充分清楚地标注指针的用途、内存管理逻辑,方便后续维护。
避免复杂的多级指针多级指针容易出错,尽量简化指针使用,提升代码可读性。
使用结构体封装相关数据与指针提升代码组织性,减少全局变量,降低错误率。

指针最佳实践

最佳实践说明
初始化指针声明指针时将其初始化为 NULL 或指向有效内存。
限制指针的作用范围尽量将指针的使用限制在必要的范围内,减少全局指针的使用。
避免裸指针操作使用高级数据结构或封装指针操作,减少直接操作裸指针的次数。
使用指针时保持清晰的思路明确指针指向的数据类型和用途,避免混淆。
谨慎处理函数返回的指针确保函数返回的指针有效且符合预期,避免返回局部变量的指针。

错误预防与检测

错误类型预防措施
内存泄漏每次 malloc()/calloc() 后使用 free() 释放内存。
悬挂指针释放内存后,将指针设置为 NULL
双重释放确保每块内存只被释放一次,释放后将指针置 NULL
越界访问确保指针操作在合法范围内,使用安全的函数如 strncpy()
未初始化指针初始化指针,或在分配内存前将其设置为 NULL
不匹配的类型转换确保指针类型与所指向的数据类型一致,必要时使用强制类型转换。
忘记返回的内存指针始终保存 malloc()/calloc() 的返回值,确保后续访问和释放。

推荐工具

  • Valgrind:检测内存泄漏、未初始化内存使用和其他内存错误。
  • AddressSanitizer:编译器内置的内存错误检测工具,快速检测缓冲区溢出和内存泄漏。

结语

从 Java 转向 C 语言开发,内存管理和指针操作是最主要的挑战之一。C 语言提供了强大的内存控制能力,但也带来了更多的责任和潜在的错误风险。通过系统的学习和大量的实践,您将逐步掌握这些概念,编写出高效且安全的 C 程序。

最佳学习建议

  1. 多实践:通过编写各种示例程序,熟悉内存分配、指针操作和常见错误。
  2. 使用工具:利用 valgrindAddressSanitizer 等工具检测内存泄漏和错误。
  3. 阅读文档:参考 C 标准库文档,深入理解每个函数的用途和使用方法。
  4. 学习调试技巧:掌握调试器(如 gdb)的使用,帮助定位和修复问题。
  5. 关注安全性:养成良好的编程习惯,避免常见的内存和指针错误。

相关文章:

Java转C之内存管理

从 Java 转向 C 语言开发&#xff0c;内存管理和指针是两个核心且关键的概念。这些概念在 Java 中被抽象和自动化处理&#xff0c;而在 C 语言中则需要开发者手动管理和操作。 目录 内存管理概述指针基础 指针的定义与初始化地址运算符 & 和解引用运算符 *指针运算 动态内…...

共享内存和信号量

一、共享内存原理 1、基础前提 共享内存是为了进程之间通信服务的。进程间的通信的核心是要让两个不同的进程看到同一份资源。之前的匿名管道通过父子进程继承的关系&#xff0c;让父子进程看到同一份资源&#xff0c;进而进行通信&#xff1b;命名管道则是通过唯一的路径让两…...

SED命令

基本用法&#xff1a; sed命令具体应用&#xff1a; 1.删除第2-5行 nl regular_express.txt | sed 2d结果&#xff1a; 2.在第二行之后新增两行&#xff0c;分别为"drink tea…" 和"drink beer?" nl regular_express.txt | sed 2 a\ drink tea...\…...

洛谷 P6368 [COCI2006-2007#6] MAGIJA C语言

题目&#xff1a;https://www.luogu.com.cn/problem/P6368 题目背景 知名魔术师 AlDimi Kartimi 需要你帮助他来设计他扑克牌的背面图案。 题目描述 众所周知&#xff0c;扑克牌的背面是一个矩形&#xff0c;通过做两边的中垂线可以将矩形分成相等的四部分。 AlDimi Karti…...

TDesign:Picker 选择器

Picker 选择器 API文档地址 单列选择器用法 /// view onTap:(){TDPicker.showMultiPicker(context,data: [controller.coinList],title: ,rightTextStyle: TextStyle(color: AppColors.ColorMain),onConfirm: (selected) {controller.onTapCoin(selected);Navigator.of(contex…...

LLMs之Agent之Lares:Lares的简介、安装和使用方法、案例应用之详细攻略

LLMs之Agent之Lares&#xff1a;Lares的简介、安装和使用方法、案例应用之详细攻略 导读&#xff1a;这篇博文介绍了 Lares&#xff0c;一个由简单的 AI 代理驱动的智能家居助手模拟器&#xff0c;它展现出令人惊讶的解决问题能力。 >> 背景痛点&#xff1a;每天都有新的…...

实验3-实时数据流处理-Flink

1.前期准备 &#xff08;1&#xff09;Flink基础环境安装 参考文章&#xff1a; 利用docker-compose来搭建flink集群-CSDN博客 显示为这样就成功了 &#xff08;2&#xff09;把docker&#xff0c;docker-compose&#xff0c;kafka集群安装配置好 参考文章&#xff1a; …...

axios笔记

一、axios是什么 前端最流行的ajax请求库 二、json-server json-server可以帮助我们搭建http服务 为什么要搭建http服务&#xff1f; &#xff1a;是因为我们用axios的时候&#xff0c;需要向服务端发送请求 我们需要服务端这样一个角色&#xff0c;来与axios结合做实践 1.…...

python拆分Excel文件

按Sheet拆分Excel 或 按照某一列的不同值拆分Excel。文档样式如下&#xff1a; 结果&#xff1a;红色是按照Sheet名拆出的&#xff0c;蓝色和橙色是某个Sheet按照某列的不同值拆分的。 代码&#xff1a; # -*- coding: utf-8 -*- """ 拆分excel文件——按照…...

使用IntelliJ IDEA开发Gradle插件并发布到本地

博主所用软件版本为&#xff1a; IntelliJ IDEA 2024.1.4 (Community Edition) Android Studio Ladybug Feature Drop | 2024.2.2 Beta 1 1、制作gradle插件&#xff08;IntelliJ IDEA 2024.1.4&#xff09; 新建groovy工程&#xff0c;File–>New–>Project… 右键点…...

C# 通过smtp发送邮件(配置好了参数可以直接用)

可以直接用 using System; using System.Net; using System.Net.Mail;class EmailSender {// 发送邮件的函数&#xff0c;接收SMTP配置作为参数public static void SendEmail(string smtpServer, int smtpPort, string senderEmail, string senderPassword, string subject, s…...

【数据结构】栈和队列

目录 1.栈 1.1.栈的概念及结构 1.2.栈的实现 2.队列 2.1.队列的概念及结构 2.2.队列的实现 3.运用栈理解一道题 4.使用两个队列实现一个栈 1.栈 1.1.栈的概念及结构 首先&#xff0c;我们来了解一种新的数据结构——栈。栈是一种特殊的线性表&#xff0c;其只允许在固…...

使用 Spring Doc 为 Spring REST API 生成 OpenAPI 3.0 文档

Spring Boot 3 整合 springdoc-openapi 概述 springdoc-openapi 是一个用于自动生成 OpenAPI 3.0 文档的库&#xff0c;它支持与 Spring Boot 无缝集成。通过这个库&#xff0c;你可以轻松地生成和展示 RESTful API 的文档&#xff0c;并且可以使用 Swagger UI 或 ReDoc 进行…...

网络数据库安全和数据安全概述

1、数据安全的三要素 完整性&#xff0c;机密性&#xff0c;可用性 2、安全技术和措施 数据完整性技术、数据备份和恢复技术、数据加密技术、访问控制技术、用户管理和身份验证技术 3、数据库安全 数据库安全是指数据库的任何部分都不允许受到侵害&#xff0c;或未经授权的…...

STM32F10X与GD32工程创建区别

1.宏定义区别 GD32虽然与STM32F10X的管脚兼容&#xff0c;但是用keil创建工程时&#xff0c;GD32在选择芯片型号的时候就已经自己定义了宏。STM32F10x需要手动定义宏。在以下界面定义用到的单片机。 2.时钟区别。GD32最大时钟为120Mhz,所用的hal库里面的系统初始化会初始化时钟…...

cmakelist使用总结

支持能编译成在不同系统上运行程序的方法 我们代码就一份&#xff0c;但有时需要编译成arm32&#xff0c;有时需要编译成x86_64,或更多 1、首先先将代码定义一个名称&#xff1a; #将所有的源文件列为一个集合&#xff0c;集合名字叫做SRC_LISTS set(SRC_LISTS main.cpp A.cpp…...

【论文阅读】国际开源发展经验及其对我国开源创新体系建设的启示

作者&#xff1a;包云岗老师 包云岗老师是计算机体系结构方向的大牛&#xff0c;推动了体系结构方面的开源事业! 欢迎对本栏目感兴趣的人学习"一生一芯"~ 学习体会&#xff1a; 承接前文&#xff0c;唐志敏老师讲到已有的软硬件生态系统和开发成本制约了对新结构的探…...

React 初学者指南:从零开始构建第一个 React 应用

React 是一个用于构建用户界面的 JavaScript 库&#xff0c;它能够让我们高效地构建复杂的 UI。对于 React 初学者来说&#xff0c;最重要的就是理解它的基本概念以及如何开始使用它。本文将引导你从零开始构建你的第一个 React 应用 1.1. 安装 React 环境 首先&#xff0c…...

力扣93题:复原 IP 地址

力扣93题&#xff1a;复原 IP 地址&#xff08;C语言实现详解&#xff09; 题目描述 给定一个只包含数字的字符串 s&#xff0c;复原它并返回所有可能的 IP 地址格式。 有效的 IP 地址需满足以下条件&#xff1a; IP 地址由四个整数&#xff08;每个整数位于 0 到 255 之间…...

字符编码发展历史

文章目录 发展历史- ASCII- ISO-8859- Unicode- UTF系列- UTF-8- UTF-16- UTF-32 - 中文编码- GB2312- GBK- GB18030 - 其他编码 归纳总结 发展历史 计算机的基础是二进制数字&#xff08;0和1&#xff09;。虽然人类日常使用的是各种字符&#xff08;如字母、数字、符号等&am…...

aws codepipeline + github + sonarqube + jenkins实践CI/CD

https://blog.csdn.net/u011564831/article/details/144007981文章浏览阅读1.2k次&#xff0c;点赞31次&#xff0c;收藏21次。本文使用 Jenkins 结合 CodeBuild, CodeDeploy 实现 Serverless 的 CI/CD 工作流&#xff0c;用于自动化发布已经部署 lambda 函数。在 AWS 海外区&a…...

KNN_识别图片数字

1.实验过程省略灰度化处理过程&#xff0c;用已经处理好的txt数据进行训练 3.jpg from PIL import Image def imgtotxt(imgfile,txtfile,size (32,32)):image_file Image.open(imgfile).resize(size,Image.LANCZOS).convert(L)width,height image_file.sizef open(txtfile,…...

python 清华pip镜像源报HTTP error 403

报错信息 ERROR: HTTP error 403 while getting https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/52/79/a64937a2185b91a96cc5406e3ea58120980c725543d047e112fb3084a972/fake_useragent-2.0.0-py3-none-any.whl (from https://mirrors.tuna.tsinghua.edu.cn/pypi/we…...

HTTP 网络技术学习:缓存;为什么有时候出现问题要清除浏览器缓存?客户端缓存和服务端缓存是什么。

目录&#xff1a; 问题的背景Http的缓存是什么客户端设置缓存&#xff0c;需要服务端允许&#xff1f;缓存分类&#xff1a;为什么会有客户端缓存和服务器端缓存&#xff1f;客户端缓存的参数设置&#xff0c;举例代码实现缓存 一、问题的背景 有时候网站出现问题&#xff0…...

极限科技三周年庆典:追求极致,共创未来科技新篇章

2024 年 12 月 3 日&#xff0c;一场盛大的庆典活动在香港 IFS 集团旗下长沙玛珂酒店璀璨启幕&#xff0c;极限科技迎来了其三周年的辉煌庆典。此次庆典不仅是对极限科技过往成就的回顾与庆祝&#xff0c;更是对未来科技梦想的展望并开始新的启航。 庆典现场&#xff0c;极限科…...

利用PHP和GD库实现图片拼接的方法

利用PHP和GD库实现图片拼接的方法主要涉及到加载图片资源、创建目标画布、将图片资源绘制到目标画布上&#xff0c;并最终输出或保存拼接后的图片。以下是实现图片拼接的基本步骤&#xff1a; 加载图片资源&#xff1a; 使用imagecreatefromjpeg()、imagecreatefrompng()或ima…...

SpringBoot自动配置底层核心源码

SpringBoot底层核心源码 一、工程创建二、进一步改造三、自动配置 探究SpringBoot的自动配置原理&#xff0c;我们可以自己写一个启动类的注解。 一、工程创建 首先创建一个工程&#xff0c;工程目录如下&#xff1a; 自定义一个启动函数&#xff1a; package org.springboo…...

故障识别 | GADF-CNN-SSA-XGBoost数据分类预测/故障识别(Matlab)

故障识别 | GADF-CNN-SSA-XGBoost数据分类预测/故障识别&#xff08;Matlab&#xff09; 目录 故障识别 | GADF-CNN-SSA-XGBoost数据分类预测/故障识别&#xff08;Matlab&#xff09;分类效果基本描述程序设计参考资料 分类效果 基本描述 格拉姆角场差&#xff08;GADF&#…...

【VUE小型网站开发】优化通用配置 二

1. 引入 MyBatis Plus 1.1 添加依赖 <dependencies><!-- Spring Boot Starter Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- My…...

基于阻塞队列的生产者消费者模型动画演示

一个基于阻塞队列的生产者消费者模型的动画演示&#xff1a; 这是打包好的程序。程序是用 QT 写的。 通过网盘分享的文件&#xff1a;CP模型.7z 链接: https://pan.baidu.com/s/1YjC7YiSqHGqdr6bbffaDWg?pwde6g5 提取码: e6g5 CP模型...

springSecurity认证流程

Spring Security 是spring家族中的一个安全管理框架。相比于另一个安全框架Shiro&#xff0c;它提供更丰富的功能和社区资源&#xff0c;但也较难上手。所以一般大项目用spring Security&#xff0c;小项目用Shiro。 一般web应用需要认证和授权&#xff0c;这也是spring Secur…...

vite5+vue3+Ts5 开源图片预览器上线

images-viewer-vue3&#xff1a;一款Vue3的轻量级图像查看器&#xff0c;它基于Flip动画技术&#xff0c;支持PC和h5移动网页预览照片&#xff0c;如果它是Vue3开发的产品。 npm开源地址:https://www.npmjs.com/package/images-viewer-vue3?activeTabreadme Flip 动画 < …...

Qt开发:元对象系统的介绍和使用

文章目录 概述元对象系统的概念和组成QObject 的详细介绍1.QObject 的主要特性信号与槽机制动态属性对象树&#xff08;Object Trees&#xff09;事件处理 2.QMetaObject的主要特性函数签名常用功能 概述 Qt本身并不是一种编程语言&#xff0c;它实质上是一个跨平台的C开发类库…...

.vscode文件中各个脚本需要修改的地方

NOTE&#xff1a; 此篇文章由VSCode编辑GCC for ARM交叉编译工具链Makefile构建OpenOCD调试&#xff08;基于STM32的标准库&#xff09;派生而来&#xff0c;对.vscode文件中各个脚本需要修改的地方作补充说明。 tasks.json 该json文件的主要作用&#xff1a;使用XX名称去执行…...

JavaScript模块化规范

欢迎来到“雪碧聊技术”CSDN博客&#xff01; 在这里&#xff0c;您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者&#xff0c;还是具有一定经验的开发者&#xff0c;相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导&#xff0c;我将…...

第二篇:k8s工作流程

我们来看通过deployment部署pod的常规流程&#xff1a; kubectl向apiserver发送部署请求&#xff08;例如使用 kubectl create -f deployment.yml&#xff09;apiserver将 Deployment 持久化到etcd&#xff1b;etcd与apiserver进行一次http通信。controller manager通过watch a…...

什么是数据架构?

数据架构是如何使用数据的蓝图--它是数据和数据相关资源的高层结构&#xff0c;是整个组织的数据框架&#xff0c;包括模型、规则和标准。通过高效的数据架构&#xff0c;企业可以跟踪整个 IT 基础设施中数据的获取、移动、存储、安全性和可访问性。 数据架构总览 数据架构是…...

ChatGPT、Python和OpenCV支持下的空天地遥感数据识别与计算——从0基础到15个案例实战

在科技飞速发展的时代&#xff0c;遥感数据的精准分析已经成为推动各行业智能决策的关键工具。从无人机监测农田到卫星数据支持气候研究&#xff0c;空天地遥感数据正以前所未有的方式为科研和商业带来深刻变革。然而&#xff0c;对于许多专业人士而言&#xff0c;如何高效地处…...

微信小程序权限授权工具类

最近写微信小程序的时候需要在页面获取设备权限&#xff0c;又不想每个页面都写&#xff0c;就写了一个工具类方便管理 /*** 权限工具类用于获取授权* 权限工具类使用方法&#xff0c;注意调用时函数需要定义为异步函数 async* import PermissionUtils from "./permissio…...

CT中的2D、MPR、VR渲染、高级临床功能

CT中的2D、MPR、VR渲染 在CT&#xff08;计算机断层扫描&#xff09;中&#xff0c;2D、MPR&#xff08;多平面重建&#xff09;、VR&#xff08;体积渲染&#xff09;是不同的图像显示和处理技术&#xff0c;它们各自有独特的用途和优势。下面分别介绍这三种技术&#xff1a;…...

【MySQL】Windows下重启MySQL服务时,报错:服务名无效

1、问题描述 在终端中&#xff0c;停止、启动MySQL服务时报错&#xff1a;服务名无效 2、原因分析 1&#xff09;权限不够 如果是权限不够&#xff0c;会提示&#xff1a;系统错误5&#xff0c;拒绝访问。 2&#xff09;服务名错误 如果是服务名错误&#xff0c;会提示“…...

java枚举的基本用法

在 Java 中&#xff0c;枚举&#xff08;enum&#xff09;是一种特殊的类&#xff0c;用于定义一组常量。它可以使代码更具可读性和可维护性。枚举类型可以用于表示固定的、有限的值集合&#xff0c;比如星期几、季节、方向等。 以下是一些常见的枚举用法示例&#xff1a; 1.…...

Web网络安全

一. 浏览器系统安全方面&#xff0c;使用多进程方案&#xff0c;而js主线程运行在渲染进程中&#xff0c;渲染进程时运行在沙箱中的&#xff0c;没有对本地OS文件的直接读写权限&#xff0c;所以需要通过IPC与浏览器主线程通信&#xff0c;才可以获取cookie等信息&#xff0c;这…...

【分子材料发现】——GAP:催化过程中吸附构型的多模态语言和图学习(数据集处理详解)(二)

Multimodal Language and Graph Learning of Adsorption Configuration in Catalysis https://arxiv.org/abs/2401.07408Paper Data: https://doi.org/10.6084/m9.figshare.27208356.v2 1 Dataset CatBERTa训练的文本字符串输入来源于Open Catalyst 2020 &#xff08;OC20…...

matlab Delaunay三角剖分提取平面点云的边界

目录 一、算法原理1、算法概述2、参考文献二、代码实现三、结果展示四、详细过程版一、算法原理 1、算法概述 Delaunay三角网在生成三角网过程中,以最近的三点形成三角形,且各三角形的边皆不相交,每条边都使用所在的三角形的顶点验算并记录相应的2个顶点坐标。整个验算过程…...

Spring07——AOP通知以及几个相关案例

切入点表达式 注意&#xff0c;不是参数&#xff0c;是参数类型 可以使用通配符描述切入点&#xff0c;快速描述 ■ *&#xff1a;单个独立的任意符号&#xff0c;可以独立出现&#xff0c;也可以作为前缀或者后缀的通配符出现 execution(public∗com.itheima.∗.UserServi…...

【AI工具】强大的AI编辑器Cursor详细使用教程

目录 一、下载安装与注册 二、内置模型与配置 三、常用快捷键 四、项目开发与问答 五、注意事项与技巧 参考资料 近日&#xff0c;由四名麻省理工学院&#xff08;MIT&#xff09;本科生共同创立的Anysphere公司宣布&#xff0c;其开发的AI代码编辑器Cursor在成立短短两年…...

CV工程师专用键盘开源项目硬件分析

1、前言 作为一个电子发烧友&#xff0c;你是否有遇到过这样的问题呢。当我们去查看函数定义的时候&#xff0c;需要敲击鼠标右键之后选择go to definition。更高级一些&#xff0c;我们使用键盘的快捷键来查看定义&#xff0c;这时候可以想象一下&#xff0c;你左手按下ALT&a…...

STM32标准固件库官网下载方法

Keil标准固件库官网下载方法 Keil中DFP.pack下载方法 打开keil官网 Keil 官网 www.keil.com 点击产品 点击“Products” 点击 “Arm Cortex-M” 下拉找到CMSIS-Packs 点击CMSIS-Pack index 搜索对应的MCU&#xff0c;我这里是STM32F4 注意搜索对应系列就好 点击下载...

数据库原理实验实验四 统计查询和组合查询

实验目的和要求加深对统计查询的理解,熟练使用聚簇函数。 实验环境SQL SERVER 2008 SQL Server Management Studio 20 实验内容与过程题目一: 学生(学号,年龄,性别,系名) 课程(课号,课名,学分,学时) 选课(学号,课号,成绩) 根据上面基本表的信息完成下列查…...