深入研究:對變量以及指針重新賦值過程中原本的地址是否會改變。(按值傳遞機制的深入)


在研究按值傳遞機制時,發現一些模糊的概念。就是在對一個原本的(指針)變量重新給定另外一個值時,會不會改變這個變量原本所在的內存位置(即地址)。因此,決定深入研究一下。而且這也是必要的。

  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**型應該沒什么疑問吧?
      


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM