你们好,我是一名嵌入式工程师。我正在持续更新《嵌入式工程师的C语言实战》系列教程,目前已更新7篇。如果你也在学习嵌入式,欢迎点个关注,我们一起从零到一。
文章目录
- 前言
- 一、strcpy —— 字符串复制
- 头文件
- 函数原型
- 功能
- 参数
- 返回值
- 重要警告
- 示例
- 安全替代方案
- 二、strlen —— 获取字符串长度
- 头文件
- 函数原型
- 功能
- 参数
- 返回值
- 示例
- 重要提醒
- 三、malloc —— 动态内存分配
- 头文件
- 函数原型
- 功能
- 参数
- 返回值
- 重要注意事项
- 示例
- 常见应用场景
- 四、free —— 释放动态内存
- 头文件
- 函数原型
- 功能
- 参数
- 返回值
- 重要注意事项
- 示例
- 五、rand —— 生成随机数
- 头文件
- 函数原型
- 功能
- 参数
- 返回值
- 重要注意事项
- 示例
- 生成特定范围随机数的公式
- 六、memset —— 设置内存块的值
- 头文件
- 函数原型
- 功能
- 参数
- 返回值
- 常见用途
- 示例
- 警告
- 七、memcpy —— 内存复制
- 头文件
- 函数原型
- 功能
- 参数
- 返回值
- 重要注意事项
- 示例
- memcpy vs strcpy
- 综合示例:实战应用
- 总结对比表
- 最佳实践总结
- 如果这篇文章对你有帮助,欢迎点赞、收藏、关注,主播是一个马上粉丝量超1个的嵌入式内容主播。谢谢!
前言
strcpy、strlen、malloc、free、rand、memset、memcpy这7个函数是C语言中字符串操作、内存管理和工具函数的核心成员。
一、strcpy —— 字符串复制
头文件
#include<string.h>函数原型
char*strcpy(char*dest,constchar*src);功能
将源字符串src(包括结尾的\0)复制到目标字符串dest中。
参数
dest:目标字符串的指针(必须有足够的空间容纳src)。src:源字符串的指针(必须以\0结尾)。
返回值
返回dest的指针(便于链式调用)。
重要警告
strcpy不检查目标缓冲区大小,如果src比dest的空间大,会导致缓冲区溢出(严重安全漏洞)。
示例
#include<stdio.h>#include<string.h>intmain(){charsrc[]="Hello, World!";chardest[20];// 必须足够大strcpy(dest,src);printf("复制后: %s\n",dest);// 输出: Hello, World!// 链式调用charbuffer[50];strcpy(buffer,strcpy(dest,"C语言"));printf("buffer: %s\n",buffer);// 输出: C语言printf("dest: %s\n",dest);// 输出: C语言return0;}安全替代方案
建议使用strncpy(指定最大复制长度):
chardest[10];strncpy(dest,"Hello, World!",sizeof(dest)-1);dest[sizeof(dest)-1]='\0';// 确保结尾有 \0二、strlen —— 获取字符串长度
头文件
#include<string.h>函数原型
size_tstrlen(constchar*s);功能
计算字符串的长度(不包含结尾的\0),即从起始地址到第一个\0之前的字符个数。
参数
s:以\0结尾的字符串。
返回值
返回字符串的长度,类型为size_t(无符号整数)。
示例
#include<stdio.h>#include<string.h>intmain(){charstr1[]="Hello";charstr2[]="中文测试";// 中文字符在 UTF-8 下占多个字节printf("'%s' 的长度: %zu\n",str1,strlen(str1));// 输出 5printf("'%s' 的长度: %zu\n",str2,strlen(str2));// 输出 12 (UTF-8)// sizeof vs strlenchararr[20]="Hello";printf("sizeof(arr) = %zu\n",sizeof(arr));// 20 (整个数组大小)printf("strlen(arr) = %zu\n",strlen(arr));// 5 (字符串长度)// 手动实现 strlen 的思路constchar*p="Test";size_tlen=0;while(*p!='\0'){len++;p++;}printf("手动计算: %zu\n",len);// 输出 4return0;}重要提醒
strlen返回的是字符数,不是字节数(对多字节字符集需注意)。- 传入的字符串必须保证以
\0结尾,否则结果不可预知。
三、malloc —— 动态内存分配
头文件
#include<stdlib.h>函数原型
void*malloc(size_tsize);功能
在**堆(heap)**上分配size个字节的连续内存空间,并返回指向该内存块的指针。
参数
size:要分配的字节数。
返回值
- 成功时返回指向已分配内存的指针(
void*,可强制转换为任意类型)。 - 失败时(内存不足)返回
NULL。
重要注意事项
- 分配的内存内容未初始化(包含垃圾值)。
- 必须检查返回值是否为
NULL。 - 使用完后必须调用
free释放,否则造成内存泄漏。 - 分配的大小要用
sizeof计算,避免硬编码。
示例
#include<stdio.h>#include<stdlib.h>intmain(){int*arr;intn=5;// 分配 n 个 int 类型的内存arr=(int*)malloc(n*sizeof(int));if(arr==NULL){printf("内存分配失败!\n");return1;}// 使用内存(注意:此时值未初始化)for(inti=0;i<n;i++){arr[i]=i*10;printf("%d ",arr[i]);}printf("\n");// 释放内存free(arr);arr=NULL;// 避免野指针return0;}常见应用场景
- 在运行时才知道需要多大的数组。
- 创建链表、树等动态数据结构。
- 处理可变长度的数据(如从文件读取内容)。
四、free —— 释放动态内存
头文件
#include<stdlib.h>函数原型
voidfree(void*ptr);功能
释放之前由malloc、calloc或realloc分配的内存,将其归还给系统。
参数
ptr:指向要释放的内存块的指针(必须是之前动态分配的)。
返回值
无。
重要注意事项
- 只能释放动态分配的内存,不能释放栈上的变量。
- 不能重复释放(double free)同一块内存,会导致未定义行为。
- 释放后最好将指针置为
NULL,避免野指针。 - 释放
NULL是安全的(不会产生错误)。
示例
#include<stdio.h>#include<stdlib.h>intmain(){char*buffer;// 分配 100 字节buffer=(char*)malloc(100*sizeof(char));if(buffer==NULL){printf("分配失败\n");return1;}// 使用内存sprintf(buffer,"动态分配的字符串");printf("%s\n",buffer);// 释放内存free(buffer);buffer=NULL;// 安全实践// free(NULL) 是安全的free(NULL);// 什么也不做return0;}五、rand —— 生成随机数
头文件
#include<stdlib.h>函数原型
intrand(void);功能
生成一个0到RAND_MAX之间的伪随机整数。RAND_MAX通常定义为32767。
参数
无。
返回值
返回一个伪随机整数。
重要注意事项
- 默认种子是 1,每次运行程序生成的随机数序列相同。
- 需要用
srand设置不同的种子(通常用当前时间)。 rand的随机质量有限,对安全性要求高的场景需用专用库。
示例
#include<stdio.h>#include<stdlib.h>#include<time.h>// 用于 time()intmain(){// 设置随机种子(使用当前时间)srand((unsignedint)time(NULL));// 生成 10 个随机数printf("10 个随机数:\n");for(inti=0;i<10;i++){printf("%d ",rand());}printf("\n");// 生成 1~100 之间的随机数printf("\n1~100 的随机数:\n");for(inti=0;i<10;i++){intnum=rand()%100+1;printf("%d ",num);}printf("\n");// 生成 0.0~1.0 之间的随机浮点数printf("\n0.0~1.0 随机浮点数:\n");for(inti=0;i<5;i++){doublef=(double)rand()/RAND_MAX;printf("%.3f ",f);}printf("\n");return0;}生成特定范围随机数的公式
| 范围 | 公式 |
|---|---|
0 ~ N-1 | rand() % N |
1 ~ N | rand() % N + 1 |
min ~ max | rand() % (max - min + 1) + min |
0.0 ~ 1.0 | (double)rand() / RAND_MAX |
六、memset —— 设置内存块的值
头文件
#include<string.h>函数原型
void*memset(void*s,intc,size_tn);功能
将内存块s的前n个字节全部设置为c(按字节填充)。
参数
s:指向要填充的内存块的指针。c:要填充的值(会被转换为unsigned char)。n:要填充的字节数。
返回值
返回s的指针。
常见用途
- 初始化数组或结构体为 0。
- 清空敏感数据(如密码)。
- 重置缓冲区。
示例
#include<stdio.h>#include<string.h>intmain(){// 1. 初始化数组为 0intarr[10];memset(arr,0,sizeof(arr));printf("arr[0] = %d\n",arr[0]);// 输出 0// 2. 初始化数组为特定值(注意:按字节填充)charbuffer[20];memset(buffer,'A',10);// 前10个字节设为 'A'buffer[10]='\0';printf("buffer: %s\n",buffer);// 输出: AAAAAAAAAA// 3. 初始化结构体typedefstruct{intid;charname[50];floatscore;}Student;Student s;memset(&s,0,sizeof(s));// 所有成员清零printf("s.id = %d, s.score = %.2f\n",s.id,s.score);// 4. 清空字符串charstr[100]="Hello, World!";printf("清空前: %s\n",str);memset(str,0,sizeof(str));printf("清空后: %s (长度: %zu)\n",str,strlen(str));// 长度 0return0;}警告
memset按字节填充,对于int数组,memset(arr, 1, sizeof(arr))不会将每个元素设为 1,而是每个字节设为 1,结果每个int的值是0x01010101(即 16843009)。只有填充0或-1时才适用于整数数组。
七、memcpy —— 内存复制
头文件
#include<string.h>函数原型
void*memcpy(void*dest,constvoid*src,size_tn);功能
将内存块src的前n个字节复制到dest中。
参数
dest:目标内存指针。src:源内存指针。n:要复制的字节数。
返回值
返回dest的指针。
重要注意事项
- 不检查
\0,直接按字节复制n个字节。 - 源和目标不能重叠(重叠请用
memmove)。 - 目标内存必须有足够的空间。
示例
#include<stdio.h>#include<string.h>intmain(){// 1. 复制数组intsrc_arr[]={1,2,3,4,5};intdest_arr[5];memcpy(dest_arr,src_arr,sizeof(src_arr));printf("复制后的数组: ");for(inti=0;i<5;i++){printf("%d ",dest_arr[i]);}printf("\n");// 2. 复制字符串(包括 \0)charsrc_str[]="Hello, World!";chardest_str[50];memcpy(dest_str,src_str,strlen(src_str)+1);// +1 复制 \0printf("复制后的字符串: %s\n",dest_str);// 3. 复制结构体typedefstruct{intx;inty;}Point;Point p1={10,20};Point p2;memcpy(&p2,&p1,sizeof(Point));printf("p2: (%d, %d)\n",p2.x,p2.y);// 4. strcpy 与 memcpy 的区别chartext[]="ABCDEFG";charbuf1[10],buf2[10];strcpy(buf1,text);// 复制直到遇到 \0memcpy(buf2,text,3);// 只复制前3个字节(不自动加 \0)buf2[3]='\0';// 手动添加结束符printf("strcpy: %s\n",buf1);printf("memcpy: %s\n",buf2);// 输出: ABCreturn0;}memcpy vs strcpy
| 对比项 | memcpy | strcpy |
|---|---|---|
| 复制方式 | 按字节数复制 | 复制到\0为止 |
是否检查\0 | 否 | 是 |
| 能否复制二进制数据 | ✅ 可以 | ❌ 不行(遇\0停止) |
| 性能 | 通常更快 | 略慢(需扫描\0) |
| 安全性 | 需手动控制长度 | 不检查目标大小(危险) |
综合示例:实战应用
#include<stdio.h>#include<stdlib.h>#include<string.h>#include<time.h>intmain(){// 1. 动态分配内存intn=10;int*numbers=(int*)malloc(n*sizeof(int));if(numbers==NULL){printf("内存分配失败\n");return1;}// 2. 初始化内存为 0memset(numbers,0,n*sizeof(int));// 3. 生成随机数填充srand((unsignedint)time(NULL));for(inti=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);return1;}memcpy(copy,numbers,n*sizeof(int));// 5. 输出结果printf("原始数组: ");for(inti=0;i<n;i++){printf("%d ",numbers[i]);}printf("\n");printf("复制数组: ");for(inti=0;i<n;i++){printf("%d ",copy[i]);}printf("\n");// 6. 统计数组长度(类比 strlen)charmsg[]="Dynamic memory management";printf("消息: %s\n",msg);printf("消息长度: %zu\n",strlen(msg));// 7. 释放内存free(numbers);free(copy);numbers=NULL;copy=NULL;return0;}总结对比表
| 函数 | 头文件 | 核心功能 | 是否检查\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> | 按字节复制内存 | ❌ 否 | ✅ 需手动指定 | 目标指针 |
最佳实践总结
- 字符串操作:优先使用
strncpy、strnlen等带n的安全版本。 - 动态内存:
malloc后必须检查NULL,使用后必须free,释放后置NULL。 - 随机数:程序开始时调用一次
srand(time(NULL)),避免重复种子。 - 内存操作:
memset用于清零,memcpy用于复制二进制数据,注意不要重叠。 - 内存泄漏检测:Linux 下可用
valgrind工具检测。