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。

相关推荐
小莞尔6 小时前
【51单片机】【protues仿真】基于51单片机的篮球计时计分器系统
c语言·stm32·单片机·嵌入式硬件·51单片机
小莞尔6 小时前
【51单片机】【protues仿真】 基于51单片机八路抢答器系统
c语言·开发语言·单片机·嵌入式硬件·51单片机
liujing102329296 小时前
Day03_刷题niuke20250915
c语言
第七序章9 小时前
【C++STL】list的详细用法和底层实现
c语言·c++·自然语言处理·list
l1t11 小时前
利用DeepSeek实现服务器客户端模式的DuckDB原型
服务器·c语言·数据库·人工智能·postgresql·协议·duckdb
l1t13 小时前
利用美团龙猫用libxml2编写XML转CSV文件C程序
xml·c语言·libxml2·解析器
Gu_shiwww19 小时前
数据结构8——双向链表
c语言·数据结构·python·链表·小白初步
你怎么知道我是队长19 小时前
C语言---循环结构
c语言·开发语言·算法
程序猿编码20 小时前
基于 Linux 内核模块的字符设备 FIFO 驱动设计与实现解析(C/C++代码实现)
linux·c语言·c++·内核模块·fifo·字符设备
mark-puls1 天前
C语言打印爱心
c语言·开发语言·算法