數組指針與二維數組的定址

来源:http://www.cnblogs.com/ggggg63/archive/2017/01/31/6359183.html
-Advertisement-
Play Games

引例:已知如下程式 試寫出程式的輸出值。(雖然我很討厭做這種筆頭功夫的題,我也堅信編程語言是在實踐中練出來的,但是這個題還是比較經典,所以還是拿來當一個例子來說明一下數組指針到底是個什麼玩意) 最初在學習C語言時,就一直為這兩個名詞所困擾。其實也怪漢語的博大精深,兩個詞交換一下位置,所表示的含義就不 ...


引例:已知如下程式

1 #include <stdio.h>
2 main()
3 { 
4     int x[3][4] = {1,3,5,7,9,11,2,4,6,8,10,12} ;
5     int (*p)[4] = x, k = 1, m, n = 0;
6     for(m=0; m < 2; m++) 
7         n += *(*(p+m)+k);
8     printf("%d\n",n);
9 }

試寫出程式的輸出值。(雖然我很討厭做這種筆頭功夫的題,我也堅信編程語言是在實踐中練出來的,但是這個題還是比較經典,所以還是拿來當一個例子來說明一下數組指針到底是個什麼玩意)

最初在學習C語言時,就一直為這兩個名詞所困擾。其實也怪漢語的博大精深,兩個詞交換一下位置,所表示的含義就不一樣了。如果直接從英文來說,指針數組叫做Array of pointers,明顯重點是array,至於是什麼樣的array呢,就是存放pointers的array。而數組指針叫做pointer of an array,重點是pointer,那麼這個pointer 指向的是什麼呢,是一個array。當然這個指向的array到底是什麼樣的,還需要方括弧的維度說明,以及前面的類型說明。

接著回到剛纔的引例,x為定義的一個二維數組,p是一個數組指針,指向一個長度為4的數組,一開始指向x的第一行(x的行是一個長度為4的int型數組)接下來一個for迴圈,依次對p+1取值,加上k(實際就是1)後再取值,並將其累加到變數n上。迴圈一共執行了2次,分別取第1行和第2行(對應第一個下角標0和1)的第一個元素(也就是x[0][1],x[1][1]),因此最後的輸出結果是3+11=14.

光從紙面上分析顯然是不夠的。GCC編譯器對上述程式產生如下的代碼

 1 0x401340    push   %ebp
 2 0x401341    mov    %esp,%ebp
 3 0x401343    and    $0xfffffff0,%esp
 4 0x401346    sub    $0x50,%esp
 5 0x401349    call   0x4019d0 <__main>
 6 0x40134e    movl   $0x1,0x10(%esp)
 7 0x401356    movl   $0x3,0x14(%esp)
 8 0x40135e    movl   $0x5,0x18(%esp)
 9 0x401366    movl   $0x7,0x1c(%esp)
10 0x40136e    movl   $0x9,0x20(%esp)
11 0x401376    movl   $0xb,0x24(%esp)
12 0x40137e    movl   $0x2,0x28(%esp)
13 0x401386    movl   $0x4,0x2c(%esp)
14 0x40138e    movl   $0x6,0x30(%esp)
15 0x401396    movl   $0x8,0x34(%esp)
16 0x40139e    movl   $0xa,0x38(%esp)
17 0x4013a6    movl   $0xc,0x3c(%esp)
18 0x4013ae    lea    0x10(%esp),%eax
19 0x4013b2    mov    %eax,0x44(%esp)
20 0x4013b6    movl   $0x1,0x40(%esp)
21 0x4013be    movl   $0x0,0x48(%esp)
22 0x4013c6    movl   $0x0,0x4c(%esp)
23 0x4013ce    jmp    0x4013f9 <main+185>
24 0x4013d0    mov    0x4c(%esp),%eax
25 0x4013d4    lea    0x0(,%eax,4),%edx
26 0x4013db    mov    0x40(%esp),%eax
27 0x4013df    add    %edx,%eax
28 0x4013e1    lea    0x0(,%eax,4),%edx
29 0x4013e8    mov    0x44(%esp),%eax
30 0x4013ec    add    %edx,%eax
31 0x4013ee    mov    (%eax),%eax
32 0x4013f0    add    %eax,0x48(%esp)
33 0x4013f4    addl   $0x1,0x4c(%esp)
34 0x4013f9    cmpl   $0x1,0x4c(%esp)
35 0x4013fe    jle    0x4013d0 <main+144>
36 0x401400    mov    0x48(%esp),%eax
37 0x401404    mov    %eax,0x4(%esp)
38 0x401408    movl   $0x403024,(%esp)
39 0x40140f    call   0x401c40 <printf>
40 0x401414    leave
41 0x401415    ret

其中第4行編譯器為局部變數(auto)在棧上分配記憶體空間0x50位元組,6~17行,編譯器為二維數組x初始化,其中,x[0][0]的地址為%esp+10。19~22行分別為p,k,m,n初始化。(從中可以看出,p初始化使用了leal指令取第一個元素的地址,且p只占用了4個位元組,也就是說,從數據大小來看,數組指針本質上還是一個指針)

現在想要研究編譯器如何對數組指針進行操作,通過jle指令可以定位到迴圈為24~35行。在原始的C語言代碼中,for迴圈的body-statement只有一句複合語句,最後的操作顯然對應累加,也就是32行的add指令(33行的addl顯然是計數器累加,因為34行用到了cmpl指令判斷大小)。32行的add指令中,%esp+48對應變數n,31行用%eax的值作為地址進行定址,將地址為%eax的值放進%eax中,顯然對應C語言語句中最外層的一個*號。30行的add指令後的%eax的值顯然便是表達式:*(p+m)+k的值。

重點在於理解編譯器如何解析這個表達式了。24行取%esp+0x4c(m的值),25行用leal指令將m*4並放入%edx寄存器中,26行取%esp+0x40(k的值)放入寄存器%eax中,27行將%eax和%edx的值相加,得到整個的偏移地址4m+k,28行將整個偏移地址乘以4得到實際的位元組偏移地址,29行再將其與數組第一個元素的地址相加,得到表達式*(p+m)+k的值了。因此,25行leal指令得到的繫數4,恰好對應定義的數組指針的長度4。如果在原題中將(*p)[4]改為(*p)[3],於是編譯器得到如下代碼(僅截取迴圈內):

 1 0x4013d0    mov    0x4c(%esp),%edx
 2 0x4013d4    mov    %edx,%eax
 3 0x4013d6    add    %eax,%eax
 4 0x4013d8    add    %eax,%edx
 5 0x4013da    mov    0x40(%esp),%eax
 6 0x4013de    add    %edx,%eax
 7 0x4013e0    lea    0x0(,%eax,4),%edx
 8 0x4013e7    mov    0x44(%esp),%eax
 9 0x4013eb    add    %edx,%eax
10 0x4013ed    mov    (%eax),%eax
11 0x4013ef    add    %eax,0x48(%esp)
12 0x4013f3    addl   $0x1,0x4c(%esp)
13 0x4013f8    cmpl   $0x1,0x4c(%esp)
14 0x4013fd    jle    0x4013d0 <main+144>

這裡編譯器使用兩條add指令計算數組長度3代替了原先的leal指令計算的數組長度4(編譯器往往會選擇合適的指令來減小開銷,比如用移位和加法指令代替常數乘法,但是會使得彙編碼和C代碼的對應不是很明顯),而後的代碼與原先如出一轍。

可以看出,數組指針指向的是一個數組,數組指針進行自增,會將實際的地址指向下一個依靠的數組。由於二維數組在記憶體中實際也是按照“行優先”的規則映射到一維的線性的數組中來存儲的,編譯器在解釋數組指針的過程中,會首先計算數組指針所指向的數組的長度(定義數組指針時確定),然後根據所指向的數組的長度計算偏移地址,將其與初始化的基地址(將其與一個二級指針關聯時得到的基地址)相加,得到所指向的數組的第一個元素的地址。因此,數組指針的長度和與它相關聯的實際的二維數組的行列長度並不需要嚴格一致,只是為了使用方便,往往會將數組指針所指向的數組的長度與實際需要操作的二維數組的行長度相對應。
事實上,訪問二維數組D(定義為ElementType D[R][C])中的i行j列的元素時,通用的定址方法是

&D[i][j]=xD+L(C·i+j),其中xD為二維數組的首地址,L為數組的元素數據類型的大小,C為定義的行長度。

數組指針的定址本質上是一致的。在開頭的例題里,公式中xD=p,i=m,j=k。

 參考:深入理解電腦系統第二版,p158.3.8節 數組的分配與訪問。

 

 

 

 

 

 

 

 

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前幾天用Python的Bottle框架寫個小web程式,在進行Ajax交互之時,前端則先用 JSON.stringify 來將類序列化,然後用escape() 函數將其編碼,確保傳輸正確。 再基本上配合上Jquery的$.ajax應該就可以了,可能是經驗不足,即使編碼之後的數據依然在 Python ...
  • 很多剛剛接觸編程的人都不知道怎麼下手編寫程式,特別是學習了新的知識點,不知道有什麼用,那麼本文將以簡單的存儲結構及簡單的運算,條件語句,分支語句,迴圈語句結合,帶來一個雙人對戰版五子棋,這是一個簡單的模型,實現了五子棋最最基本的功能,還有好多地方需要補全,如邊界問題,設計問題,游戲邏輯問題,希望讀者... ...
  • MySQL是一個關係型資料庫管理系統,由瑞典MySQL AB 公司開發,目前屬於 Oracle 旗下公司。MySQL 最流行的關係型資料庫管理系統,在 WEB 應用方面MySQL是最好的 RDBMS (Relational Database Management System,關係資料庫管理系統) ...
  • /* 說明: 插入排序法由未排序的後半部分前端取出一個值,插入已排序前半部分的適當位置,概念簡單但速度不快。 排序要加快的基本原則之一,是讓後一次的排序進行時,儘量利用前一次排序後的結果,以加快排序的速度,Shell排序法即是基於此一概念來改良 插入排序法。 解法: 略 */ #include #i... ...
  • /* m元素集合的n個元素子集 說明: 假設有個集合擁有m個元素,任意的從集合中取出n個元素,則這n個元素所形成的可能子集有那些? 解法: 假設有5個元素的集點,取出3個元素的可能子集如下: {1 2 3} 、{1 2 4 } 、{1 2 5} 、{1 3 4} 、{1 3 5} 、{1 4 5} ... ...
  • /* 格雷碼 說明: Gray Code是一個數列集合 ,每個數使用二進位來表示 ,假設使用n位元來表示每個數好了 ,任兩個數之間只有一個位元值不同, 例如以下為3位元的Gray Code: 000 001 011 010 110 111 101 100 由定義可以知道,Gray Code的順序並不... ...
  • /* 後序式的運算 說明: 將中序式轉換為後序式的好處是,不用處理運運算元先後順序問題,只要依序由運算式由前往後讀取即可。 解法: 運算時由後序式的前方開始讀取,遇到運算元先存入堆疊,如果遇到運運算元,則由堆疊中取出兩個運算元進行對應的運算,然後將 結果存回堆疊,如果運算式讀取完畢,那麼堆疊頂的值就是答... ...
  • 首先,該方法是將數組轉化為list。有以下幾點需要註意: (1)該方法不適用於基本數據類型(byte,short,int,long,float,double,boolean) (2)該方法將數組與列錶鏈接起來,當更新其中之一時,另一個自動更新 (3)不支持add和remove方法 上代碼: 運行結果 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...