【C语言】strncpy,strncat,strncmp,memcpy,memmove,memcmp,memset的模拟实现

这里写目录标题


首先注意,接下来的函数基本都需要目标内存块足够大。

如果不够大很有可能会产生下述情况:

  1. 比较结果不准确:如果目标函数不够大,比较的字符数量较少,可能无法完整比较两个字符串的内容。这样就无法得到正确的比较结果,可能会导致程序逻辑错误。
  2. 隐患安全问题:如果目标函数不够大,比较的字符数量小于其中一个字符串的长度,可能会导致缓冲区溢出的安全问题。因为在比较过程中,如果需要比较的字符串长度超过了目标函数的大小,就会访问到未分配的内存空间,可能导致程序崩溃或者被攻击者利用。
  3. 性能影响:如果目标函数不够大,比较的字符数量较少,可能需要多次调用strncmp函数才能完成完整的字符串比较。这样会增加函数调用的开销和执行时间。

strncpy

概念

上面这张图是MSDN上对strncpy所需要的形参类型以及返回类型的介绍,我们可以知道,这个函数的头文件为<string.h>,需要输入一个目标地址,一个源头地址,以及拷贝多少个字符,最后返回的是一个字符指针

(关于指针的一些详细介绍,本人主页有,不嫌弃可以看看下)

然后这张是这个函数的一个实现案例,它先把"Dogs"的4个字符全部拷贝到string里,再把"mean"的四个字符拷贝到string+9的位置,随后实现了string里字符串的变化

这里是cpluscplus对strncpy的注意声明,大概意思如下

  • 拷贝num个字符从源字符串到目标空间。
  • 如果在复制num个字符之前找到源C字符串的结尾(由null字符表示),则目的地将用零填充,直到向其写入总数为num个字符。
  • 如果source长度大于num,则不会在destination的末尾隐式添加空字符。因此,在这种情况下, destination不应被视为以空结尾的C字符串(这样读取会溢出)。
  • destination和source不应重叠(重叠时,请参阅memmove以获得更安全的替代方法)。

实现

这个函数的实现较简单,只需要找对destination和source的位置,然后用佛如循环一个一个改变,唯一要注意的是,因为再循环后往往destination的地址惠王后偏移num个长度,所以需要提前备份一个destination的地址

代码如下

c 复制代码
char* my_strncpy(char* dest, const char* src, int numsSize)
{
	char* cp = dest;
	while (numsSize--)
	{
		*dest++ = *src++;
	}
	return cp;
}

strncat

概念

上面这张图是MSDN上对strncat所需要的形参类型以及返回类型的介绍,我们可以知道,这个函数的头文件为<string.h>,需要输入一个目标地址,一个源头地址,以及拷贝多少个字符,最后返回的是一个字符指针,基本和strncpy一样

  • 将source指向字符串的前num个字符追加到destination指向的字符串末尾,再追加⼀个 \0 字
  • 如果source指向的字符串的长度小于num的时候,只会将字符串中到\0 的内容追加到destination指向的字符串末尾

这是MSDN上的案例,不过多赘述。
实现

这里除了跟strncpy一样需要提前备份一次,还需要通过循环*dest++达到destination字符串最后一位的效果,并且如果source指向的字符串的长度小于num的时候,只会将字符串中到\0 的内容追加到destination指向的字符串末尾,需要在返回前加上一个'\0'

代码如下

c 复制代码
char* my_strncat(char* dest, char* src, size_t num)
{
	char* ret = dest;
	assert(dest && src);
	while (*dest++)//循环直到dest指向'\0'
		;
	dest--;//消除遇到'\0'时的++
	while (num--)
	{
		if ((*dest++ = *src++) == 0)//当src结束但num还没结束时,就直接返回
			return ret;
	}
	*dest = '\0';
	return ret;
}

strncmp

概念

上面这张图是MSDN上对strncmp所需要的形参类型以及返回类型的介绍,我们可以知道,这个函数的头文件为<string.h>,需要输入两个比较字符串的地址,以及比较多少多少个字符,最后返回的是一个整形

然后是cplusplus上的介绍,大概意思就是,将C字符串stri的最多num个字符与C字符串str2的字符进行比较。这个函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续执行后面的对,直到字符不相同,直到达到终止的空字符,或者直到两个字符串中的num字符匹配,以先发生的为准。

⽐较str1和str2的前num个字符,如果相等就继续往后⽐较,最多⽐较num个字⺟,如果提前发现不⼀

样,就提前结束,⼤的字符所在的字符串⼤于另外⼀个。如果num个字符都相等,就是相等返回0.

这是MSDN上的例子,也是比较简单的,有兴趣看看,就不多说了。

实现

这里我们子需要一直循环,知道两个字符串布希昂同,或者其中一个到最后了,最后返回两者的相减,就可以根据返回值与0关系,知晓判断结果

代码如下

c 复制代码
int my_strncmp(const char* str1, const char* str2, size_t nums)
{
	assert(str1 && str2);
	while (nums-- && *str1 == *str2)
	{
		str1++;
		str2++;
	}
	return *str1 - *str2;
}

注意

上面的str开头的几个函数,只能作用于字符类型的对象,所以如果遇到非字符类型的对象就需要用到mem开头的函数

memcpy

概念

我们可以发现,这里memcoy的返回值时无类型指针,目标地址和源头地址也是无类型指针,也需要一个决定拷贝多少个字节的count,头文件可以是string.h也可以是memory.h

这里我们可以注意下,之所以用void* 类型的目的就是能够让所有类型的对象都适用,而count的单位的不是对象类型的单位而是字节

翻译如下

  • 将num字节的值从源指向的位置直接复制到目标指向的内存块。
  • 源指针和目标指针所指向的对象的底层类型与此函数无关; 结果是数据的二进制副本。
  • 该函数不检查源中是否有任何终止null字符------它总是精确地复制num个字节。
  • 为了避免溢出,目标参数和源参数所指向的数组的大小应该至少为num字节,并且不应该重叠(对于重叠的内存块,emmove是一种更安全的方法)。

案例如上,摆烂了...

代码实现基本与strcpy类似,唯一需要注意的时,这是不论是形参还是返回的都是无类型指针,用来适应所用的类型,但是在函数累不,为防止各种情况的出现,我们选用char* 类型,以字节为最小单位,保证只要不是出现比字节还小的单位就能够做到交换。

值得一提的是,8,9行为推进循环而要往下看一个字节的地址,不能够直接加上1,而需要先转换为char* 类型,确保每次加一的距离都是一个字节,而重复char* 的原因是这种强制类型转换只是临时的,所以每次需要使用时,都必须强制类型转换。

代码如下

c 复制代码
void* my_memcpy(void* dest, void* src, size_t n)
{
	char* ret = dest;
	assert(dest && src);
	while (n--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
	return (ret);
}

memmove

概念

这里memmove的返回值时无类型指针,目标地址和源头地址也是无类型指针,也需要一个决定拷贝多少个字节的count,头文件是string.h

这个函数和memcpy的差别就在于,memcpy只能用于不重叠的源内存块和⽬标内存块,但是memmove可以。

翻译如下

  • 将num字节的值从源指向的位置复制到目标指向的内存块。复制就像使用了中间缓冲区一样,允许目标和源重叠。
  • 源指针和目标指针所指向的对象的底层类型与此函数无关;结果是数据的二进制副本。
  • 该函数不检查源中是否有任何终止null字符--它总是精确地复制num个字节。
  • 为了避免溢出,目的参数和源参数所指向的数组的大小至少为num字节

案例如上

实现

这里我们需要注意的是,关注这个函数的源内存块和⽬标内存块的相对位置,我们可以简单看为三种情况

dest与src重叠,且在src后面,在src前面,以及与src毫不相关。大致图示如下

这样以来的话,我们之选哟分三种情况

  • dest与src重叠,且在src后面:从后往前赋值
  • dest与src重叠,且在src前面:从前往后赋值
  • dest与src不重叠:随意

为了使代码尽可能地简单,我们就分为

dest在src前面和dest在src后面这两种即可

代码如下

c 复制代码
void* my_memmove(void* dest, void* src, size_t n)
{
	char* ret = dest;
	assert(dest && src);
	if (src < dest)
	{
		dest = (char*)dest + n;
		src = (char*)src + n;
		while (n--)
		{
			*(char*)dest = *(char*)src;
			dest = (char*)dest - 1;
			src = (char*)src - 1;
		}
	}
	else
	{
		while (n--)
		{
			*(char*)dest = *(char*)src;
			dest = (char*)dest + 1;
			src = (char*)src + 1;
		}
	}
	return ret;
}

memcmp

概念

这里memcmp的返回值时无类型指针,两个比较量也是无类型指针,以及一个决定比较多少个字节的count,头文件可以是string.h也可以是memory.h

  • 将ptri所指向的内存块的前num字节与ptr2所指向的前num字节进行比较,返回如果全部匹配则为零,如果不匹配则为不同于零的值。
  • 注意,与strcmp不同,该函数在找到空字符后不会停止比较。

返回值如下

案例如上

这里的代码实现基本就是char* 版的strcmp不过多赘述了,唯一不同的就是他遇到空字符不停止,也是一直比较到出现不同处的位置,最多比较num次


memset

概念

这里memset返回的是无类型指针,形参是一个无类型目标内存块,需要设置的值。该值作为int类型传递,但函数使用unsignedchar填充内存块,以及记录需要改变多少字节的count

memset的目的是⽤来设置内存的,将内存中的值以字节为单位设置成想要的内容。

案例如上

实现

这个代码是通过指向的空间前size个字节,初始化ASCII为c的值来实现的

同样也需要备份一次dest

c 复制代码
void* my_memset(void* str, int c, size_t count)
{
	assert(str != NULL);
	char* cp = str;
	while (count--)
	{
		*(char*)str = (char)c;
		str = (char*)str + 1;
	}
	return cp;
}

相关推荐
lzb_kkk1 分钟前
【JavaEE】JUC的常见类
java·开发语言·java-ee
SEEONTIME1 分钟前
python-24-一篇文章彻底掌握Python HTTP库Requests
开发语言·python·http·http库requests
Zfox_1 分钟前
【Linux】进程信号全攻略(二)
linux·运维·c语言·c++
起名字真南19 分钟前
【OJ题解】C++实现字符串大数相乘:无BigInteger库的字符串乘积解决方案
开发语言·c++·leetcode
少年负剑去20 分钟前
第十五届蓝桥杯C/C++B组题解——数字接龙
c语言·c++·蓝桥杯
tyler_download31 分钟前
golang 实现比特币内核:实现基于椭圆曲线的数字签名和验证
开发语言·数据库·golang
小小小~31 分钟前
qt5将程序打包并使用
开发语言·qt
hlsd#32 分钟前
go mod 依赖管理
开发语言·后端·golang
小春学渗透33 分钟前
Day107:代码审计-PHP模型开发篇&MVC层&RCE执行&文件对比法&1day分析&0day验证
开发语言·安全·web安全·php·mvc
杜杜的man36 分钟前
【go从零单排】迭代器(Iterators)
开发语言·算法·golang