通过代码再探内存

30阅读 0评论2025-05-12 snow888
分类:C/C++

我们知道,在 C 语言开发的程序中,不同的函数之间进行内存信息的传递方式有如下几种:
1、全局变量
      全局变量在该程序代码中的所有函数中可见,如果你是在程序代码开头就定义了一个变量,则该变量的内容在其代码段中的所有函数中都是可见的。我们可以通过以下的程序代码进行验证。

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>

  4. int i;
  5. void print_i ()
  6. {
  7.        printf("=== i+1 = %d ===\n",i+1);
  8. }

  9. int main ( void )
  10. {
  11.        i = 1;
  12.        printf( " ==== %d ====\n",i);
  13.        print_i();
  14.        return 0;
  15. }
运行这段代码,我们可以看到程序分别打印出了 1 和 i+1 = 2 .
我们在 main 函数和 print_i 函数中并没有定义 i 变量,而是在这个程序代码的{BANNED}最佳前面定义了一个整型的变量 i 。

2、消息队列
     这个我就不通过代码进行举例了,消息队列不仅仅是可以在不同的函数间传递内存信息,还可以在不同的进程间传递内存信息。

3、管道
     管道也是基本功,我也就不再通过程序代码进行举例说明了,同样的,通过管道也不仅仅是可以在不同的函数间传递内存信息,也同样可以在不同的进程间传递内存信息。

4、共享内存
     共享内存不仅仅是可以在函数之间传递内存信息,还可以在不同的进程间传递内存信息。

5、通过参数在不同的函数间传递内存信息。

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>

  4. void create_mem(char **s)
  5. {
  6.         *s = (char *)malloc(sizeof(char)*65535);
  7.         if ( s == NULL )
  8.                 printf("=== malloc error ... ===\n");
  9. }

  10. int main ( void )
  11. {
  12.         int i = 0;
  13.         char *s;

  14.         for ( i = 0 ; i <100000000 ; i++ )
  15.         {
  16.                 printf("== %d ==\n",i);
  17.                 create_mem(&s);
  18.                 free(s); s=NULL;
  19.         }
  20.         return 0;
  21. }

这里我们要注意两个方面:
a、通过参数来进行内存信息的传递时,如果我们需要在被调用的函数中对传入的参数变量进行内容修改,我们需要传入该参数的地址,而不是传入该参数本身。
比如此例,我们在 main 函数中定义了一个 char *s ,如果我们要在 create_mem 函数中对这个字符指针进行内存分配的话,我们就需要在 create_mem 函数的参数中定义一个指向 char 类型的指针的指针,在调用的时候,我们将 s 的地址传进去,否则程序会 coredump 。
b、通过被调用的子函数 create_mem 内进行的内存分配,由于 create_mem 函数中要将对 s 的改变通过参数返回来,因此在子函数中不能进行 free 操作,则在调用 create_mem 的父函数中,必须执行 free(s); 和 s=NULL; 的操作,否则在多次进行内存分配后,会造成内存分配失败。这种情况经常会发生在重复调用 create_mem 函数的时候。

我们通过如下代码加以验证:

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>

  4. void create_mem(char **s)
  5. {
  6.         *s = (char *)malloc(sizeof(char)*65535);
  7.         if ( s == NULL )
  8.                 printf("=== malloc error ... ===\n");
  9. }

  10. int main ( void )
  11. {
  12.         int i = 0;
  13.         char *s;

  14.         for ( i = 0 ; i <100000000 ; i++ )
  15.         {
  16.                 printf("== %d ==\n",i);
  17.                 create_mem(&s);
  18. // free(s); s=NULL;
  19.         }
  20.         return 0;
  21. }
当我们将 free(s); s=NULL; 进行注释掉的时候,我们再次运行上述代码,发现会在运行一段时间后,程序出现错误。这表明如果我们没有在父函数中进行内存释放,那么子函数被持续调用时,会持续的分配内存,且内存不会被释放,{BANNED}最佳终导致内存分配失败。


6、通过返回值来实现在函数间传递内存信息。

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>


  4. char *create_mem();



  5. int main ( void )
  6. {
  7.         char *s;
  8.         long i = 0;


  9.         for ( i = 0 ; i < 100000000 ;i++ )
  10.         {
  11.                 s = create_mem();
  12.                 printf("== %d == %s\n",i,s);
  13.                 free(s); s = NULL;
  14.         }

  15. }

  16. char *create_mem()
  17. {
  18.         char *str;

  19.         str = ( char *)malloc(sizeof(char)*65535);

  20.         if ( str == NULL )
  21.                 printf("== malloc faild ==\n");
  22.         else
  23.                 snprintf(str,40,"This is test for malloc function \n");
  24.         return str;
  25. }
我们观察上述程序代码,我们在 create_mem 函数中定义了一个 char *str 的字符指针变量,并进行内存分配和赋值,我们在调用 create_mem 函数的父函数 main 中,定义了一个 char *s 的字符指针,并使之指向了 create_men 的函数返回值,实际上是指向了 create_men 函数中定义的 char *str 的地址。
通过以上的分析,我们要注意如下的情况:
通过返回值来进行函数间的内存信息传递时,由于内存是在子函数中分配的,那么父函数中定义的 char *s 的指针指向了子函数 create_mem 的返回值,也就是子函数中定义的 char *str 的地址。由于在子函数中不能执行 free(str); 和 str=NULL; 操作(因为执行了这两个操作,str 的内存就被释放和清理了,也就无法返回 str 的值了),因此我们需要在父函数中进行  free(s);和 s=NULL;的操作(实际上就相当于是在 create_men 函数中执行的 free(str); 和 str=NULL;进行了针对 char *str 的内存释放和清理,只是内存释放和清理是在将 str 的值返回给父函数后再执行的。)。

我们再来观察如下代码:

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>


  4. char *create_mem();



  5. int main ( void )
  6. {
  7.         char *s;
  8.         long i = 0;


  9.         for ( i = 0 ; i < 100000000 ;i++ )
  10.         {
  11.                 s = create_mem();
  12.                 printf("== %d == %s\n",i,s);
  13. // free(s); s = NULL;
  14.         }

  15. }

  16. char *create_mem()
  17. {
  18.         char *str;

  19.         str = ( char *)malloc(sizeof(char)*65535);

  20.         if ( str == NULL )
  21.                 printf("== malloc faild ==\n");
  22.         else
  23.                 snprintf(str,40,"This is test for malloc function \n");
  24.         return str;
  25. }

这一段代码也能执行,但是在执行一段时间后会报错,原因就是在不停的嗲用 create_men 进行内存分配,由于内存分配始终没有释放,经过一段时间的运行后,内存会报错。

几个启示:
1、在两个函数中,不管是通过参数传递还是通过返回值传递某个内存信息,其在子函数中分配的内存并不会跟随着子函数的退出而消失,必须在父函数中进行清理,否则随着子函数的多次调用,有可能造成内存泄漏或者是内存分配失败,导致程序运行出现混乱,严重的会造成程序崩溃或者是操作系统崩溃。
2、通过参数在不同的函数中进行内存信息传递时,如果你需要在子函数中改变传入的参数值,那么你需要传递的是这个参数的地址,而不应该是参数变量本身。
这一点我们分别通过如下的两个函数来进行验证。
函数 1 :

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. #include <stdlib.h>

  3. void add_one(int *j)
  4. {
  5.        i = j+1;
  6. }

  7. int main ( void )
  8. {
  9.         int i;
  10.         add_one(&i);
  11.         printf(" == i + 1 = %d ==\n",i);
  12.         return 0;
  13. }
注意:
这个地方我们在 main 函数中定义的是 int i; 变量,我们传入到 add_one 函数中时,传入的是指向 i 变量的地址,而我们在 add_one 函数中定义的接收这个变量的是一个指针。假设我们进行如下修改:

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. #include <stdlib.h>

  3. void add_one(int j)
  4. {
  5.        i = j+1;
  6. }

  7. int main ( void )
  8. {
  9.         int i;
  10.         add_one(i);
  11.         printf(" == i + 1 = %d ==\n",i);
  12.         return 0;
  13. }
则程序运行会报错。
原因是我们传入的参数 i ,但是在子函数 add_one 中,会将该变量当作是一个静态的变量,对于一个静态的不可修改的内存区域进行更改,会导致程序 coredump 。

函数 2 

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. #include <stdlib.h>

  3. void update_str(char **str)
  4. {
  5.        snprintf(*str,64,"%s\n","This is a menory test");
  6. }

  7. int main ( void )
  8. {
  9.         char *s;
  10.         s = ( char *)malloc(sizeof(char)*65);
  11.         update_str(&s);
  12.         printf("%s\n",s);
  13.         free(s);s=NULL;
  14.         return 0;
  15. }
注意:
这个程序里面,我们在父函数 main 中定义了一个字符指针 char *s; 我们希望这个字符指针指向的地址可以被赋值并保存一个字符串。这个时候我们进行了内存分配,并通过调用 update_men 函数对 char *s 指向的内存地址区间进行了赋值。我们可以看到,我们传入 update_str 函数的,是这个指针的地址,而不是这个指针本身。
同样的,我们在 update_str 函数中,我们定义的参数是一个指向了 str 字符串的指针,即 char **str , 同时,我们在进行赋值操作时,我们是对 *str 进行的赋值操作,也就是指向的这个字符串的内存地址位置进行的赋值操作。
假设我们对该程序进行如下修改:

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. #include <stdlib.h>

  3. void update_str(char *str)
  4. {
  5.        snprintf(*str,64,"%s\n","This is a menory test");
  6. }

  7. int main ( void )
  8. {
  9.         char *s;
  10.         s = ( char *)malloc(sizeof(char)*65);
  11.         update_str(s);
  12.         printf("%s\n",s);
  13.         free(s);s=NULL;
  14.         return 0;
  15. }

则程序的执行会报错。




上一篇:三段代码扒内存
下一篇:浅析结构 struct 与联合 union 的区别