注:本文為 “數組存儲 · 行主序與列主序” 相關合輯。
英文引文,機翻未校。
中文引文,略作重排。
未整理去重,如有內容異常,請看原文。
Row major and Column Major Address calculations
按行主序和按列主序的地址計算
Kolli Rohit Reddy
May 3, 2012
In this tutorial, we are going to learn about how to store elements in a two-dimensional array.
在本教程中,我們將學習如何在二維數組中存儲元素。
Before starting storing elements of a multi-dimensional array, let’s say I’ve an one dimensional array having elements like this:
在開始存儲多維數組的元素之前,假設我有一個一維數組,其元素如下:
A1, A2, A3….An
These elements are stored in my linear memory space as follows:
這些元素在內存空間中的存儲方式如下:
A1 A2 A3 A4 ... An
One thing you need to remember is, no matter what type of array it is (1D or multi-dimensional), they will be always stored linearly in the memory space as above.
需要記住的一點是,無論數組是何種類型(一維或多維),它們始終會像上面那樣線性地存儲在內存空間中。
Let’s jump to the case of multi-dimensional arrays now. Imagine I’ve a 3x3 matrix like this:
現在我們來看多維數組的情況。假設我有一個 3×3 的矩陣,如下所示:
A11 A12 A13
A21 A22 A23
A31 A32 A33
The real problem of storing elements arises here, since elements have to be stored linearly in the memory space, we have many possible ways to store them. Here are some of the possibilities I could’ve had for the matrix above.
存儲元素的真正問題就在這里,因為元素必須線性地存儲在內存空間中,所以有多種可能的存儲方式。以下是我對上述矩陣可能采用的一些存儲方式。
1. A11 A13 A12 A21 A23 A22 A31 A33 A32 A33
2. A11 A22 A33 A12 A32 A13 A23 A31 A32 A21...
and I could go on filling randomly depending on the no. of elements I’ve in my 2-D array.
我還可以根據二維數組中的元素數量隨機填充。
Out of all these possible ways, there are two main ways of storing them, they are:
在所有這些可能的方式中,有兩種主要的存儲方式,分別是:
-
Row Major Ordering
按行主序存儲 -
Column Major Ordering
按列主序存儲
1. Row Major method
1. 按行主序存儲方法
In this method, the elements of an array are filled up row-by-row such that the first row elements are stored first, then the second row and so on. Most of the high level programming languages like C/C++, Java, Pascal, etc use this method for storing multidimensional arrays.
在該方法中,數組的元素按行填充,即先存儲第一行的元素,然后是第二行的元素,依此類推。大多數高級編程語言,如 C/C++、Java、Pascal 等,都使用這種方法來存儲多維數組。
Offset for row major ordering
按行主序的偏移量
As you can see in the 3x3 Matrix array we have above, I want to know at what position is my element A12 located in the linear memory space. This position is called the OFFSET.
正如我們在上面的 3×3 矩陣數組中看到的,我想知道元素 A12 在線性內存空間中的位置。這個位置被稱為偏移量(OFFSET)。
One way to do it is to make up a linear diagram like the one below, count manually using 0 as the starting index and go on till the element I need. But imagine you are given an array like A[20][40], definitely you can’t go over all these elements if you wanted to know, say where A[15][20] is located.
一種方法是制作一個如下面的線性圖表,從 0 開始手動計數,直到我需要的元素。但是想象一下,如果你有一個像 A[20][40] 這樣的數組,如果你想知道 A[15][20] 的位置,你肯定不能逐個查看所有這些元素。
For this, we resort to a fairly easy mathematical method of calculating the offset.
Here is the formula to calculate the offset for row major ordering
為此,我們采用一種相對簡單的數學方法來計算偏移量。
以下是按行主序計算偏移量的公式:
2. Column Major method
2. 按列主序存儲方法
In Column Major ordering, all the elements of the first column are stored first, then the next column elements and so on till we are left with no columns in our 2-D array.
在按列主序存儲中,先存儲第一列的所有元素,然后是下一列的元素,依此類推,直到二維數組中沒有列為止。
Offset for column major method
按列主序的偏移量
Calculating address of a particular element
計算特定元素的地址
Depending on the ordering method the elements are stored in the memory, we will get different positions of an element in the linear memory and consequently a different address for each method. To calculate the address we use the following procedure:
根據元素在內存中的存儲順序,元素在線性內存中的位置會有所不同,因此每種方法的地址也會不同。我們使用以下步驟來計算地址:
Step 1: Get the offset value of the element under consideration, make sure you use the correct forumula.
步驟 1:獲取所考慮元素的偏移量,確保使用正確的公式。
Step 2: Multiply the offset with the size of the element’s datatype (Like int is of 4 bytes for 32-bit, Earlier compilers like TurboC worked on 16-bit platform and for them size of int is 2 bytes).
步驟 2:將偏移量乘以元素數據類型的大小(例如,對于 32 位系統,int 類型為 4 字節;早期的編譯器,如 TurboC,在 16 位平臺上工作,對于它們,int 類型的大小為 2 字節)。
Step 3: Add this to the base address to get the final address
步驟 3:將此值加到基地址上,得到最終地址。
Cbse
Published in L’arome
·Last published Nov 6, 2016
Written by Kolli Rohit Reddy
Not the typical 9 to 6 Guy …
多維數組存儲的兩種方式
竹影半墻 原創于 2013-11-08 12:47:30 發布
1. 數組存儲的要求
數組存儲的核心要求:連續存儲。
- 連續:數組的 nnn 個元素對應 nnn(或 n+1n+1n+1)個內存地址,且任意兩個相鄰元素的地址在內存中是連續的。
- 相鄰元素的定義:
- 對于一維數組,相鄰元素的判定無歧義,即下標絕對值差為 1 的兩個元素(如 a[0]a[0]a[0] 與 a[1]a[1]a[1]、a[2]a[2]a[2] 與 a[3]a[3]a[3])。
- 對于二維及以上數組,需通過“下標權重”判定:若將最左(或最右)下標視為“個位”、次左(或次右)下標視為“十位”、更左側(或更右側)下標視為更高位,則相鄰元素對應“下標組合所構成的數字絕對值差為 1”的兩個元素。
需明確:內存空間本身是連續的線性結構,不存在“矩陣”“立方體”等具象化形態。數組存儲時,只需記錄其基地址(首個元素的內存地址),后續元素的存儲順序由編程語言規范或編譯器底層邏輯決定——即一旦確定開發平臺(如特定語言、編譯器版本),數組的存儲方式便已固定。
對應用開發者而言,直接記錄數組存儲方式的場景較少(除非開發編譯器中負責數組存儲的模塊),但探究該過程可鍛煉邏輯思維,積累“將復雜結構轉化為線性存儲”的方法經驗。
2. 數組存儲的方式
數組既是構造型數據類型,也是一種基礎數據結構,其存儲方式需滿足“連續存儲”的核心要求。對于一維、二維數組,教材中常以“行優先”“列優先”描述存儲方式,因二者可對應簡單的具象模型(如二維數組對應二維矩陣),分析過程較直觀,此處不再展開筆記。
(1)多維數組左下標優先的存儲方式(n>2n > 2n>2)
“左下標優先存儲”的邏輯與教材中二維數組的“行優先存儲”一致。需說明:對 n>2n>2n>2 的多維數組,“行”“列”的具象概念已失效,更恰當的理解是將其抽象為“多層嵌套的低維數組”,再按左下標優先級依次存儲。
存儲過程
以多維數組 a[s1][s2]…[sn]a[s_1][s_2] \ldots [s_n]a[s1?][s2?]…[sn?](n>2n > 2n>2)為例,左下標優先的存儲過程遵循“從左到右、逐層拆解”的規則,具體如下:
- 先順序存儲最左側下標對應的 (n?1)(n-1)(n?1) 維數組:即先存 a[0]a[0]a[0](一個 (n?1)(n-1)(n?1) 維數組)、再存 a[1]a[1]a[1]、……、最后存 a[s1?1]a[s_1-1]a[s1??1],直至最左側下標 s1s_1s1? 遍歷完畢。
- 存儲每個 (n?1)(n-1)(n?1) 維數組時,仍按左下標優先規則拆解為 (n?2)(n-2)(n?2) 維數組:例如存儲 a[i]a[i]a[i](0≤i<s10 \leq i < s_10≤i<s1?)時,需先存 a[i][0]a[i][0]a[i][0](一個 (n?2)(n-2)(n?2) 維數組)、再存 a[i][1]a[i][1]a[i][1]、……、最后存 a[i][s2?1]a[i][s_2-1]a[i][s2??1],直至次左側下標 s2s_2s2? 遍歷完畢。
- 重復上述拆解邏輯,直至拆解為一維數組:存儲任意一個一維數組時,按最右側下標的順序遍歷,即 a[i][i2]…[in?1][0]a[i][i_2] \ldots [i_{n-1}][0]a[i][i2?]…[in?1?][0]、a[i][i2]…[in?1][1]a[i][i_2] \ldots [i_{n-1}][1]a[i][i2?]…[in?1?][1]、……、a[i][i2]…[in?1][sn?1]a[i][i_2] \ldots [i_{n-1}][s_n-1]a[i][i2?]…[in?1?][sn??1](其中 0≤i<s10 \leq i < s_10≤i<s1?,0≤i2<s20 \leq i_2 < s_20≤i2?<s2?,……,0≤in?1<sn?10 \leq i_{n-1} < s_{n-1}0≤in?1?<sn?1?)。
- 當所有維度的下標均遍歷完畢,整個 nnn 維數組存儲完成。
采用的數學邏輯
多維數組在左下標優先方式下的存儲,本質遵循如下數學規則:在對第 m+1m+1m+1 個下標 imi_mim?(imi_mim? 遞增 1)進行計數前,需先將其右側所有下標(從 im+1i_{m+1}im+1? 到 sm+1s_{m+1}sm+1?)完全遍歷(計滿)(其中 0≤m<n?10 \leq m < n-10≤m<n?1,imi_mim? 代表數組的第 m+1m+1m+1 個下標值,sm+1s_{m+1}sm+1? 代表該下標的最大取值范圍)。
計算 a[i1][i2]…[in]a[i_1][i_2] \ldots [i_n]a[i1?][i2?]…[in?] 的地址
教材中頻繁涉及“多維數組元素地址計算”,其核心邏輯是:求目標元素 a[i1][i2]…[in]a[i_1][i_2] \ldots [i_n]a[i1?][i2?]…[in?] 之前的元素總數——即統計在 [i1][i2]…[in][i_1][i_2] \ldots [i_n][i1?][i2?]…[in?] 這組下標之前,已存儲的元素個數(再結合“基地址 + 單個元素字節數 × 前序元素總數”,即可得到目標元素地址)。
分析過程
- 下標 iki_kik? 的權重:對于第 kkk 個下標 iki_kik?,其每增加 1,需跳過“右側所有維度的元素總數”,即 iki_kik? 對應的元素數量為 ik×(Sk+1×Sk+2×…×Sn)i_k \times (S_{k+1} \times S_{k+2} \times \ldots \times S_n)ik?×(Sk+1?×Sk+2?×…×Sn?)(其中 StS_tSt? 代表第 ttt 個維度的長度,即 sts_tst?)。
- 前序元素的累加邏輯:當下標 iki_kik? 固定時,其右側的下標 ik+1,ik+2,…,ini_{k+1}, i_{k+2}, \ldots, i_nik+1?,ik+2?,…,in? 仍需從 0 遍歷至當前值,因此需將這些下標的對應元素數量依次累加。
- 下標 iki_kik? 之前的元素總數:綜合上述邏輯,下標 iki_kik? 之前已存儲的元素個數為:
ik×Sk+1×Sk+2×…×Sn+ik+1×Sk+2×…×Sn+…+in?1×Sn+ini_k \times S_{k+1} \times S_{k+2} \times \ldots \times S_n + i_{k+1} \times S_{k+2} \times \ldots \times S_n + \ldots + i_{n-1} \times S_n + i_n ik?×Sk+1?×Sk+2?×…×Sn?+ik+1?×Sk+2?×…×Sn?+…+in?1?×Sn?+in? - 目標元素 a[i1][i2]…[in]a[i_1][i_2] \ldots [i_n]a[i1?][i2?]…[in?] 之前的總元素數:將上述邏輯推廣到完整下標,目標元素前的總元素數公式為:
i1×S2×S3×…×Sn+i2×S3×…×Sn+…+in?1×Sn+in=∑j=1n(ij×∏i=j+1nSi)i_1 \times S_2 \times S_3 \times \ldots \times S_n + i_2 \times S_3 \times \ldots \times S_n + \ldots + i_{n-1} \times S_n + i_n = \sum_{j=1}^{n} \left( i_j \times \prod_{i=j+1}^{n} S_i \right) i1?×S2?×S3?×…×Sn?+i2?×S3?×…×Sn?+…+in?1?×Sn?+in?=j=1∑n?(ij?×i=j+1∏n?Si?)
若難以通過空間想象理解多維數組的“下標-存儲”關系,可通過上述數學公式直接推導。需強調:數組存儲本身僅依賴該數學規則,不存在天然的“行”“列”概念;“行-列對應”是為解決具象數學問題(如矩陣運算)而定義的輔助邏輯——例如用二維數組存儲矩陣時,可將矩陣的“行號”“列號”對應到二維數組的兩個下標,從而通過下標分析矩陣運算規則。
(2)多維數組右下標優先的存儲方式(n>2n > 2n>2)
與左下標優先相反,右下標優先存儲的核心數學規則為:在對第 m+1m+1m+1 個下標 imi_mim?(imi_mim? 遞增 1)進行計數前,需先將其左側所有下標(從 im?1i_{m-1}im?1? 到 sm?1s_{m-1}sm?1?)完全遍歷(計滿)(其中 0<m≤n?10 < m \leq n-10<m≤n?1,imi_mim? 代表數組的第 m+1m+1m+1 個下標值,sm?1s_{m-1}sm?1? 代表左側下標的最大取值范圍)。
計算 a[i1][i2]…[in]a[i_1][i_2] \ldots [i_n]a[i1?][i2?]…[in?] 前的元素個數
遵循“右側下標權重更高”的邏輯,目標元素 a[i1][i2]…[in]a[i_1][i_2] \ldots [i_n]a[i1?][i2?]…[in?] 之前的總元素數公式為:
in×Sn?1×Sn?2×…×S1+in?1×Sn?2×…×S1+…+i2×S1+i1=∑j=1n(ij×∏i=1j?1Si)i_n \times S_{n-1} \times S_{n-2} \times \ldots \times S_1 + i_{n-1} \times S_{n-2} \times \ldots \times S_1 + \ldots + i_2 \times S_1 + i_1 = \sum_{j=1}^{n} \left( i_j \times \prod_{i=1}^{j-1} S_i \right) in?×Sn?1?×Sn?2?×…×S1?+in?1?×Sn?2?×…×S1?+…+i2?×S1?+i1?=j=1∑n?(ij?×i=1∏j?1?Si?)
(注:當 j=1j=1j=1 時,乘積項 ∏i=10Si\prod_{i=1}^{0} S_i∏i=10?Si? 為空乘積,按數學慣例取值為 1,此時該項簡化為 i1×1=i1i_1 \times 1 = i_1i1?×1=i1?,與公式左側首尾項一致。)
3. 總結
為滿足“連續存儲”的核心要求,多維數組的存儲方式本質僅兩種,其差異體現在“下標優先級”:
- 方式一:從最左側下標開始,按“左→右”的順序依次遍歷存儲(左下標優先);
- 方式二:從最右側下標開始,按“右→左”的順序依次遍歷存儲(右下標優先)。
需重點注意:
- “行”“列”是二維數組的專屬具象描述,對 n>2n>2n>2 的多維數組不適用;
- “左下標/右下標優先”是更通用的規則,無需依賴空間想象,可通過統一的數學公式(前序元素個數計算)推導存儲邏輯,且該公式可直接用于元素地址的計算。
Row- and Column- major order(行優先和列優先順序)
原創于 2018-04-28 11:18:33 發布
1. 概念的核心定義與維度推廣
行優先(Row-major order)和列優先(Column-major order)的術語雖起源于二維數組(矩陣)的“行”與“列”存儲邏輯,但并非局限于二維場景,可自然推廣到 任意維度的數組,核心差異體現在“索引變化的快慢順序”上:
1.1 行優先(Row-major order)的維度規則
在行優先存儲中,數組各維度的索引變化存在明確的“快慢層級”:
-
核心邏輯:沿著數組 最后一個軸(維度)的索引變化最快,沿著 第一個軸(維度)的索引變化最慢。
-
二維場景驗證:以二維數組
a[行][列]
(第一軸為“行”,第二軸為“列”)為例,行索引(第一軸)變化最慢(需先存完一行所有元素),列索引(第二軸,最后一個軸)變化最快(同一行內依次存不同列元素),即存儲順序為a[0][0]→a[0][1]→a[0][2]→…→a[1][0]→a[1][1]→…
,與“先存行、再換行”的直覺一致。 -
三維場景推廣:以三維數組
a[深度][行][列]
(第一軸為“深度”,第二軸為“行”,第三軸為“列”)為例,索引變化速度從慢到快依次為:深度索引(第一軸)→ 行索引(第二軸)→ 列索引(第三軸,最后一個軸)。
存儲順序為a[0][0][0]→a[0][0][1]→a[0][0][2]→…→a[0][1][0]→a[0][1][1]→…→a[0][2][0]→…→a[1][0][0]→…
,即“先存完一個深度下的所有行與列,再切換到下一個深度”。
1.2 列優先(Column-major order)的維度規則
列優先存儲的索引變化順序與行優先完全相反:
-
核心邏輯:沿著數組 第一個軸(維度)的索引變化最快,沿著 最后一個軸(維度)的索引變化最慢。
-
二維場景驗證:仍以二維數組
a[行][列]
為例,行索引(第一軸)變化最快(需先存完一列所有行元素),列索引(第二軸,最后一個軸)變化最慢(同一列內依次存不同行元素),存儲順序為a[0][0]→a[1][0]→a[2][0]→…→a[0][1]→a[1][1]→…
,即“先存完一列、再換列”。 -
三維場景推廣:以三維數組
a[深度][行][列]
為例,索引變化速度從慢到快依次為:列索引(第三軸,最后一個軸)→ 行索引(第二軸)→ 深度索引(第一軸)。
存儲順序為a[0][0][0]→a[1][0][0]→a[2][0][0]→…→a[0][1][0]→a[1][1][0]→…→a[0][0][1]→a[1][0][1]→…
,即“先存完一個列下的所有深度與行,再切換到下一個列”。
2. 主流編程語言的存儲順序差異
支持多維數組的編程語言或其標準庫,會根據設計目標(如通用開發、科學計算)選擇默認的存儲順序,這一選擇直接影響數組的訪問效率與代碼邏輯:
存儲順序 | 代表編程語言/工具 | 適用場景說明 |
---|---|---|
行優先(Row-major) | C / C++(C風格數組)、Java、Python(列表嵌套)、JavaScript、Go | 適用于通用軟件開發場景,符合人類“從左到右、從上到下”的讀寫習慣,按行遍歷數組時能最大化利用CPU緩存,減少緩存未命中 |
列優先(Column-major) | Fortran、MATLAB、R、Julia(默認) | 適用于科學計算、數值分析、統計分析場景,這類場景常需頻繁操作數組的“列向量”(如矩陣運算、特征工程),列優先存儲可讓列內元素連續,提升計算效率 |
注意:部分編程語言支持靈活切換存儲順序,例如 Julia 可通過關鍵字指定數組為行優先(Array{Type, N, RowMajor}
)或列優先(默認);Python 的 numpy
庫也允許通過 order='C'
(C 風格,行優先)或 order='F'
(Fortran 風格,列優先)創建數組,以適配不同計算需求。
行主序、列主序概念辨析以及基地址的含義
天射手座 原創于 2019-01-19 23:05:18 發布
1. 行主序(Row-Major Order)
在數組中,數據按照 a[0][0]、a[0][1]、a[0][2]…a[1][0]、a[1][1]、a[1][2]…的順序依次存儲。
這種存儲方式的核心是“先存完一行,再存下一行”,即優先填充同一行內的所有元素,行索引變化滯后于列索引變化。
例如,對于一個 2×3 的二維數組 a[2][3]
,行主序的存儲順序為:
a[0][0] → a[0][1] → a[0][2] → a[1][0] → a[1][1] → a[1][2]
常見于 C、C++、Java 等主流編程語言。
2. 列主序(Column-Major Order)
在數組中,數據按照 a[0][0]、a[1][0]、a[2][0]…a[0][1]、a[1][1]、a[2][1]…的順序依次存儲。
這種存儲方式的核心是“先存完一列,再存下一列”,即優先填充同一列內的所有元素,列索引變化滯后于行索引變化。
例如,對于同樣的 2×3 二維數組 a[2][3]
,列主序的存儲順序為:
a[0][0] → a[1][0] → a[0][1] → a[1][1] → a[0][2] → a[1][2]
常見于 Fortran、MATLAB 等編程語言。
3. 基地址(Base Address)
基地址指的是數組 首元素的內存地址,也就是數組在計算機內存中的起始存儲地址。
在內存中,數組元素按連續地址依次排列,基地址是計算數組中任意元素內存地址的“基準點”。
以行主序存儲的二維數組 a[m][n]
為例,若基地址為 base
,每個元素占用 size
字節內存,則元素 a[i][j]
的內存地址計算公式為:
地址 = base + (i × n + j) × size
(其中 i
為行索引,j
為列索引,m
為總行數,n
為總列數)
矩陣中“行優先”和“列優先”
paridas101 原創于 2020-01-29 21:26:17 發布
1. 行優先和列優先
顧名思義,在表示矩陣時,“行優先”以“行”為核心組織邏輯,“列優先”以“列”為核心組織邏輯,二者的核心差異體現在矩陣維度的解讀與數據存儲的對應關系上:
-
維度表示差異:對于一個
m × n
的矩陣(數學中標準記法為“m
行n
列”): -
行優先:直接對應“
m
行n
列”的物理存儲,即先存儲完第一行的所有元素,再存儲第二行,以此類推; -
列優先:需將維度解讀為“
m
列n
行”的物理存儲,即先存儲完第一列的所有元素,再存儲第二列,以此類推。 -
平移矩陣實例(三維向量平移):
若三維空間中的向量p
分別沿x
、y
、z
方向平移a
、b
、c
距離,其行優先與列優先的平移矩陣(齊次坐標下,4×4 矩陣)存在明確區別,且二者互為轉置: -
行優先平移矩陣:
[100001000010abc1]\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ a & b & c & 1 \end{bmatrix}?100a?010b?001c?0001?? -
列優先平移矩陣:
[100a010b001c0001]\begin{bmatrix} 1 & 0 & 0 & a \\ 0 & 1 & 0 & b \\ 0 & 0 & 1 & c \\ 0 & 0 & 0 & 1 \end{bmatrix}?1000?0100?0010?abc1??
2. 行優先與列優先:矩陣與向量的乘法形式
在通過“矩陣 × 向量”實現變換時,行優先與列優先的乘法順序(左乘/右乘)完全不同,需匹配向量的表示形式(行向量/列向量):
2.1 行優先:行向量右乘矩陣
行優先場景下,向量需表示為行向量(1×4 矩陣),且需放在矩陣左側,通過“行向量右乘矩陣”完成變換。
以向量 p(x, y, z, 1)
(齊次坐標,行向量形式為 (x, y, z, 1)
)的平移為例:
(x,y,z,1)×[100001000010abc1]=(x+a,y+b,z+c,1)(x, y, z, 1) \times \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ a & b & c & 1 \end{bmatrix} = (x+a, y+b, z+c, 1)(x,y,z,1)×?100a?010b?001c?0001??=(x+a,y+b,z+c,1)
(注:原文中行向量最后一位為“0”,修正為“1”,齊次坐標下點向量的最后一位應為“1”,確保平移計算正確)
2.2 列優先:列向量左乘矩陣
列優先場景下,向量需表示為列向量(4×1 矩陣),且需放在矩陣右側,通過“矩陣左乘列向量”完成變換。
同樣以向量 p(x, y, z, 1)
(列向量形式為 \begin{pmatrix} x \\ y \\ z \\ 1 \end{pmatrix}
)的平移為例:
[100a010b001c0001]×(xyz1)=(x+ay+bz+c1)\begin{bmatrix} 1 & 0 & 0 & a \\ 0 & 1 & 0 & b \\ 0 & 0 & 1 & c \\ 0 & 0 & 0 & 1 \end{bmatrix} \times \begin{pmatrix} x \\ y \\ z \\ 1 \end{pmatrix} = \begin{pmatrix} x+a \\ y+b \\ z+c \\ 1 \end{pmatrix}?1000?0100?0010?abc1??×?xyz1??=?x+ay+bz+c1??
(注:原文中列向量最后一位為“0”,修正為“1”,原因同前,確保平移邏輯正確)
3. 行優先與列優先在 C++ 代碼中的表示
在 C++ 中,二維數組(如 float mat[4][4]
)的物理存儲默認遵循行主序,但這并不影響其同時承載“行優先”或“列優先”的矩陣邏輯——二者的代碼寫法完全一致,差異僅體現在“數組元素的邏輯解讀”上:
3.1 代碼形式與邏輯映射
以 4×4 平移矩陣為例,C++ 中統一表示為:
float mat[4][4] = {{1, 0, 0, 0}, // 第 0 行(數組索引從 0 開始,原文“第 1 行”修正為“第 0 行”){0, 1, 0, 0}, // 第 1 行{0, 0, 1, 0}, // 第 2 行{a, b, c, 1} // 第 3 行
};
- 若按行優先解讀:數組的每一行直接對應矩陣的每一行。例如
mat[3]
(第 3 行)對應行優先矩陣的平移分量行(a, b, c, 1)
; - 若按列優先解讀:數組的每一行對應矩陣的每一列。例如
mat[3]
(第 3 行)對應列優先矩陣的平移分量列\begin{pmatrix} a \\ b \\ c \\ 1 \end{pmatrix}
。
3.2 核心結論
C++ 代碼的數組存儲形式(行主序)是“物理層面”的固定規則,而“行優先”“列優先”是“邏輯層面”的矩陣解讀方式——二者不沖突,僅需在后續計算(如矩陣與向量相乘)時,匹配對應的乘法順序(行向量右乘/列向量左乘)即可。
4. 行優先與列優先的典型應用:DirectX 與 OpenGL 的差異
在圖形學領域,DirectX(DX)與 OpenGL 對矩陣的處理差異,本質是“行優先”與“列優先”的工程實踐體現,核心區別體現在內存布局與著色器處理邏輯的匹配上:
圖形 API | C++ 內存布局(CPU 端) | 著色器(GPU 端)默認向量類型 | 數據傳遞是否需轉置 | 核心原因 |
---|---|---|---|---|
DirectX | 行優先(遵循 C++ 行主序) | HLSL 默認使用列向量 | 是(需轉置) | CPU 端行優先矩陣 → GPU 端列向量需左乘,因此需將行優先矩陣轉置為列優先矩陣,確保變換邏輯正確 |
OpenGL | 列優先(需按列優先組織) | GLSL 默認使用列向量 | 否(無需轉置) | CPU 端直接按列優先組織矩陣 → 與 GPU 端列向量左乘邏輯完全匹配,無需額外轉置操作 |
說明
- DirectX 的“轉置”并非改變 C++ 數組的存儲,而是在傳遞給 HLSL 前,通過 API(如
XMMatrixTranspose
)將行優先矩陣的邏輯結構轉置為列優先,以適配 HLSL 的列向量計算; - OpenGL 要求 CPU 端直接按“列優先”邏輯組織數組(例如列優先矩陣的第一列存在數組的第一行),因此無需轉置即可直接與 GLSL 的列向量匹配。
矩陣存儲順序:Row-Major 與 Column-Major 在 DirectX 中的應用與差異
在圖形編程領域,矩陣作為描述坐標變換(如平移、旋轉、縮放)的核心數據結構,其存儲順序(即內存中元素的排列方式)直接影響數據傳輸效率與 GPU 計算正確性。DirectX(簡稱 DX)作為主流圖形開發接口,其矩陣存儲與處理邏輯需結合 Row-Major(行主序) 和 Column-Major(列主序) 的特性綜合理解。本章將圍繞 DirectX 環境下兩種存儲順序的定義、實際應用差異及接口適配規則展開,為開發者提供清晰的理論與實踐指引。
1. 核心概念:Row-Major 與 Column-Major 的定義
在深入 DirectX 具體邏輯前,需先明確兩種存儲順序的本質區別——二者的核心差異在于矩陣元素在一維內存中的排列優先級,即優先按“行”存儲還是按“列”存儲。
以 3×3 矩陣 M=[a11a12a13a21a22a23a31a32a33]M = \begin{bmatrix} a_{11} & a_{12} & a_{13} \\ a_{21} & a_{22} & a_{23} \\ a_{31} & a_{32} & a_{33} \end{bmatrix}M=?a11?a21?a31??a12?a22?a32??a13?a23?a33??? 為例:
- Row-Major(行主序):優先按“行”連續存儲,內存布局為 [a11,a12,a13,a21,a22,a23,a31,a32,a33][a_{11}, a_{12}, a_{13}, a_{21}, a_{22}, a_{23}, a_{31}, a_{32}, a_{33}][a11?,a12?,a13?,a21?,a22?,a23?,a31?,a32?,a33?]。即先完整存儲第 1 行,再存儲第 2 行,以此類推。
- Column-Major(列主序):優先按“列”連續存儲,內存布局為 [a11,a21,a31,a12,a22,a32,a13,a23,a33][a_{11}, a_{21}, a_{31}, a_{12}, a_{22}, a_{32}, a_{13}, a_{23}, a_{33}][a11?,a21?,a31?,a12?,a22?,a32?,a13?,a23?,a33?]。即先完整存儲第 1 列,再存儲第 2 列,以此類推。
需注意:存儲順序僅定義“內存排列方式”,不改變矩陣的數學意義(如矩陣的秩、逆矩陣等屬性),但會直接影響 CPU 與 GPU 之間的數據交互邏輯——若存儲順序與計算端(如 GPU)的默認處理順序不匹配,可能導致變換結果錯誤。
2. DirectX 中的矩陣存儲與數學意義:Row-Major 存儲,Column-Major 計算
DirectX 環境下的矩陣邏輯存在“存儲形式”與“數學處理”的分離,這是開發者易混淆的核心點,需從兩個維度拆解:
2.1 存儲維度:DirectX 矩陣的內存布局為 Row-Major
根據 DirectX 官方設計規范及實踐反饋(如 2008 年 Sherk 的總結),DirectX(尤其是早期的 D3DX 矩陣庫)中,矩陣在 CPU 內存中的存儲嚴格遵循 Row-Major 行主序。例如,通過 D3DX 矩陣構造函數(如 D3DXMatrixRotationY
)生成的旋轉矩陣、D3DXMatrixLookAtLH
生成的觀察矩陣,其內存中元素均按“行優先”排列。
這一設計的核心目的是適配 CPU 端的緩存訪問邏輯——CPU 緩存通常以“行”為單位預取數據,Row-Major 存儲可減少緩存缺失,提升 CPU 對矩陣的計算(如矩陣乘法)效率。
2.2 數學維度:DirectX 矩陣的計算邏輯為 Column-Major
盡管 DirectX 矩陣在內存中按 Row-Major 存儲,但其數學意義上遵循 Column-Major 列主序的變換邏輯(Sherk,2008)。具體表現為:
- DirectX 中的矩陣與向量的乘法默認采用“向量右乘矩陣”(即 向量×矩陣\text{向量} \times \text{矩陣}向量×矩陣),這是 Column-Major 矩陣的典型計算方式(Row-Major 矩陣通常采用“矩陣左乘向量”)。
- 當矩陣通過接口上傳至 GPU 后,默認的著色器(Shader)代碼會以 Column-Major 方式解析矩陣元素——即 GPU 會將內存中的矩陣數據“按列讀取”,每一列對應一個寄存器(Register),用于后續的頂點變換或像素計算。
這種“存儲(Row-Major)與計算(Column-Major)分離”的設計,是 DirectX 為平衡 CPU 緩存效率與 GPU 計算習慣而做的妥協,也是開發者需重點關注的適配點。
3. DirectX 接口的矩陣上傳規則:自動轉置與手動轉置的差異
當 CPU 端的 Row-Major 矩陣需上傳至 GPU(按 Column-Major 處理)時,DirectX 提供了不同的接口,其核心差異在于是否自動完成“Row-Major 到 Column-Major”的轉置。開發者需根據接口特性選擇是否手動干預,否則會導致矩陣數據解析錯誤。
3.1 支持自動轉置的接口:ID3DXConstantTable::SetMatrix
ID3DXConstantTable::SetMatrix
是 DirectX 中用于向著色器常量表(Constant Table)上傳矩陣的高頻接口,其核心特性是自動處理轉置(Rambler,2010):
- 工作原理:該接口內部會檢測矩陣的存儲順序(CPU 端的 Row-Major)與 GPU 處理順序(Column-Major)的差異,自動將 Row-Major 矩陣轉置為 Column-Major 格式后,再上傳至 GPU。
- 適用場景:適用于大多數常規場景(如上傳模型變換矩陣、觀察矩陣、投影矩陣),開發者無需手動編寫轉置代碼,可直接傳入 D3DX 庫生成的 Row-Major 矩陣,降低出錯概率。
3.2 不支持自動轉置的接口:SetPixelShaderConstantF
與 SetVertexShaderConstantF
SetPixelShaderConstantF
(像素著色器常量設置)和 SetVertexShaderConstantF
(頂點著色器常量設置)是更底層的常量設置接口,其設計目標是“輕量、直接”,因此不具備自動轉置功能(Rambler,2010):
- 工作原理:這兩個接口會將 CPU 端內存中的矩陣數據“原封不動”地上傳至 GPU 的著色器常量寄存器。若直接傳入 Row-Major 矩陣,GPU 會按 Column-Major 規則解析,導致矩陣列與行顛倒,最終變換結果錯誤(如模型位置偏移、旋轉方向反向)。
- 適配要求:若使用這兩個接口,開發者必須在上傳前手動調用轉置函數(如 D3DX 庫的
D3DXMatrixTranspose
),將 Row-Major 矩陣轉為 Column-Major 矩陣,確保 GPU 能正確解析數據。
4. 總結:DirectX 矩陣存儲與處理的核心適配邏輯
DirectX 中 Row-Major 與 Column-Major 的差異并非“非此即彼”,而是“存儲-計算-接口”三者協同的邏輯體系,開發者需牢記以下核心結論:
- 存儲層:CPU 端 DirectX 矩陣(如 D3DX 生成的矩陣)默認按 Row-Major 排列,適配 CPU 緩存效率。
- 計算層:GPU 端著色器默認按 Column-Major 處理矩陣,需“按列讀取”數據并執行變換計算。
- 接口層:根據接口是否支持自動轉置選擇操作——
ID3DXConstantTable::SetMatrix
自動轉置,可直接傳原矩陣;SetPixelShaderConstantF
/SetVertexShaderConstantF
需手動轉置,避免數據解析錯誤。
掌握這一邏輯體系,是避免 DirectX 圖形編程中“矩陣變換錯誤”的關鍵,也是學習復雜坐標變換(如骨骼動畫、視錐體裁剪)的基礎。
行主序與列主序的應用優勢及選擇策略
在對行主序(Row-major)和列主序(Column-major)概念與主流語言支持的基礎上,二者在實際工程中還存在更多與性能、兼容性、場景適配相關的優勢,而選擇哪種存儲順序,核心需圍繞數據訪問模式、硬件特性、工具鏈生態等維度綜合判斷。
一、行主序與列主序的應用優勢
除了 “符合通用編程直覺”(行主序)或 “適配科學計算列操作”(列主序)的基礎優勢外,二者在性能優化、跨場景兼容、特殊數據處理等方面還具備以下關鍵價值:
1. 行主序的優勢
(1)最大化 CPU 緩存命中率,降低通用場景延遲
現代 CPU 通過 “緩存(Cache)” 加速數據訪問,緩存的核心特性是 “空間局部性”—— 即最近訪問過的數據,其相鄰數據有極高概率被再次訪問。
行主序中,數組的連續內存地址對應 “同一行的相鄰元素”,而通用編程場景(如遍歷表格數據、處理字符串矩陣、實現二維數組的逐行初始化)中,數據訪問天然以 “行” 為單位(例如讀取一行用戶信息、修改一行文本內容)。這種情況下,行主序能讓 CPU 一次性將 “一整行數據” 加載到緩存中,后續訪問同一行元素時無需再次從內存讀取,大幅降低 “緩存未命中” 導致的性能損耗。
例如在 C 語言中遍歷二維數組 int a [1000][1000]
,按行訪問(for (i=0;i<1000;i++) for (j=0;j<1000;j++) a [i][j]
)的速度,通常比按列訪問快 10 倍以上,核心原因就是行主序與緩存空間局部性的匹配。
(2)兼容多數通用數據格式與工具鏈
主流通用數據格式(如 CSV、JSON、TXT 表格)的存儲邏輯天然是 “行優先”——CSV 文件中每一行對應一條記錄,JSON 數組的嵌套結構(如 [[1,2],[3,4]]
)也默認按行組織數據。
行主序存儲的數組與這些格式交互時,無需進行 “維度轉置”,可直接按 “讀一行、存一行” 的邏輯解析或生成數據,減少數據轉換的代碼復雜度與性能開銷。例如用 Python 讀取 CSV 文件到二維列表時,列表嵌套天然是行主序(外層列表對應行,內層列表對應列),直接使用無需額外處理。
(3)簡化指針與內存操作邏輯
在支持指針的語言(如 C/C++)中,行主序的內存布局更符合 “線性內存連續訪問” 的直覺。例如二維數組 a [m][n]
的行主序存儲中,第 i
行的起始地址可直接通過 a [i]
(或 *(a+i)
)獲取,而列主序中第 j
列的起始地址需要計算 a [0] + j*m
(需額外傳入行數 m
),邏輯更復雜。
這種簡化對底層內存操作(如數組拷貝、內存映射文件、硬件驅動數據交互)尤為重要,能減少代碼出錯概率,提升開發效率。
2. 列主序的優勢
(1)適配科學計算與矩陣運算的核心需求
科學計算(如數值分析、機器學習、信號處理)中,數據操作常以 “列” 為單位:例如矩陣乘法中,左矩陣的 “列” 與右矩陣的 “行” 需逐元素相乘;機器學習中,特征矩陣的每一列對應一個特征(如 “年齡”“收入”),標準化、歸一化等預處理操作需對 “列” 整體計算均值和方差。
列主序中,同一列的元素在內存中連續存儲,執行 “列操作” 時可充分利用 CPU 緩存和向量指令(如 Intel AVX、ARM NEON)—— 向量指令能一次性對連續內存中的多個數據進行并行計算,大幅提升矩陣運算、特征處理的速度。例如在 MATLAB 中執行 mean (A)
(計算每列均值),列主序存儲讓程序無需跳過行間隙查找元素,直接按連續內存塊計算,效率遠高于行主序下的同等操作。
(2)降低稀疏矩陣的存儲與訪問開銷
稀疏矩陣(矩陣中大部分元素為 0)是科學計算與工程(如有限元分析、圖論)中的常見數據結構,其存儲需避免冗余的 0 元素。列主序更適配稀疏矩陣的主流存儲格式 ——壓縮列存儲(Compressed Column Storage, CCS,也叫 CSC):
CCS 格式通過 “列指針數組” 記錄每列非零元素的起始位置,“行索引數組” 記錄非零元素的行號,“值數組” 存儲非零元素的值。由于列主序下同一列元素連續,CCS 格式可直接按列遍歷非零元素,無需額外轉換;而若使用行主序的稀疏格式(如壓縮行存儲 CRS),執行列操作時需遍歷所有行,效率更低。例如在求解大型線性方程組 Ax=b
時,基于列主序的 CCS 格式能顯著減少內存占用(比 dense 矩陣節省 90% 以上空間),并加速迭代求解器(如共軛梯度法)的計算過程。
(3)兼容硬件加速庫的底層優化
多數科學計算硬件加速庫(如 BLAS、LAPACK、CUDA Math Library)的底層實現默認基于列主序優化:
- BLAS(Basic Linear Algebra Subprograms,基礎線性代數子程序庫)是矩陣運算的標準接口,其核心函數(如矩陣乘法
dgemm
)默認假設輸入矩陣為列主序存儲,若傳入行主序矩陣需額外轉置(增加時間開銷); - GPU 加速(如 NVIDIA CUDA)中,列主序更適配 GPU 的內存架構 ——GPU 的全局內存訪問延遲高,需通過 “合并訪問”(即線程束內的線程訪問連續內存地址)降低延遲,列主序下的列操作可天然實現合并訪問,而行主序需額外調整線程索引邏輯。
二、行主序與列主序的選擇策略
選擇哪種存儲順序,本質是 “讓數據存儲邏輯與數據的訪問模式、工具鏈生態、硬件特性匹配”,核心可遵循以下 4 個步驟:
1. 優先根據 “數據的核心訪問模式” 判斷
數據訪問模式是決定存儲順序的最關鍵因素 ——“訪問頻率最高的維度,應對應存儲中變化最快的維度”(即讓高頻訪問的元素在內存中連續):
- 若核心操作是 “按行訪問”(如遍歷表格記錄、逐行處理文本、行內數據比較),選擇行主序;
示例場景:通用軟件開發(如用戶數據表格處理、日志分析)、前端二維數據渲染(如 HTML 表格、Canvas 像素矩陣)。 - 若核心操作是 “按列訪問”(如計算列均值 / 方差、矩陣乘法、特征工程、列內數據篩選),選擇列主序;
示例場景:科學計算(如有限元分析、信號濾波)、機器學習(特征矩陣預處理、神經網絡權重更新)、統計分析(R 語言數據框操作)。
2. 適配所用編程語言與工具鏈的默認規則
不同語言 / 工具鏈的默認存儲順序已綁定其生態優化,強行違背默認規則會增加代碼復雜度并損失性能:
- 若使用通用編程語言(C/C++、Java、Python、Go、JavaScript),優先選擇行主序—— 這些語言的原生數組 / 列表默認是行主序,且社區庫(如 Python 的
pandas
、Java 的ArrayList
)的 API 設計也基于行主序,無需額外轉置即可直接使用;
例外:Python 的numpy
可通過order='F'
指定列主序,但僅在調用 BLAS 庫(如numpy.linalg
)時建議使用,通用場景仍推薦order='C'
(行主序)。 - 若使用科學計算工具(MATLAB、R、Fortran、Julia),優先選擇列主序—— 這些工具的底層引擎、線性代數庫(如 MATLAB 的
linprog
、R 的lm
)均基于列主序優化,使用默認順序可避免 API 調用時的隱式轉置(例如在 R 中用matrix ()
創建矩陣,默認是列主序,若強行按行填充需指定byrow=TRUE
,但會增加內存拷貝)。
3. 結合硬件與加速庫的優化方向
若涉及高性能計算(HPC)或硬件加速(CPU 向量指令、GPU、FPGA),需匹配硬件 / 庫的底層優化邏輯:
- 若使用 CPU 向量指令(如 AVX、SSE)或通用 CPU 加速庫(如 OpenBLAS),列主序更適合矩陣運算,行主序更適合通用遍歷;
- 若使用 GPU 加速(如 CUDA、OpenCL),列主序更適配 GPU 的合并訪問機制,尤其在執行矩陣乘法、卷積等操作時,優先選擇列主序以減少內存延遲;
- 若使用稀疏矩陣庫(如 Intel MKL Sparse、SuiteSparse),列主序適配 CCS 格式,行主序適配 CRS 格式 —— 需根據稀疏矩陣的核心操作(列操作選 CCS / 列主序,行操作選 CRS / 行主序)判斷。
4. 權衡兼容性與未來擴展性
若數據需跨語言 / 工具鏈交互(如 C++ 程序生成數據供 MATLAB 分析,或 Python 讀取 Fortran 輸出的二進制文件),需提前約定存儲順序,避免數據錯亂:
- 跨語言交互時,若一方是行主序(如 C++)、另一方是列主序(如 MATLAB),需在數據傳輸后執行維度轉置(如 C++ 中用
transpose (a)
轉置矩陣,再寫入文件供 MATLAB 讀取); - 若數據未來可能擴展到更高維度(如從二維矩陣擴展到三維張量),需考慮高維下的訪問模式:例如三維張量(深度 × 行 × 列)的核心操作是 “按深度 - 行遍歷”,則行主序(深度變化最慢、列變化最快)更合適;若核心操作是 “按列 - 深度遍歷”,則列主序(列變化最慢、深度變化最快)更合適。
三、總結
行主序與列主序的優勢本質是 “存儲邏輯與場景需求的匹配”:
- 行主序:通用場景的緩存友好、工具鏈兼容、代碼簡化,適合以 “行訪問” 為核心的通用開發、數據處理;
- 列主序:科學計算的并行效率、稀疏存儲優化、硬件加速適配,適合以 “列訪問” 為核心的矩陣運算、機器學習、數值分析。
選擇時無需絕對化 —— 部分語言 / 庫(如 numpy
、Julia)支持動態指定存儲順序,可根據具體操作(如 “行遍歷用行主序,矩陣乘法用列主序”)靈活切換,在兼容性與性能間找到最優平衡。
via:
-
Row major and Column Major Address calculations | by Kolli Rohit Reddy | L’arome_
https://laro.me/row-major-and-column-major-address-calculations-99e66f4fe734 -
數組存儲方式解析-CSDN博客
https://blog.csdn.net/misskissC/article/details/14520297 -
Row- and Column- major order(行優先和列優先順序)_column major-CSDN博客
https://blog.csdn.net/u013608424/article/details/80118311 -
行主序、列主序概念辨析以及基地址的含義_以行序為主序和以列序為主序-CSDN博客
https://blog.csdn.net/tiansheshouzuo/article/details/86558120 -
矩陣中 “行優先“ 和 “列“ 優先-CSDN博客
https://blog.csdn.net/paxis0813/article/details/104108283 -
……