程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C語言內存地址基礎

C語言內存地址基礎

編輯:C++入門知識

從計算機內存的角度思考C語言中的一切東東,是挺有幫助的。我們可以把計算機內存想象成一個字節數組,內存中每一個地址表示 1 字節。比方說我們的電腦有 4K 內存,那這個內存數組將會有 4096 個元素。當我們談論一個存儲地址的指針時,就當相於我們在談論一個存儲著該內存數組某個元素索引的指針。逆向引用某個指針,將會得到數組中該索引所指向的 值。這一切當然都是謊言。操作系統對內存的管理要遠比這復雜。內存不一定連續,也不一定按順序處理。但前面的類比是一種討論C語言內存的簡單方式。

如果對『指針』、『地址』和『逆向引用』感到混亂,請看《C語言指針5分鐘教程》。// 譯注:“dereferencing” 的譯法比較多,本文采用了“逆向引用”。 

假設我們的計算機有 4K 的內存,下一個開放地址的索引是2048。我們聲明一個新的字符變量i='a'。當該變量所 獲得的內存放置了它的值,變量的名字也與內存中的該位置關聯,我們的字符i就獲得了一個存儲在2048位置的值。該字符是單字節的因此它只占用了索引為 2048 的位置。如果我們對 i 變量使用地址操作符&),它將返回到索引為2048的位置。如果這個變量是另一種類型,比如是 int,它將占用4字節,在數組中占用索引為 2048-2051 的位置。使用地址操作符仍將返回索引2048的位置,因為 int 型即便占用了 4 字節,但它開始於 2048 位置。我們看一個例子:

  1. // intialize a char variable, print its address and the next address  
  2. char charvar = '\0';  
  3. printf("address of charvar = %p\n", (void *)(&charvar));  
  4. printf("address of charvar - 1 = %p\n", (void *)(&charvar - 1));  
  5. printf("address of charvar + 1 = %p\n", (void *)(&charvar + 1));  
  6.    
  7. // intialize an int variable, print its address and the next address  
  8. int intvar = 1;  
  9. printf("address of intvar = %p\n", (void *)(&intvar));  
  10. printf("address of intvar - 1 = %p\n", (void *)(&intvar - 1));  
  11. printf("address of intvar + 1 = %p\n", (void *)(&intvar + 1));  

運行將得到如下的輸出:

  1. address of charvar = 0x7fff9575c05f 
  2. address of charvar - 1 = 0x7fff9575c05e 
  3. address of charvar + 1 = 0x7fff9575c060 
  4. address of intvar = 0x7fff9575c058 
  5. address of intvar - 1 = 0x7fff9575c054 
  6. address of intvar + 1 = 0x7fff9575c05c 

在第一個例子的1-5行中,我們聲明了一個字符變量,並打印輸出該字符的地址,然後打印了內存中位於該變量前後的兩個地址。我們是通過使 用&操作符並+1或-1來獲取前後兩個地址的。在7-11行的第二個例子中我們做了差不多的事,除了聲明了一個int型變量,打印出它的地址以及 緊鄰它前後的地址。

在輸出中,我們看到地址是 16 進制的。更值得注意的是,字符的地址前後相差1字節。int 型變量地址前後相差四字節。內存地址的算法、指針的算法、都是根據所引用的類型的大小的。一個給定的類型的大小是依賴於平台的,我們這個例子中的char 是1字節,int是四字節。將字符的地址-1是改地址前的地址,而將int型地址-1是該地址前4個的地址。

在例子中,我們是用地址操作符來獲取變量的地址,這和使用表示變量地址的指針是一樣的效果。

英文原博中評論已經提出:存儲&charvar-1一個非法的地址因它位於數組之前)在技術上是未特別指出的行為。C的標准已經聲明,未特別指出的以及在一些平台存儲一個非法地址都將引起錯誤。

數組地址

在C語言中,數組是相鄰的內存區域,它存儲了大量相同數據類型的值int、long、*char等等)。很多程序員第一次用C時,會將數組當做指針。那是不對的。指針存儲一個簡單的內存地址,而一個數組是一塊存儲多個值的連續的內存區域。

  1. // initialize an array of ints 
  2. int numbers[5] = {1,2,3,4,5}; 
  3. int i = 0; 
  4.   
  5. // print the address of the array variable 
  6. printf("numbers = %p\n", numbers); 
  7.   
  8. // print addresses of each array index 
  9. do { 
  10.     printf("numbers[%u] = %p\n", i, (void *)(&numbers[i])); 
  11.     i++; 
  12. } while(i < 5); 
  13.   
  14. // print the size of the array 
  15. printf("sizeof(numbers) = %lu\n", sizeof(numbers)); 

運行將得到如下的輸出:

  1. numbers = 0x7fff0815c0e0 
  2. numbers[0] = 0x7fff0815c0e0 
  3. numbers[1] = 0x7fff0815c0e4 
  4. numbers[2] = 0x7fff0815c0e8 
  5. numbers[3] = 0x7fff0815c0ec 
  6. numbers[4] = 0x7fff0815c0f0 
  7. sizeof(numbers) = 20 

在這個例子中,我們初始化了一個含有 5 個 int 元素的數組,我們打印了數組本身的地址,注意我們沒有使用地址操作符 & 。這是因為數組變量已經代表了數組首元素的地址。你會看到數組的地址與數組首元素的地址是一樣的。然後我們遍歷這個數組並打印每個元素的內存地址。在我們 的計算機中 int 是四個字節的,數組內存是連續的,因此每個int型元素地址之間相差4。

在最後一行,我們打印了數組的大小,數組的大小等於sizeof(type)乘上數組元素的數量。這裡的數組有5個int型變量,每一個占用4字節,因此整個數組大小為20字節。

結構體地址

在C語言中,結構體一般是連續的內存區域,但也不一定是絕對連續的區域。和數組類似,它們能存儲多種數據類型,但不同於數組的是,它們能存儲不同的數據類型。

  1. struct measure { 
  2.   char category; 
  3.   int width; 
  4.   int height; 
  5. }; 
  6.   
  7. // declare and populate the struct 
  8. struct measure ball; 
  9. ball.category = 'C'; 
  10. ball.width = 5; 
  11. ball.height = 3; 
  12.   
  13. // print the addresses of the struct and its members 
  14. printf("address of ball = %p\n", (void *)(&ball)); 
  15. printf("address of ball.category = %p\n", (void *)(&ball.category)); 
  16. printf("address of ball.width = %p\n", (void *)(&ball.width)); 
  17. printf("address of ball.height = %p\n", (void *)(&ball.height)); 
  18.   
  19. // print the size of the struct 
  20. printf("sizeof(ball) = %lu\n", sizeof(ball)); 

運行後的輸出結果如下:

  1. address of ball = 0x7fffd1510060 
  2. address of ball.category = 0x7fffd1510060 
  3. address of ball.width = 0x7fffd1510064 
  4. address of ball.height = 0x7fffd1510068 
  5. sizeof(ball) = 12 

在這個例子中我們定義了一個結構體measure,然後聲明了該結構體的一個實例ball,我們賦值給它的width、height以及 category成員,然後打印出ball的地址。與數組類似,結構體也代表了它首元素的地址。然後打印了它每一個成員的地址。category是第一個 成員,它與ball具有相同的地址。width後面是height,它們都具有比category更高的地址。

你可能會想因為category是一個字符,而字符型變量占用1字節,因此width的地址應該比開始出高1個字節。從輸出來看這不對。 根據C99標准§6.7.2.1),為邊界對齊,結構體可以給成員增加填充字節。它不會記錄數據成員,但會增加額外的字節。在實際中,大多數的編譯器會 使結構體中的每個成員與結構體最大的成員有相同大小,

在我們的例子中,你可以看到char實際上占用4字節,整個struct占用12個字節。都發生了什麼?

1.struct變量指向struct首元素的地址

2.不要假設struct的成員是與其它區域分離的大量特殊的字節,它們也許有邊界字節或者內存並不連續。使用地址操作符&來獲得成員的地址

3.使用sizeof(struct instance)來獲得struct的總大小,不用假設它是各個成員域的大小總和,也許還有補充。

結論

喜歡這篇博文可以幫你理解更多的在C中如何操作不同的數據類型的地址。在以後的博文中,我們將會繼續研究一下指針和數組的基礎。

譯文鏈接:http://blog.jobbole.com/44845/

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved