變量名存放在哪裏?

變量名存放在哪裏?

這是一個有意思且無聊的問題,以前在網上看到有人問道這個問題,好比說在PHP裏面咱們寫下 $name = "名字" 這樣的代碼語句,在代碼運行的時候,$name 在哪裏呢? 瞭解了變量在內存中存儲方式的人會知道,通常變量的值在存放在棧內存裏面的,可是名字呢?php

針對這個問題,我們先要區分一下編譯型語言和解釋型語言,這2種語言運行方式徹底不同,C/C++是典型的編譯型語言,並且PHP/JS則是典型的解釋型語言。程序員

編譯型語言要想運行,必須使用一個編譯器去把代碼轉換成目標平臺機器代碼。而解釋型語言是經過一個解釋器實時翻譯成一種中間代碼一行行運行。前者又被稱爲靜態語言,後者又被稱爲動態語言。像Java,C#則屬於這2種中間,由於他們有一個預編譯的過程,會先把代碼轉換成中間代碼存放起來,在Java裏面就叫字節碼,而後在虛擬機(jvm)裏面執行,效率比純解釋執行高。PHP就有一個opcache擴展能夠把生成的中間代碼opcode緩存起來以提升效率,沒必要每次運行的時候都生成。shell

說這麼多,想說明一個問題,那就是變量名和變量在這2種語言裏面的存儲是有區別的,回到最開始的問題,咱先說說經典的C語言:編程


C語言裏面變量和變量名的存儲

爲了說明這個問題,我們簡單的來講一下C裏面變量在內存裏面的存儲:數組

1.棧區(stack)— 由編譯器自動分配釋放 ,存放爲運行函數而分配的局部變量、函數參數、返回數據、返回地址等。緩存

2.堆區(heap) — 通常由程序員分配釋放, 用來存儲數組,結構體,對象等。若程序員不釋放,程序結束時可能由OS回收。sass

3.全局區(靜態區)(static)— 存放全局變量、靜態數據、常量。程序結束後由系統釋放。php7

4.文字常量區 — 常量字符串就是放在這裏的。 程序結束後由系統釋放。jvm

5.程序代碼區 — 存放函數體(類成員函數和全局函數)的二進制代碼。函數

棧堆

棧內存是有大小限制的,好比默認狀況下,Linux平臺的是8MB,若是超過這個限制,就會出現 stackoverflow,而堆內存並沒有限制,內存有多大就能夠申請多大。

看完上面的說明,咱們能夠得出一個結論: 全局變量存放在全局區,在程序一開始就分配好了,並且局部變量在存放在棧區,運行的時候分配內存,用完以後內存會被自動釋放。

可是這好像並無說明變量名在哪裏吧?好比下面這段C代碼,a, b到底存在哪裏?:

#include <stdio.h>

int a = 1; //全局初始化區

int main(int argc, char const *argv[]) {
    int b; //棧
    b = a + 5;
    printf("%d\n", b);
    return 0;
}
複製代碼

爲了搞明白這個問題,咱們須要瞭解一下C語言的執行過程,C語言執行須要通過預處理(Preprocessing)、編譯(Compilation)、彙編(Assemble)、連接(Linking)等幾個階段,在編譯成彙編語言這個階段就已經沒有變量名了,使用gdb能夠查看編譯後的彙編代碼:

(gdb) disass main
Dump of assembler code for function main:
   0x0000000000400526 <+0>:	push   %rbp
   0x0000000000400527 <+1>:	mov    %rsp,%rbp
   0x000000000040052a <+4>:	sub    $0x20,%rsp
   0x000000000040052e <+8>:	mov    %edi,-0x14(%rbp)
   0x0000000000400531 <+11>:	mov    %rsi,-0x20(%rbp)
   0x0000000000400535 <+15>:	mov    0x200afd(%rip),%eax        # 0x601038 <a>
   0x000000000040053b <+21>:	add    $0x5,%eax
   0x000000000040053e <+24>:	mov    %eax,-0x4(%rbp)
   0x0000000000400541 <+27>:	mov    -0x4(%rbp),%eax
   0x0000000000400544 <+30>:	mov    %eax,%esi
   0x0000000000400546 <+32>:	mov    $0x4005e4,%edi
   0x000000000040054b <+37>:	mov    $0x0,%eax
   0x0000000000400550 <+42>:	callq  0x400400 <printf@plt>
=> 0x0000000000400555 <+47>:	mov    $0x0,%eax
   0x000000000040055a <+52>:	leaveq 
   0x000000000040055b <+53>:	retq   
End of assembler dump.
複製代碼

雖然上面這個很難讀懂,可是應該能看到在這一大堆彙編指令執行的背後,並無變量名這個東西,全部的變量名到最後都變成了內存地址,彙編指令操做的是各類寄存器和內存地址。

定義int a;時,編譯器分配4個字節內存,並命名該4個字節的空間名字爲a(即變量名),當用到變量名a時,就是在使用那4個字節的內存空間。 5是一個常數,在程序編譯時存放在代碼的常量區存放着它的值(就是5),當執行a=5時,程序將5這個常量拷貝到a所在的4個字節空間中,就完成了賦值操做.a是咱們對那個整形變量的4個字節取的"名字",是咱們人爲給的,實際上計算機並不存儲a這個名字,只是咱們編程時給那4個字節內存取個名字好用。實際上程序在編譯時,全部的a都轉換爲了那個地址空間了,編譯成機器代碼後,沒有a這個說法了。a這個名字只存在於咱們編寫的代碼中.5不是被隨機分配的,而老是位於程序的數據段中,可能在不一樣的機器上在數據段中的位置可能不一致,它的地址其實不能以咱們經常使用到的內存地址來理解,由於牽扯到一個叫"計算機尋址方式"的問題。

以上的內容有參考網上不少文章,僅供參考!有一點須要明白在操做系統裏面,程序的內存地址並非物理地址,並且經過一個基址+偏移量的方式的計算獲得的虛擬地址,操做系統爲了更好的管理應用在內存這個層面作了不少抽象。


PHP裏面的變量和變量名存儲

PHP語句在執行的時候須要zend引擎進行詞法分析,語法分析,編譯成opcode,opcode能夠理解爲一種相似機器指令的語句,而後由zend引擎去執行。

有擴展能夠打印出生成的opcode,下面看一下:

PHP代碼:

<?php
$a = 1;
$b = 2;

function hello($d,$e) {
    $c = $d+$e;
}

hello($a, $b);
複製代碼

opcode結果:

jwang@jwang:~$ php7.0 -dvld.active=1 ~/index.php 
Finding entry points
Branch analysis from position: 0
1 jumps found. (Code = 62) Position 1 = -2
filename:       /home/jwang/index.php
function name:  (null)
number of ops:  14
compiled vars:  !0 = $a, !1 = $b
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   EXT_STMT                                                 
         1        ASSIGN                                                   !0, 1
   3     2        EXT_STMT                                                 
         3        ASSIGN                                                   !1, 2
   5     4        EXT_STMT                                                 
         5        NOP                                                      
  10     6        EXT_STMT                                                 
         7        INIT_FCALL                                               'hello'
         8        EXT_FCALL_BEGIN                                          
         9        SEND_VAR                                                 !0
        10        SEND_VAR                                                 !1
        11        DO_FCALL                                      0          
        12        EXT_FCALL_END                                            
  11    13      > RETURN                                                   1

branch: #  0; line:     2-   11; sop:     0; eop:    13; out0:  -2
path #1: 0, 
Function hello:
Finding entry points
Branch analysis from position: 0
1 jumps found. (Code = 62) Position 1 = -2
filename:       /home/jwang/index.php
function name:  hello
number of ops:  8
compiled vars:  !0 = $d, !1 = $e, !2 = $c
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   5     0  E >   EXT_NOP                                                  
         1        RECV                                             !0      
         2        RECV                                             !1      
   7     3        EXT_STMT                                                 
         4        ADD                                              ~3      !0, !1
         5        ASSIGN                                                   !2, ~3
   8     6        EXT_STMT                                                 
         7      > RETURN                                                   null

branch: #  0; line:     5-    8; sop:     0; eop:     7; out0:  -2
path #1: 0, 
End of function hello
複製代碼

zend引擎會把PHP代碼轉換成一組op命令操做,上面的就有2組操做。在第一組命令裏面能夠看到在開始的時候,有一個compiled vars: !0 = $a, !1 = $b, 而後後面有2個ASSIGN操做。能夠看到在最終執行的時候並非使用的a,b,而是使用了!0, !1這樣的符號去代替。

!0, !1並非一個固定的值,它每次執行的時候表明的是op命令的操做數。op命令是zend引擎本身定義好的一些操做,具體怎麼執行得看zend引擎怎麼處理了。

PHP的變量則是經過一個 _zval_struct 結構體形式存儲的,講道理,大部分時候還在存儲在堆內存裏面的,既然存儲在堆裏面那麼就必須手動釋放內存,因此纔有了自動垃圾回收機制!


因此,最後總結一下,變量名說到底仍是方便程序員編程的,名字起的好便於記憶和閱讀代碼,就像人同樣,名字只是一個代號,本質上只是一堆碳水化合物。變量名在代碼運行的時候都會被一些特殊的符號代替,內存裏面並不會有變量名,因此變量名寫的長並不會影響運行速度,用中文仍是英文也不影響。而變量不管什麼類型,最終運行的時候操做的仍是內存地址裏面數據,變量之因此有類型,是爲了方便編譯器處理。

相關文章
相關標籤/搜索