C语言:内存函数

1. memcpy函数的使用和模拟实现

函数原型如下:

cpp 复制代码
 void * memcpy ( void * destination, const void * source, size_t num );
cpp 复制代码
Copies count characters from the object pointed to by src to the object pointed to by dest.
 Both objects are interpreted as arrays of unsigned char.

函数memcpy从原地址source的位置开始向后复制num个字节的数据到目标地址destination指向的内存位置。两个地址所指向的对向都会被当作unsigned char类型的数组处理。因此,memcpy在处理数据时是逐个字节进行处理。

函数只会处理数据而不会在意数据的类型,因此,函数在碰到'\0'时,并不会停止,而是会一直复制直到复制完num个字节的数据为止。

当源地址与目标地址有任何的重叠时,结果都是未定义的。

cpp 复制代码
#include <string.h>
#include <stdio.h>
int main ()
{
    char arr1[10] = {1,2,3,4,5,6,7,8,9,10};
    char arr2[10] = {0};
    memcpy(arr2,arr1,10);
    int i;
    for(i = 0;i < 10;i++)
        printf("%d ",arr2[i]);
    return 0;
}
cpp 复制代码
1 2 3 4 5 6 7 8 9 10 

我们可以发现,memcpy函数成功的将数组arr2中的内容全部复制到了数组arr1中(char类型数据大小为1字节)。

在了解了如何使用memcpy函数后,我们现在来尝试模拟实现memcpy函数:

由于memcpy函数的操作对象为内存,与数据类型无关。因此,我们需要将函数接收两个指针参数dest和src分别设置为void* 和const void* 类型,保证函数能够接收任何类型的指针并保护指针。接收一个size_t类型的参数num,表示函数要处理的空间大小,单位为字节。返回值类型也设置为void* 类型。

接着,使用assert函数保证传递给函数的指针为有效指针。创建一个指针变量origin_dest来存储指针变量dest的初始地址。

然后,就是核心的函数处理的逻辑。

因为vois * 和 const void*类型的指针无法直接使用,因此,在处理时,要将指针变量类型强制转换为unsigened char *和const unsigned char *类型,同时也能保证每次指针+1时,只会移动一个字节。然后,通过循环来完成复制操作。

最后,返回指针origin_dest。

代码形式如下:

cpp 复制代码
void * my_mcpy(void * dest,const void * src,size_t num)
{
    assert(dest && src);
    void * origin_dest = dest;
    while(num--)
    {
        *(unsigned char *)dest = *(const unsigned char *)src;
        dest = (unsigned char *)dest + 1;
        src = (const unsigned char *)src + 1;
    }
    return origin_dest;
}

尝试调用一下我们自己定义的函数:

cpp 复制代码
int main ()
{
    char arr1[10] = {1,2,3,4,5,6,7,8,9,10};
    char arr2[10] = {0};
    my_mcpy(arr2,arr1,10);
    int i;
    for(i = 0;i < 10;i++)
        printf("%d ",arr2[i]);
    return 0;
}
cpp 复制代码
1 2 3 4 5 6 7 8 9 10 

可见,函数成功将字符数组arr2的内容复制到了arr1中。但是,和memcpy函数一样,这个函数无法处理指针指向区域重叠的问题,那么有没有函数可以处理这种问题呢?答案当然是有,那就是memmove函数。

2. memmove函数的使用和模拟实现

函数原型如下:

cpp 复制代码
void *memmove(void *dest, const void *src, size_t num);
cpp 复制代码
Copies num characters from the object pointed to by src to the object pointed to by dest.
 Both objects are interpreted as arrays of unsigned char. 
The objects may overlap: copying takes place as if the characters were copied to 
    a temporary character array and then the characters were copied from the array to dest.

简单来说就是:

函数memmove能够实现和函数memcpy相同的效果,区别在于,函数memmove能够处理重叠区域的问题(复制过程 "仿佛" 先将 src 的数据复制到一个临时字符数组,再从临时数组复制到 dest )。

cpp 复制代码
int main ()
{
    char arr1[10] = {1,2,3,4,5,6,7,8,9,10};
    char arr2[10] = {0};
    memmove(arr1 + 3,arr1,5);
    int i;
    for(i = 0;i < 10;i++)
        printf("%d ",arr1[i]);
    return 0;
}
cpp 复制代码
1 2 3 1 2 3 4 5 9 10

如果是memcpy函数来处理,结果就会变为:

cpp 复制代码
1 2 3 1 2 3 1 2 9 10

我们会发现,后面的值在前面的值进行复制时被覆盖掉了,导致数据丢失。

因此,我们才说,函数memcpy无法处理重叠区域的情况。

但是,有意思的是,部分的编译器将函数memcpy的效果优化的和函数memmove一样,所以,可能在部分编译器上无法检验出二者的区别。比如我正在使用的CLion2024。

那么,接下来。我们就开始尝试模拟实现memmove的效果。

首先,函数的返回值,参数都与函数memcpy函数一致。

接着,保证传递给函数的指针是有效的,并创建指针变量origin_dest来保存dest的初始值。

然后,就是核心的复制逻辑。

我们可以发现,当src在dest的右侧时,不需要担心区域重叠的问题,因为此时即使从前往后复制也不会导致未复制的值被覆盖。此时,函数memmove与函数memmove的处理效果是相同的。那么,我们就只需要思考,src在dest左侧的情况。那么,如何处理这种情况呢?

首先,当从src指向的地址移动num个字节后依旧无法访问到dest初始指向的地址,那么也不会有重叠的风险,此时,函数的处理效果与memcpy相同。当有可能重叠时,如果依旧按照memcpy函数的逻辑,按地址从低到高逐个复制,那就会导致后面的值被前面的值覆盖。因此,我们需要换一个逻辑,既然地址从低到高行不通,那我们可以尝试地址从高到低逐个进行复制,这样就不用担心后面的值被覆盖。

最后,就是返回origin_dest值。

那么,代码形式如下:

cpp 复制代码
void * my_move(void * dest,const void * src,size_t num)
{
    assert(dest && src);
    void * origin_dest = dest;
        if(dest <= src || (const unsigned char *)src + num <= (unsigned char *)dest)
        {
            while(num--)
            {
                *(unsigned char *)dest = *(const unsigned char *)src;
                dest = (unsigned char *)dest + 1;
                src = (const unsigned char *)src + 1;
            }

        }
        else
        {
            dest = (unsigned char *)dest + num - 1;
            src = (unsigned char *)src + num - 1;
            while(num--)
            {
                *(unsigned char *)dest = *(const unsigned char *)src;
                dest = (unsigned char *)dest - 1;
                src = (const unsigned char *)src - 1;
            }
        }
    return origin_dest;
}

这里没有在将指针变量dest和src进行强制类型转换后没有直接进行自增或自减运算,是因为自增或自减运算操作符的优先级是高于强制类型转换的,这样可以防止编译器直接对void *类型的指针进行自增或自减运算。不过部分编译器可能将void *类型与整数运算的效果视为char *类型的指针运算的效果,所以不会显示错误。

3. memset函数的使用

函数原型如下:

cpp 复制代码
 void * memset ( void * dest, int ch, size_t count );
cpp 复制代码
Copies the value (unsigned char)ch into each of the first count characters of the object pointed to by dest.
 The behavior is undefined if access occurs beyond the end of the dest array.
 The behavior is undefined if dest is a null pointer.

简单来说就是:

函数memset会接收dest,ch,count三个参数,并将int类型的ch类型强制转换为unsigned char类型后插入地址dest后count个字节的空间中,并返回初始dest指向的地址。当函数访问越界或访问的是空指针时,结果都是未定义的。

我们可以用代码尝试一下:

cpp 复制代码
#include <string.h>
#include <stdio.h>
int main ()
{
    char arr1[] = "Hello World";
    memset(arr1,'a',4);
    printf("%s\n",arr1);
    return 0;
}
cpp 复制代码
aaaao World

这里在输入字符后依旧可以正常运行,原因是在C语言中,字符型数据的本质就是int类型的数据,值为字符对应的ASCII码值。

4. memcmp函数的使用

函数原型如下:

cpp 复制代码
int memcmp( const void* lhs, const void* rhs, size_t count );
cpp 复制代码
Compares the first count bytes of the objects pointed to by lhs and rhs. 
The comparison is done lexicographically.

简单来说就是:

函数memcmp会接收三个参数:lhs,rhs,count。然后,对lhs与rhs后的数据逐个比较,比较时,会将其类型强制转换为unsigned char类型进行比较,大于则返回一个正数,小于则返回一个负数,相等则比较下一个字节的数据。最多比较count个字节内的数据,如果都相等,则返回0。

用代码尝试使用一下:

cpp 复制代码
#include <string.h>
#include <stdio.h>
int main ()
{
    char arr1[] = "Hello World";
    char arr2[] = "Good morning";
    int n = memcmp(arr1,arr2,5);
    printf("%d\n",n);
    if(n > 0)
        printf("%s > %s\n",arr1,arr2);
    else if(n < 0)
        printf("%s < %s\n",arr1,arr2);
    else
        printf("%s = %s\n",arr1,arr2);
    return 0;
}
cpp 复制代码
1
Hello World > Good morning

我们发现,在这里,函数memcmp的返回值为1,应该是编译器内部进行定义的将正数返回值定为1。

相关推荐
3壹19 分钟前
单链表:数据结构中的高效指针艺术
c语言·开发语言·数据结构
YLCHUP3 小时前
【联通分量】题解:P13823 「Diligent-OI R2 C」所谓伊人_连通分量_最短路_01bfs_图论_C++算法竞赛
c语言·数据结构·c++·算法·图论·广度优先·图搜索算法
特立独行的猫a5 小时前
C/C++三方库移植到HarmonyOS平台详细教程
c语言·c++·harmonyos·napi·三方库·aki
啟明起鸣11 小时前
【数据结构】B 树——高度近似可”独木成林“的榕树——详细解说与其 C 代码实现
c语言·开发语言·数据结构
XH华12 小时前
C语言第十三章自定义类型:联合和枚举
c语言·开发语言
草莓熊Lotso13 小时前
【C语言强化训练16天】--从基础到进阶的蜕变之旅:Day13
c语言·开发语言·刷题·强化训练
刃神太酷啦14 小时前
Linux 常用指令全解析:从基础操作到系统管理(1w字精简版)----《Hello Linux!》(2)
linux·运维·服务器·c语言·c++·算法·leetcode
tju新生代魔迷1 天前
C语言宏的实现作业
c语言·开发语言
小莞尔1 天前
【51单片机】【protues仿真】基于51单片机宠物投食器系统
c语言·stm32·单片机·嵌入式硬件·51单片机·proteus