深入研究:对变量以及指针重新赋值过程中原本的地址是否会改变。(按值传递机制的深入)

在研究按值传递机制时,发现一些模糊的概念。就是在对一个原本的(指针)变量重新给定另外一个值时,会不会改变这个变量原本所在的内存位置(即地址)。因此,决定深入研究一下。而且这也是必要的。

  1. 给一个变量重新赋值时,地址的变化
     1 //验证变量在被赋值(以及被重赋值)时原本分配的内存地址是否会改变。
     2  
     3  #include <stdio.h>
     4  
     5  int main(void)
     6  {
     7      int a;//声明a,即已经给a分配一个内存地址
     8      printf("声明a时的地址:%p\n", &a);
     9  
    10      a = 1;  //初始化a
    11      printf("The address of origin: %p\n", &a);//输出a=1时的地址
    12  
    13      //int a = 2; ----> 不能以这种方式对a重新赋值. 否则会产生错误:‘a’重定义
    14      scanf("%d", &a);//对a重新赋值。
    15      printf("The address of later: %p\n", &a);//被重新赋值后的地址
    16  
    17      return 0;
    18  }

    运行结果:声明a时的地址:0x7ffc3cabc31c
                   The address of origin: 0x7ffc3cabc31c
                          2  ->这个2是我输入的。
                         The address of later: 0x7ffc3cabc31c
    结论:在声明时给变量a划分的地址,会在变量作用域内一直保持不变。原本a=1时,地址是xxx,在第a重新赋值时,会先将a内的数删除,然后再将新的数放进去,这个新的数的地址还是xxx。即地址一直不变,变得是这个地址里的数。(就像租房,有效期内房子一直不变,变的是住的人)

  2. 在对指针指向的地址重新赋值时,指针所指向的地址是否会改变?
     1 //验证对指针所指向的值重新赋值时,指针是否会改变。
     2  #include <stdio.h>
     3  
     4  int main(void)
     5  {
     6      int *p = NULL;
     7      int a = 1;
     8      printf("p指针存储的地址:%p\n", p); 
     9      printf("p指针的地址:%p\n", &p);
    10  
    11      p = &a; 
    12      printf("p指针存储的a地址:%p\n", p); 
    13      printf("p指针的地址:%p\n", &p);
    14  
    15      *p = 2;
    16      printf("p指针存储的对a重新赋值时地址:%p\n", p); 
    17      printf("p指针的地址:%p\n", &p);
    18  
    19      int b = 2;
    20      p = &b; 
    21      printf("p指针存储的b地址:%p\n", p); 
    22      printf("p指针的地址:%p\n", &p);
    23  
    24      return 0;
    25  }

     运行结果:p指针存储的地址:(nil)
                       p指针的地址:0x7ffe95265668
                       p指针存储的a地址:0x7ffe95265664
                       p指针的地址:0x7ffe95265668
                       p指针存储的对a重新赋值时地址:0x7ffe95265664
                       p指针的地址:0x7ffe95265668
                       p指针存储的b地址:0x7ffe95265660
                       p指针的地址:0x7ffe95265668
    分析:有点乱,但没关系,一句一句看。在将指针指向NULL时,实质上并没有给指针p分配存储的地址。即p不指向任何地址。所以输出了:p指针存储的地址:(nil)。而p本身的地址(即存储p的地址)为0x7ffe95265668,在整个main函数内,存储p指针的地址其实是一直不变的。这在第1点可以知道原因。p = &a这句是将p指向a所在的地址。*p = 2这句是通过*p对a重新赋值。期间,p存储的地址保持一致,即:0x7ffe95265664。p = &b这一句将p重新指向一个新的变量了。打个比方:(指针p包含的)地址:a本来是你家,后来你搬家了,那么地址肯定改变了,所以(指针p包含)地址也改变了。由0x7ffe95265664这个原本是指向a的地址 变为 指向b的地址:0x7ffe95265660。

  3. 在调用函数,重新赋值时,变量地址是否改变?
    按值传递机制:给函数传递变元时,变元值不会直接传递给函数,而是先复制变元值的副本,存储在栈上,再使这个副本可用于函数,而不是使用初始值。
    这个概念乍一看没什么难理解的。但是里面的含义也不仅仅停留在表面。不信?我们重点来谈谈:被调用函数修改属于调用函数的变量值的方式。(被调用函数和调用函数的关系就像被除数与除数的关系,不难理解吧?)
    被调用函数修改放在调用函数括号内的变量值的唯一方式是:把变量的地址接收为变元值。(这句话想理解透没那么简单)
                                                                                                     给被调用函数传递地址时,它只是传递地址的副本,而不是初始值。但是,副本仍是一个地址,仍引用最初的变量。这也是将变量地址传给scanf函数的原因,不传递地址,scanf这个被调用函数就没办法在最初的变量地址中存储值。还是看代码吧。
     1 #include <stdio.h>
     2  
     3  void change_int(int *a);
     4  
     5  int main(void)
     6  {
     7      int a = 2;
     8      printf("a本来的地址: %p\n", &a);
     9  
    10      change_int(&a);
    11      printf("被调用后a = %d\n", a); 
    12      printf("a后来的地址: %p\n", &a);
    13  
    14      return 0;
    15  }
    16  
    17  void change_int(int *a) 
    18  {
    19      printf("在被调用函数里面a的地址:%p\n", &a);
    20      *a = 3;
    21  }
    22  ~               

     

    输出结果:a本来存储的地址: 0x7ffc75a6f48c
                        在被调用函数里面a的地址:0x7ffc75a6f468
                        被调用后a = 3
                        a后来的存储地址: 0x7ffc75a6f48c
    分析结果:看到没?a本来存储的地址和后来存储的地址是一致的,说明被调用函数的作用是:通过调用函数,将main函数(不一定是在main函数里哦)中a的地址内的值改变。而a本身的地址是不变的。再看,在被调用函数(change_int(int *a) )里面a的地址:0x7ffc75a6f468,这说明什么?说明给被调用函数传递地址时,它只是传递地址的副本,而不是初始值。剩下没说的就是:在main函数里,a本来等于2的,后来通过调用函数,在被调用函数里(的*a=3)将a变成3,这又说明什么?要想改变变量的值,要把变量的地址作为为变元值。

    接下来要谈的是,将指针作为变元。这里你会知道我为什么说上面的一句话理解透没那么简单。先从最简单的入手,看代码:
    1  #include <stdio.h>
    2  
    3  int main(void)
    4  {
    5      char *a = "abc";
    6      scanf("%s",a);//这句目的是调用scanf函数对a重新赋值。
    7      printf("%s",a);
    8      return 0;
    9  }//这个代码的目的是将a中的abc变成sss

     

    这个代码可以编译连接成功。但是当输入:sss 按回车时,出现了这么一个错误:Segmentation fault (核心已转储),这个错误是由于地址出错。
    那么疑问来了,a是一个地址吧?那么我给被调用函数传递的是a地址啊,为什么不能成功改变a存储的值(即abs)呢?先看看代码
     1 #include <stdio.h>
     2  
     3  void change_piont(char **a);//用于改变指针所存储的值的函数。
     4  void change_int(int *b);//用于改变变量的值的函数。
     5  void change_array(char *c, int n);
     6  
     7  int main(void)
     8  {
     9      char *a = "absdefg";
    10      printf("a = %s\n", a); //输出absdefg
    11      change_piont(&a);//将指针变量a的地址作为变元
    12      printf("%s\n", a);//输出ddd,即改变指针a存储的值成功。
    13  
    14      int b = 2;
    15      printf("origin = %p\n", &b);//b原来的地址:xxx
    16  
    17      change_int(&b);//将变量b的地址作为变元。
    18      printf("(later)b = %d\n",b);//调用后b的值,输出为3
    19      printf("later = %p\n", &b);//经过调用一回后,b的地址:还是xxx
    20  
    21  
    22      char c[5] = "abcd";//数组c本来的值:abcd
    23      change_array(c,5);
    24      printf("c = %s\n",c);//输出结果:abcf
    25  
    26      return 0;
    27  }
    28  
    29  void change_piont(char **a)
    30  {
    31      char *b = "ddd";
    32      *a = b;
    33    // printf("%s\n", *a);
    34  }
    35  
    36  void change_int(int *b)
    37  {
    38      printf("chang_int -> the address of b = %p\n", &b);//这个地址和原本b的地址不一样,说明它是一个副本。
    39      *b = 3;
    40  }
    41  
    42  void change_array(char *c, int n)
    43  {
    44      c[3] = 'f';
    45  }
    46  /******************************************************
    47   * 原本输出结果:
    48   * a = absdefg
    49   * ddd
    50   * origin = 0x7ffc5c289f84
    51   * chang_int -> the address of b = 0x7ffc5c289f58
    52   * (later)b = 3
    53   * later = 0x7ffc5c289f84
    54   * c = abcf
    55   * ****************************************************/

     

    大多数都可以在注释上看明白,这里我重点要说这句(11句):change_piont(&a);//将指针变量a的地址作为变元。还记得上面的疑问吧?a是一个地址吧?那么我给被调用函数传递的是a地址啊,为什么不能成功改变a存储的值(即abs)。注意这里调用函数括号内与上一个代码中(scanf("%s",a))括号内的a有什么不同,多了一个取址符:&
    这里要注意了:指针a虽然是一个地址,但指针a也是一个变量。上面概念说了,改变一个变量所存储的值唯一的变法就是将它的地址作为变元。指针变量的地址是什么?是:&a。还是在看一次代码吧。
     1 #include <stdio.h>
     2  
     3  void change_piont(char **a);
     4  
     5  int main(void)
     6  {
     7      char *a = "abc";
     8      printf("a本来的地址:%p\n", &a);
     9      printf("a本来存储的地址:%p\n", a); 
    10  
    11      change_piont(&a);
    12      printf("调用后a的地址:%p\n",&a);
    13      printf("调用后a存储的地址:%p\n",a);
    14  
    15      printf("a = %s\n",a);
    16      return 0;
    17  }
    18  
    19  void change_piont(char **a)
    20  {
    21      printf("在被调用函数内a的地址: %p\n",&a);
    22      *a = "sss";
    23  
    24  }
    25  /************************************************
    26   * result:
    27   * a本来的地址:0x7ffe6bbd2088
    28   * a本来存储的地址:0x400680
    29   * 在被调用函数内a的地址: 0x7ffe6bbd2068
    30   * 调用后a的地址:0x7ffe6bbd2088
    31   * 调用后a存储的地址:0x400725
    32   * a = sss
    33   * **********************************************/

     

    看结果已经很清楚了。a本来的地址就是指针a本身的地址。a本来存储的地址就是本来存储在a指针的地址。举个例子吧。char *a = &b。a本来的地址就是:&a。a本来存储的地址就是:&b 或者 a。


  4. 结论:(1)在直接对变量重新赋值时,变量的地址不变。变的是存储在这个地址里的值。
                (2)对于指针,有两种可能。一是通过指针对指针所指向的地址重新赋值。这种情况下指针指向的地址不变,变的是指针指向的地址里面的值。二是直接对指针变量重新赋值,这种情况指针指向的地址会改变。(看看第二点的代码就清楚了)
                (3)在调用函数时,将(指针)变量的地址做为变元传递,并不是直接将这个变量原本的地址传递给函数调用,而是通过一个地址的副本,在改变变量的值时,变量存储的地址没有改变。特别要注意指针的地址,它是一个char**型。指针本来就是char*型。它的地址是char**型应该没什么疑问吧?
      
posted @ 2016-07-21 18:25  busui  阅读(7013)  评论(0编辑  收藏  举报