你们好,我是一名嵌入式工程师。我正在持续更新《嵌入式工程师的C语言实战》系列教程,目前已更新7篇。如果你也在学习嵌入式,欢迎点个关注,我们一起从零到一。


前言

strcpystrlenmallocfreerandmemsetmemcpy 这7个函数是C语言中字符串操作内存管理工具函数的核心成员。


一、strcpy —— 字符串复制

头文件

#include <string.h>

函数原型

char *strcpy(char *dest, const char *src);

功能

将源字符串 src(包括结尾的 \0复制到目标字符串 dest 中。

参数

  • dest:目标字符串的指针(必须有足够的空间容纳 src)。
  • src:源字符串的指针(必须以 \0 结尾)。

返回值

返回 dest 的指针(便于链式调用)。

重要警告

strcpy 不检查目标缓冲区大小,如果 srcdest 的空间大,会导致缓冲区溢出(严重安全漏洞)。

示例

#include <stdio.h>
#include <string.h>

int main() {
    char src[] = "Hello, World!";
    char dest[20];  // 必须足够大
    
    strcpy(dest, src);
    printf("复制后: %s\n", dest);  // 输出: Hello, World!
    
    // 链式调用
    char buffer[50];
    strcpy(buffer, strcpy(dest, "C语言"));
    printf("buffer: %s\n", buffer);  // 输出: C语言
    printf("dest: %s\n", dest);      // 输出: C语言
    
    return 0;
}

安全替代方案

建议使用 strncpy(指定最大复制长度):

char dest[10];
strncpy(dest, "Hello, World!", sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';  // 确保结尾有 \0

二、strlen —— 获取字符串长度

头文件

#include <string.h>

函数原型

size_t strlen(const char *s);

功能

计算字符串的长度(不包含结尾的 \0),即从起始地址到第一个 \0 之前的字符个数。

参数

  • s:以 \0 结尾的字符串。

返回值

返回字符串的长度,类型为 size_t(无符号整数)。

示例

#include <stdio.h>
#include <string.h>

int main() {
    char str1[] = "Hello";
    char str2[] = "中文测试";  // 中文字符在 UTF-8 下占多个字节
    
    printf("'%s' 的长度: %zu\n", str1, strlen(str1));  // 输出 5
    printf("'%s' 的长度: %zu\n", str2, strlen(str2));  // 输出 12 (UTF-8)
    
    // sizeof vs strlen
    char arr[20] = "Hello";
    printf("sizeof(arr) = %zu\n", sizeof(arr));    // 20 (整个数组大小)
    printf("strlen(arr) = %zu\n", strlen(arr));    // 5  (字符串长度)
    
    // 手动实现 strlen 的思路
    const char *p = "Test";
    size_t len = 0;
    while (*p != '\0') {
        len++;
        p++;
    }
    printf("手动计算: %zu\n", len);  // 输出 4
    
    return 0;
}

重要提醒

  • strlen 返回的是字符数,不是字节数(对多字节字符集需注意)。
  • 传入的字符串必须保证以 \0 结尾,否则结果不可预知。

三、malloc —— 动态内存分配

头文件

#include <stdlib.h>

函数原型

void *malloc(size_t size);

功能

在**堆(heap)**上分配 size 个字节的连续内存空间,并返回指向该内存块的指针。

参数

  • size:要分配的字节数。

返回值

  • 成功时返回指向已分配内存的指针(void*,可强制转换为任意类型)。
  • 失败时(内存不足)返回 NULL

重要注意事项

  1. 分配的内存内容未初始化(包含垃圾值)。
  2. 必须检查返回值是否为 NULL
  3. 使用完后必须调用 free 释放,否则造成内存泄漏
  4. 分配的大小要用 sizeof 计算,避免硬编码。

示例

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *arr;
    int n = 5;
    
    // 分配 n 个 int 类型的内存
    arr = (int*)malloc(n * sizeof(int));
    if (arr == NULL) {
        printf("内存分配失败!\n");
        return 1;
    }
    
    // 使用内存(注意:此时值未初始化)
    for (int i = 0; i < n; i++) {
        arr[i] = i * 10;
        printf("%d ", arr[i]);
    }
    printf("\n");
    
    // 释放内存
    free(arr);
    arr = NULL;  // 避免野指针
    
    return 0;
}

常见应用场景

  • 在运行时才知道需要多大的数组。
  • 创建链表、树等动态数据结构。
  • 处理可变长度的数据(如从文件读取内容)。

四、free —— 释放动态内存

头文件

#include <stdlib.h>

函数原型

void free(void *ptr);

功能

释放之前由 malloccallocrealloc 分配的内存,将其归还给系统。

参数

  • ptr:指向要释放的内存块的指针(必须是之前动态分配的)。

返回值

无。

重要注意事项

  1. 只能释放动态分配的内存,不能释放栈上的变量。
  2. 不能重复释放(double free)同一块内存,会导致未定义行为。
  3. 释放后最好将指针置为 NULL,避免野指针。
  4. 释放 NULL 是安全的(不会产生错误)。

示例

#include <stdio.h>
#include <stdlib.h>

int main() {
    char *buffer;
    
    // 分配 100 字节
    buffer = (char*)malloc(100 * sizeof(char));
    if (buffer == NULL) {
        printf("分配失败\n");
        return 1;
    }
    
    // 使用内存
    sprintf(buffer, "动态分配的字符串");
    printf("%s\n", buffer);
    
    // 释放内存
    free(buffer);
    buffer = NULL;  // 安全实践
    
    // free(NULL) 是安全的
    free(NULL);  // 什么也不做
    
    return 0;
}

五、rand —— 生成随机数

头文件

#include <stdlib.h>

函数原型

int rand(void);

功能

生成一个 0RAND_MAX 之间的伪随机整数RAND_MAX 通常定义为 32767

参数

无。

返回值

返回一个伪随机整数。

重要注意事项

  1. 默认种子是 1,每次运行程序生成的随机数序列相同。
  2. 需要用 srand 设置不同的种子(通常用当前时间)。
  3. rand 的随机质量有限,对安全性要求高的场景需用专用库。

示例

#include <stdio.h>
#include <stdlib.h>
#include <time.h>  // 用于 time()

int main() {
    // 设置随机种子(使用当前时间)
    srand((unsigned int)time(NULL));
    
    // 生成 10 个随机数
    printf("10 个随机数:\n");
    for (int i = 0; i < 10; i++) {
        printf("%d ", rand());
    }
    printf("\n");
    
    // 生成 1~100 之间的随机数
    printf("\n1~100 的随机数:\n");
    for (int i = 0; i < 10; i++) {
        int num = rand() % 100 + 1;
        printf("%d ", num);
    }
    printf("\n");
    
    // 生成 0.0~1.0 之间的随机浮点数
    printf("\n0.0~1.0 随机浮点数:\n");
    for (int i = 0; i < 5; i++) {
        double f = (double)rand() / RAND_MAX;
        printf("%.3f ", f);
    }
    printf("\n");
    
    return 0;
}

生成特定范围随机数的公式

范围公式
0 ~ N-1rand() % N
1 ~ Nrand() % N + 1
min ~ maxrand() % (max - min + 1) + min
0.0 ~ 1.0(double)rand() / RAND_MAX

六、memset —— 设置内存块的值

头文件

#include <string.h>

函数原型

void *memset(void *s, int c, size_t n);

功能

将内存块 s 的前 n 个字节全部设置为 c(按字节填充)。

参数

  • s:指向要填充的内存块的指针。
  • c:要填充的值(会被转换为 unsigned char)。
  • n:要填充的字节数。

返回值

返回 s 的指针。

常见用途

  1. 初始化数组或结构体为 0
  2. 清空敏感数据(如密码)。
  3. 重置缓冲区

示例

#include <stdio.h>
#include <string.h>

int main() {
    // 1. 初始化数组为 0
    int arr[10];
    memset(arr, 0, sizeof(arr));
    printf("arr[0] = %d\n", arr[0]);  // 输出 0
    
    // 2. 初始化数组为特定值(注意:按字节填充)
    char buffer[20];
    memset(buffer, 'A', 10);  // 前10个字节设为 'A'
    buffer[10] = '\0';
    printf("buffer: %s\n", buffer);  // 输出: AAAAAAAAAA
    
    // 3. 初始化结构体
    typedef struct {
        int id;
        char name[50];
        float score;
    } Student;
    
    Student s;
    memset(&s, 0, sizeof(s));  // 所有成员清零
    printf("s.id = %d, s.score = %.2f\n", s.id, s.score);
    
    // 4. 清空字符串
    char str[100] = "Hello, World!";
    printf("清空前: %s\n", str);
    memset(str, 0, sizeof(str));
    printf("清空后: %s (长度: %zu)\n", str, strlen(str));  // 长度 0
    
    return 0;
}

警告

memset字节填充,对于 int 数组,memset(arr, 1, sizeof(arr)) 不会将每个元素设为 1,而是每个字节设为 1,结果每个 int 的值是 0x01010101(即 16843009)。只有填充 0-1 时才适用于整数数组。


七、memcpy —— 内存复制

头文件

#include <string.h>

函数原型

void *memcpy(void *dest, const void *src, size_t n);

功能

将内存块 src 的前 n 个字节复制dest 中。

参数

  • dest:目标内存指针。
  • src:源内存指针。
  • n:要复制的字节数。

返回值

返回 dest 的指针。

重要注意事项

  1. 不检查 \0,直接按字节复制 n 个字节。
  2. 源和目标不能重叠(重叠请用 memmove)。
  3. 目标内存必须有足够的空间。

示例

#include <stdio.h>
#include <string.h>

int main() {
    // 1. 复制数组
    int src_arr[] = {1, 2, 3, 4, 5};
    int dest_arr[5];
    memcpy(dest_arr, src_arr, sizeof(src_arr));
    
    printf("复制后的数组: ");
    for (int i = 0; i < 5; i++) {
        printf("%d ", dest_arr[i]);
    }
    printf("\n");
    
    // 2. 复制字符串(包括 \0)
    char src_str[] = "Hello, World!";
    char dest_str[50];
    memcpy(dest_str, src_str, strlen(src_str) + 1);  // +1 复制 \0
    printf("复制后的字符串: %s\n", dest_str);
    
    // 3. 复制结构体
    typedef struct {
        int x;
        int y;
    } Point;
    
    Point p1 = {10, 20};
    Point p2;
    memcpy(&p2, &p1, sizeof(Point));
    printf("p2: (%d, %d)\n", p2.x, p2.y);
    
    // 4. strcpy 与 memcpy 的区别
    char text[] = "ABCDEFG";
    char buf1[10], buf2[10];
    
    strcpy(buf1, text);          // 复制直到遇到 \0
    memcpy(buf2, text, 3);       // 只复制前3个字节(不自动加 \0)
    buf2[3] = '\0';              // 手动添加结束符
    
    printf("strcpy: %s\n", buf1);
    printf("memcpy: %s\n", buf2);  // 输出: ABC
    
    return 0;
}

memcpy vs strcpy

对比项memcpystrcpy
复制方式按字节数复制复制到 \0 为止
是否检查 \0
能否复制二进制数据✅ 可以❌ 不行(遇 \0 停止)
性能通常更快略慢(需扫描 \0
安全性需手动控制长度不检查目标大小(危险)

综合示例:实战应用

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int main() {
    // 1. 动态分配内存
    int n = 10;
    int *numbers = (int*)malloc(n * sizeof(int));
    if (numbers == NULL) {
        printf("内存分配失败\n");
        return 1;
    }
    
    // 2. 初始化内存为 0
    memset(numbers, 0, n * sizeof(int));
    
    // 3. 生成随机数填充
    srand((unsigned int)time(NULL));
    for (int i = 0; i < n; i++) {
        numbers[i] = rand() % 100 + 1;  // 1~100
    }
    
    // 4. 复制数组
    int *copy = (int*)malloc(n * sizeof(int));
    if (copy == NULL) {
        printf("内存分配失败\n");
        free(numbers);
        return 1;
    }
    memcpy(copy, numbers, n * sizeof(int));
    
    // 5. 输出结果
    printf("原始数组: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", numbers[i]);
    }
    printf("\n");
    
    printf("复制数组: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", copy[i]);
    }
    printf("\n");
    
    // 6. 统计数组长度(类比 strlen)
    char msg[] = "Dynamic memory management";
    printf("消息: %s\n", msg);
    printf("消息长度: %zu\n", strlen(msg));
    
    // 7. 释放内存
    free(numbers);
    free(copy);
    numbers = NULL;
    copy = NULL;
    
    return 0;
}

总结对比表

函数头文件核心功能是否检查 \0是否检查大小返回值
strcpy<string.h>复制字符串✅ 是❌ 否目标指针
strlen<string.h>计算字符串长度✅ 是-size_t
malloc<stdlib.h>分配内存--void*
free<stdlib.h>释放内存--
rand<stdlib.h>生成随机数--int
memset<string.h>按字节填充内存❌ 否✅ 需手动指定目标指针
memcpy<string.h>按字节复制内存❌ 否✅ 需手动指定目标指针

最佳实践总结

  1. 字符串操作:优先使用 strncpystrnlen 等带 n 的安全版本。
  2. 动态内存malloc 后必须检查 NULL,使用后必须 free,释放后置 NULL
  3. 随机数:程序开始时调用一次 srand(time(NULL)),避免重复种子。
  4. 内存操作memset 用于清零,memcpy 用于复制二进制数据,注意不要重叠。
  5. 内存泄漏检测:Linux 下可用 valgrind 工具检测。

如果这篇文章对你有帮助,欢迎点赞、收藏、关注,主播是一个马上粉丝量超1个的嵌入式内容主播。谢谢!

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐