• C库函数详解 - 内存操作函数:memcpy()、memmove()、memset()、memcmp()


    目录

    一、memcpy()

    函数原型

    参数说明

    模拟算法

    使用示例

    二、memmove()

    函数原型

    参数说明

    模拟算法

    使用示例 

    三、memset()

    函数原型

    参数说明

    使用说明

    使用示例

    四、memcmp()

    函数原型

    参数说明

    使用说明

    使用示例


    一、memcpy()

    函数原型

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

    参数说明

    • 函数 memcpy src 位置开始向后复制 num 个字节的数据到 dest 的内存位置。
    • void * dest 代表目标的内存地址,const void * src 代表源内存地址。其中二者的数据类型均为 void * 。void * 可以存储任何类型地址的值。因此,该函数拷贝的内存数据可以是任意类型(如果int、float、double等均可)。
    • size_t 即unsigned int类型。注意:num代表的是要拷贝的字节数,而非元素个数!如要从src拷贝一个int类型的数据到dest,则num应传入4,而非1.
    • 该函数的返回值类型也为 void * ,也即只返回目标地址的数值。后续如何按照基类型取出数据、使用数据,可以由调用方在调用函数后实现。

    模拟算法

    1. void* my_memcpy(void* dest, const void* src, size_t num) {
    2. void *ret = dest; //保存dest,用于最终输出
    3. assert(dest);
    4. assert(src);
    5. while (num--) {
    6. *(char*)dest = *(char*)src1; //(char*): 取出每一个字节(8 bit)的值,以单个字节为单位进行赋值
    7. dest = (char*)dest + 1;
    8. src = (char*)src + 1;
    9. } //void * 类型是不能直接运算的,因为没有步长。(char*)将dest与src转换为以char为步长,再向后移动
    10. return ret;
    11. }
    • 该函数用于实现没有重叠部分内存的内存数据拷贝。如果sourcedestination有任何的重叠,复制的结果都是未定义的。
    • 拷贝内存值按从低地址到高地址的顺序进行,多用于数组。

    • 根据memcpy()的模拟算法,如果src与dest的内存空间有重叠部分,则可能导致src中的内容被覆盖,无法输出正确的值。 

    src中元素3的位置恰好也是dest中首元素的位置。dest的首元素被更改的同时,src中的元素也被更改。因此,memcpy()是不可用于“自己拷贝到自己后面”这样的操作的。这一问题留给了memmove()来解决。

    使用示例

    1.简单

    1. int main()
    2. {
    3. int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
    4. int arr2[10] = { 0 };
    5. memcpy(arr2, arr1, 20); //拷贝20个字节,即5个int元素
    6. float arr3[] = { 1.0f,2.0f,3.0f,4.0f };
    7. float arr4[5] = { 0.0 };
    8. memcpy(arr3, arr4, 8); //拷贝8个字节,即2个float元素
    9. return 0;
    10. }

     2.进阶

    1. //示例来自cplusplus官网
    2. /* memcpy example */
    3. #include
    4. #include
    5. struct {
    6. char name[40];
    7. int age;
    8. } person, person_copy;
    9. int main ()
    10. {
    11. char myname[] = "Pierre de Fermat"; //定义一个字符串
    12. /* 用 memcpy 拷贝字符串 */
    13. //每个char类型占一个字节,因此要拷贝的字节数即strlen()+1,加一是因为要把'\0'也拷贝过去。
    14. memcpy ( person.name, myname, strlen(myname)+1 );
    15. person.age = 46;
    16. /* 用 memcpy 拷贝结构体 */
    17. //sizeof操作符,可以直接得到结构体变量在内存中所占的字节数。
    18. memcpy ( &person_copy, &person, sizeof(person) );
    19. //直接完成了结构体之间的数据拷贝:从person拷贝到person_cpy,不用手动转义,非常方便
    20. printf ("person_copy: %s, %d \n", person_copy.name, person_copy.age );
    21. return 0;
    22. }


    二、memmove()

    函数原型

    void * memmove ( void * dest, const void * src, size_t num );

    参数说明

    • 该函数的参数与返回值类型与memcpy()函数相同。该函数同样用作 src 位置开始向后复制 num 个字节数据到 dest 的内存位置。
    • memmove()函数与memcpy()函数主要的区别在于memmove()可以进行有内存重叠的数据拷贝,而memcpy()绝对不能。memmove()的功能比memcpy()更加完善。
    • 可以理解为:如果memcpy()函数够到了60分,那么memmove()函数却能到达90分。

    模拟算法

    1. #include
    2. #include
    3. //模拟实现memmove()
    4. void* my_memmove(void* dest, const void* src, size_t num)
    5. {
    6. void* ret = dest; //保存结果用于输出
    7. //从前向后拷贝,也可以写成if(dest <= src || (char*)dest >= (char*)src + num)
    8. if (dest <= src)
    9. {
    10. while (num--) {
    11. *(char*)dest = *(char*)src; //拷贝
    12. dest = (char*)dest + 1;
    13. src = (char*)src + 1; //指针从前向后移动(从低地址向高地址移动)
    14. }
    15. }
    16. else //从前向后拷贝
    17. {
    18. dest = (char*)dest + num - 1;
    19. src = (char*)src + num - 1; //初始化两指针至各自范围的最后
    20. while (num--) {
    21. *(char*)dest = *(char*)src; //拷贝
    22. dest = (char*)dest - 1;
    23. src = (char*)src - 1; //指针从后向前移动
    24. }
    25. }
    26. return ret; //返回值为dest
    27. }
    28. //测试代码///
    29. int main() {
    30. int arr1[] = { 2,3,4,5,6 };
    31. my_memmove(arr1+2, arr1, 8);
    32. //预计 2 3 2 3 6
    33. for (int i = 0; i < 5; i++) {
    34. printf("%d ", arr1[i]);
    35. }
    36. printf("\n-------------------\n");
    37. int arr2[] = { 2,3,4,5,6 };
    38. memmove(arr2 + 2, arr2, 8);
    39. for (int i = 0; i < 5; i++) {
    40. printf("%d ", arr2[i]);
    41. }
    42. return 0;
    43. }

    拷贝顺序的结论如图所示。上面提到,当有内存重叠时,拷贝的顺序是有讲究的。若不遵守下图的结论, 仍将导致src中原来的值被覆盖,无法输出正确的结果。

    ***错误的模拟算法 

    如下代码是错误的:

    1. //错误的代码
    2. void* my_memmove(void* dest, const void* src, size_t num)
    3. {
    4. void * ret = dest;
    5. while (num--) {
    6. //从前向后拷贝
    7. if (dest <= src)
    8. {
    9. *(char*)dest = *(char*)src;
    10. dest = (char*)dest + 1;
    11. src = (char*)src + 1;
    12. }
    13. else
    14. {
    15. dest = (char*)dest + num - 1; //错误
    16. src = (char*)src + num - 1; //错误
    17. *(char*)dest = *(char*)src;
    18. dest = (char*)dest - 1;
    19. src = (char*)src - 1;
    20. }
    21. return ret;
    22. }

    有一些同学可能认为,while(num--)语句与if语句的顺序可以调换。正确的代码是先进行情况判断,再进入while(num--)进行赋值与移动。将二者顺序调换,乍一看没有什么问题,是先设定一共要移动num次,再进入判断进行具体的操作。但是这样书写是有问题的,因为在dest > src && dest < src+num时,需要从后向前拷贝,这意味着dest和src的起始位置要发生变化。

    上述代码中标记“错误”的语句便是dest和src初始化的语句。如果直接将while和if的位置调换,则每一次进入循环,都要初始化一遍dest和src。如此,dest与src的功能就被打乱了。

    使用示例 

    1. //示例来自cplusplus官网
    2. /* memmove example */
    3. #include
    4. #include
    5. int main ()
    6. {
    7.  char str[] = "memmove can be very useful......";
    8.  memmove (str+20,str+15,11); //表示将包括str+15向后11个字节的内容移动到str+20位置
    9.  puts (str);
    10.  return 0;
    11. }

    输出:

    如下图,将 src = str+11 位置开始,包括该位置共向后拷贝11字节。每个char占一个字节,因此拷贝了"very useful"这7个char字母至dest = str+20的位置。


    三、memset()

    函数原型

    void * memset ( void * ptr, int value, size_t num );

    参数说明

    • 第一个参数 ptr 为指针类型,表示要进行操作的内存的地址。如要对数组arr进行内存内容设置,则该参数的值为arr。
    • 第二个参数 value 为要设定的内存的值。该值的数据类型是int型,但char值也是可以的。
    • 第三个参数 num 为要设置值的内存的字节数。注意:是字节数,而不是元素的个数。如要改变两个int类型的值,num应为 8 ,而不是2.

    使用说明

    例如,有数组arr:[ 1  2  3  4  5 ]

    要将其前两个元素值设定为0,则可使用memset函数:memset(arr, 0 , 8)

    输出:[ 0  0  3  4  5 ].原理如下(以小端存储的形式展现):

    arr数组为int类型,一个int为4字节、32bit,内存中的存储如下(二进制):

    01  00  00  00    00  00  00  00    00  00  00  00    00  00  00  00  

    02  00  00  00    00  00  00  00    00  00  00  00    00  00  00  00  

    03  00  00  00    00  00  00  00    00  00  00  00    00  00  00  00  

    04  00  00  00    00  00  00  00    00  00  00  00    00  00  00  00  

    05  00  00  00    00  00  00  00    00  00  00  00    00  00  00  00  

    memset(arr, 0, 8),将前8个字节(2个int)置为0(其实每个比特位都被置为0了,但由于其它的比特位已经为0,故没有标出来)

    但若使用 memset(arr, 1, 8),并不是把数组前两个元素置为 1 .因为memset()函数是针对内存中每个字节的,memset(arr, 1, 8)的实际作用是将前8个字节中的内容全部置为1.

    00  00  00  01    00  00  00  01    00  00  00  01    00  00  00  01  

    00  00  00  01    00  00  00  01    00  00  00  01    00  00  00  01  

    03  00  00  00    00  00  00  00    00  00  00  00    00  00  00  00  

    04  00  00  00    00  00  00  00    00  00  00  00    00  00  00  00  

    05  00  00  00    00  00  00  00    00  00  00  00    00  00  00  00  

    此时,前4字节按照int类型解析出来,结果为1000000010000000100000001,即16843009。

    总结:

    • int类型数组除了置0外,用memset置换成任何数都是错的。
    • memset只适用于每个元素只占1个字节的数组,比如char型数组。因为memset的操作单位就是每个字节。只有char类型的数组不会出现错误。

    使用示例

     

    1. //示例来自cplusplus官网
    2. /* memset example */
    3. #include
    4. #include
    5. int main ()
    6. {
    7. char str[] = "almost every programmer should know memset!";
    8. memset (str,'-',6); //表示将从str开始,包括str向后6个字节的内存内容置为'-'
    9. puts (str);
    10. return 0;
    11. }
    12. //输出:------ every programmer should know memset!

    四、memcmp()

    函数原型

    int memcmp ( const void * ptr1, const void * ptr2, size_t num );

    参数说明

    • 比较ptr1和ptr2指针开始的num个字节。
    • ptr1和ptr2分别是两个代表要比较的内存空间(一般是数组)的指针。
    • num是要比较的字节数。(注意:不是元素个数)。

    使用说明

    • 返回值为整型,若返回值>0,则ptr1的内存长度大于ptr2;若返回值==0,则二者相等;若返回值<0,则ptr1的内存长度小于ptr2

    使用示例

    1. //示例来自cplusplus官网
    2. /* memcmp example */
    3. #include
    4. #include
    5. int main ()
    6. {
    7. //创建两个要用作比较的数组
    8. char buffer1[] = "DWgaOtP12df0";
    9. char buffer2[] = "DWGAOTP12DF0";
    10. //接受比较的结果
    11. int n;
    12. //要比较的字节数为buffer1的长度
    13. //两字符串的比较可以用strcmp(buffer1,buffer2)函数实现,原理大致相同。
    14. n = memcmp ( buffer1, buffer2, sizeof(buffer1) );
    15. if (n>0) printf ("'%s' is greater than '%s'.\n",buffer1,buffer2);
    16. else if (n<0) printf ("'%s' is less than '%s'.\n",buffer1,buffer2);
    17. else printf ("'%s' is the same as '%s'.\n",buffer1,buffer2);
    18. return 0;
    19. }

  • 相关阅读:
    内网渗透-内网环境下的横向移动总结
    【openGauss】两种在openGauss中使用存储过程生成文本文件的方式
    Miniconda 使用进阶,把它添加到右键菜单中
    【Python Numpy】Ndarray属性
    洛谷P5451 密码学第三次小作业
    小程序源码:猜歌喝酒小游戏多功能组合微信小程序源码下载
    Go语言excelize包-03-行和列操作(可见性、指定列宽、指定行高、列删除、行删除、插入列、插入行、分级显示)
    期末论文LaTeX模板
    【云原生】什么是CI/CD? | CI/CD 带来的好处
    网络路由详解
  • 原文地址:https://blog.csdn.net/wyd_333/article/details/126826890