陣列引用:C++ 陣列做引數 深入分析
"陣列引用"以避免"陣列降階"(本文曾貼於VCKBASE\C++論壇)
受[hpho]的一段模板函式的啟發,特寫此文,如有雷同,實在遺憾。
陣列降階是個討厭的事,這在C語言中是個無法解決的問題,先看一段程式碼,瞭解什麼是"陣列降階"
#include <IOSTREAM>
using namespace std;
void Test( char array[20] )
{
cout << sizeof(array) << endl; // 輸出 4
}
int main( void )
{
char array[20] = { 0 };
cout << sizeof(array) << endl; // 輸出 20
Test( array );
}
為什麼同樣申明的array一個輸出20一個輸出4?這是因為void Test( char array[20] )中的array被降階處理了,void Test( char array[20] )等同於void Test( char array[] ),也等同於void Test( char* const array ),如果你BT(開玩笑),它也等同於void Test( char array[999] )。
就是說
void Test( char array[20] )
{
cout << sizeof(array) << endl;
}
被降成
void Test( char* const array )
{
cout << sizeof(array) << endl; // 既然是char*,當然輸出4
}
這樣一來問題大了,你完全可以定義一個不足20個元素的陣列,然後傳給Test,坐等程式崩潰。在一些要求較高的場合就不能使用陣列做引數,真TMD心有不甘。
那麼在C語言中怎樣解決這個問題?
沒辦法,應該說沒有好辦法。a:做個結構,其中僅一個char array[20],然後用這個結構指標代替char array[20]。可見這是個很繁瑣的辦法,且不直觀;b:在Test內部使用_msize來計算array長度。這更不行,首先它使得錯誤的發現被推遲到執行期,而不是編譯期,其次_msize長度/元素大小>=array長度,也就是說就是new char[19]和new array[20]分配的大小是一樣的,這樣一來,雖不至於導致程式崩潰,但運算結果卻不正確。
感謝[hpho],受其啟發,C++中有C所沒有的"引用",但陣列引用是怎樣申明的呢?經過幾番試驗,Look
#include <IOSTREAM>
using namespace std;
void Test( char (&array)[20] ) // 是不是很像 char *p[20] 和 char (*p)[20] 的區別?
{
cout << sizeof(array) << endl;
}
int main( void )
{
char array[20] = { 0 };
cout << sizeof(array) << endl;
Test( array );
}
在 C++中,陣列永遠不會按值傳遞,它是傳遞第一個元素,準確地說是第 0個 的指標。
例如,如下宣告 :
void putValues( int[ 10 ] );
被編譯器視為
void putValues( int* );
陣列的長度與引數宣告無關,因此,下列三個宣告是等價的:
// 三個等價的 putValues()宣告
void putValues( int* );
void putValues( int[] );
void putValues( int[ 10 ] );
因為陣列被傳遞為指標 所以這對程式設計師有兩個含義:
1. 在被調函式內對引數陣列的改變將被應用到陣列實參上而不是本地拷貝上,當用作實參的陣列必須保持不變時,程式設計師需要保留原始陣列的拷貝函式可以通過把引數型別宣告為 const 來表明不希望改變陣列元素。
void putValues( const int[ 10 ] );
2. 陣列長度不是引數型別的一部分,函式不知道傳遞給它的陣列的實際長度,編澤器也不知道,當編譯器對實參型別進行引數型別檢查時,並不檢查陣列的長度。例如:
void putValues( int[ 10 ] ); // 視為 int*
int main() {
int i, j[ 2 ];
putValues( &i ); // ok: &i 是 int*; 潛在的執行錯誤
putValues( j ); // ok: j 被轉換成第 0 個元素的指標
// 實參型別為 int*: 潛在的執行錯誤
return 0;
}
引數的型別檢查只能保證putValues()的兩次呼叫都提供了int*型的實參,型別檢查不能檢驗實參是一個 10元素的陣列 。習慣上, C風格字串是字元的陣列,它用一個空字元編碼作為結尾。但是所有其他型別,包括希望處理內含空字元的字元陣列必須以某種方式在向函式傳遞實參時使其知道它的長度。
一種常見的機制是提供一個含有陣列長度的額外引數。例如:
void putValues( int[], int size );
int main() {
int i, j[ 2 ];
putValues( &i, 1 );
putValues( j, 2 );
return 0;
}
另外一種機制是將引數宣告為陣列的引用
當引數是一個數組型別的引用時,陣列長度成為引數和實參型別的一部分,編譯器檢查陣列實參的長度與在函式引數型別中指定的長度是否匹配。
// 引數為 10 個 int 的陣列
// parameter is a reference to an array of 10 ints
void putValues( int (&arr)[10] );//不能寫成&arr[10],因為下標操作符的優先順序較高
int main() {
int i, j[ 2 ];
putValues( i ); // 錯誤: 實參不是 10 個 int 的陣列
putValues( j ); // 錯誤: 實參不是 10 個 int 的陣列
return 0;
}
受[hpho]的一段模板函式的啟發,特寫此文,如有雷同,實在遺憾。
陣列降階是個討厭的事,這在C語言中是個無法解決的問題,先看一段程式碼,瞭解什麼是"陣列降階"
#include <IOSTREAM>
using namespace std;
void Test( char array[20] )
{
cout << sizeof(array) << endl; // 輸出 4
}
int main( void )
{
char array[20] = { 0 };
cout << sizeof(array) << endl; // 輸出 20
Test( array );
}
為什麼同樣申明的array一個輸出20一個輸出4?這是因為void Test( char array[20] )中的array被降階處理了,void Test( char array[20] )等同於void Test( char array[] ),也等同於void Test( char* const array ),如果你BT(開玩笑),它也等同於void Test( char array[999] )。
就是說
void Test( char array[20] )
{
cout << sizeof(array) << endl;
}
被降成
void Test( char* const array )
{
cout << sizeof(array) << endl; // 既然是char*,當然輸出4
}
這樣一來問題大了,你完全可以定義一個不足20個元素的陣列,然後傳給Test,坐等程式崩潰。在一些要求較高的場合就不能使用陣列做引數,真TMD心有不甘。
那麼在C語言中怎樣解決這個問題?
沒辦法,應該說沒有好辦法。a:做個結構,其中僅一個char array[20],然後用這個結構指標代替char array[20]。可見這是個很繁瑣的辦法,且不直觀;b:在Test內部使用_msize來計算array長度。這更不行,首先它使得錯誤的發現被推遲到執行期,而不是編譯期,其次_msize長度/元素大小>=array長度,也就是說就是new char[19]和new array[20]分配的大小是一樣的,這樣一來,雖不至於導致程式崩潰,但運算結果卻不正確。
感謝[hpho],受其啟發,C++中有C所沒有的"引用",但陣列引用是怎樣申明的呢?經過幾番試驗,Look
#include <IOSTREAM>
using namespace std;
void Test( char (&array)[20] ) // 是不是很像 char *p[20] 和 char (*p)[20] 的區別?
{
cout << sizeof(array) << endl;
}
int main( void )
{
char array[20] = { 0 };
cout << sizeof(array) << endl;
Test( array );
}
去年,周星星大哥曾經在VCKBASE/C++論壇發表過一篇文章《"陣列引用"以避免"陣列降階"》*1,當時我不能深入理解這種用法的含義;時隔一年,我的知識有幾經錘鍊,終於對此文章漸有所悟,所以把吾所知作想詳細道來,竟也成了一篇文章。希望本文能對新手有所啟迪,同時也希望大家發現本文中的疏漏之處後不吝留言指教。
故事起源於周星星大哥給出的兩個Demo,為了節省地方,我把兩個Demo合二為一,也能說明同樣的問題:
#include using namespace std;void Foo1(int arr[100]){ cout << "pass by pointer: " << sizeof(arr) << endl;}void Foo2(int (&arr)[100]){ cout << "pass by reference: " << sizeof(arr) << endl;}void main(){ int a[100]; cout << "In main function : " << sizeof(a) << endl; Foo1(a); Foo2(a); }
其執行結果如下:
In main function : 400
pass by pointer: 4
pass by reference: 400
這段程式碼說明了,如果陣列形參是陣列名形式(或者指標形式,下文討論)時,使用sizeof運算子,將得不到原來陣列的長度;如果用傳遞原陣列引用的方法,則沒有問題。
這段程式碼的確很難理解,因為這短短的十幾行涉及到了形參與實參的關係、陣列名和指標的關係、引用的意義、聲名和表示式的關係這4大類問題,只要有1條理解不透、或者理解不正確,就理解不透上面的這段程式碼。本文也就從這4個問題入手,把這4個問題首先解決掉,然後再探討上面的這段程式碼。雖然這樣看來很是繁複,但是我認為從根上入手來理解、學習,是條似遠實近的道路。
一、函式形參和實參的關係
void Foo(int a);Foo(10);
這裡的a叫做形式引數(parameter),簡稱形參;這裡的10叫做實際引數(argument),簡稱實參。形參和式參之間是什麼關係呢?他們是賦值的關係,也就是說:把實參傳遞給形參的過程,可以看作是把實參賦值給形參的過程。上面的例子中,實參10傳遞給形參a,就相當於a=10;這個賦值的過程。(因為資料型別多的很,無法舉例子舉全面,所以這裡就不舉例子了;如果覺得不好理解,就在vc中寫個sample除錯一下各種資料型別的情況,你就能夠驗證這個結論了。)
二、陣列名和指標的關係
這個問題是個歷史性的問題了,在C語言中,陣列名是當作指標來處理的。更確切的說,陣列名就是指向陣列首元素地址的指標,陣列索引就是距陣列首元素地址的偏移量。理解這一點很重要,很多陣列應用的問題就是有此而起的。這也就是為什麼C語言中的陣列是從0開始計數,因為這樣它的索引就比較好對應到偏移量上。在C語言中,編譯過程中遇到有陣列名的表示式,都會把陣列名替換成指標來處理;編譯器甚至無法區分a[4]和4[a]的區別!*2但是下面這一點需要注意:
int a[100];int *b;
這兩者並不等價,第一句話聲明瞭陣列a,並定義了這個陣列,它有100個int型元素,sizeof(a)將得到整個陣列所佔的記憶體大小,是400;第二句話只是宣告並定義了一個int型的指標,sizeof(b)將得到這個指標所佔的記憶體大小,是4。所以說,雖然陣列名在表示式中一般會當作指標來處理,但是陣列名和指標還是有差距的,最起碼有a==&a[0]但是sizeof(a)!=sizeof(a[0])。
並且在ANSI C標準中,也明文規定:在函式引數的宣告中,陣列名北邊一起當作指向該陣列第一個元素的指標。所以,下面的幾種書寫形式是等效的:
void Foo1(int arr[100]){}void Foo2(int arr[]){}void Foo3(int *arr){}
C++儘可能的全面相容C語言,所以這一部分的語法相同。
三、引用的意義
“引用“是C++中引進的概念,C語言中沒有。它的目的在於,在某些方面取代指標。如果你認為引用和指標並無大不同,肯定會為指標報不平,頗有一種“即生亮何生瑜”的感慨;但是,引用確實有新的特色,也確實在很多地方的表現和指標有所不同,本文就是一例。使用引用,我們要把握這它最最最重要的一點,這也是它和指標最大的區別:引用一經定義,就和被它引用的變數緊緊地結合在一起,再不分開,對引用的任何操作都反映在它引用的變數上;而指標,只是訪問它指向變數的另一種方式,兩者雖有聯絡,但是並不像引用那樣密不可分。:)
#include using namespace std; void main(){ int a = 10; int & a_ref = a; int b = 20; //int & b_ref ; // error C2530: 'b_ref' : references must be initialized // 定義引用時就要初始化,說明引用跟它指向的元素密不可分 int & b_ref = b; int * p; int * q; //下面的結果證明了:引用一經定義,就不能再指向其他目標; //把一個引用b_ref賦值給另一個引用a_ref,其實就是把b賦值給了a. cout << a_ref << " " << b_ref << endl; a_ref = b_ref; cout << a_ref << " " << b_ref << endl; cout << a << " " << b << endl; cout << endl; //即使對一個引用a_ref取地址,取得也是a的地址。已經“惡鬼附體”了:) p = &a; q = &a_ref; cout << p << " " << q << endl; cout << endl; //下面這段程式碼展示了指標與引用的不同 p = &a; q = &b; cout << p << " "<< q << endl; p = q; cout << p << " "<< q << endl; cout << endl; system("pause");}
下面是執行的結果,以供參考:
10 20
20 20
20 20
0012FED4 0012FED4
0012FED4 0012FEBC
0012FEBC 0012FEBC
四、宣告和表示式的關係
這裡想說明的是,分析一個宣告可以把它看作一個表示式,按照表達式中的運算子優先順序順序來宣告。比如int (&arr)[100],你首先要找到宣告器arr,那麼&arr說明arr是一個引用。什麼引用呢?在看括號外面,[]說明了這一個陣列,100說明這個陣列有100個元素,前面的int說明了這個陣列的每個元素都是int型的。所以,這個宣告的意思就是:arr就是指向具有100個int型元素的陣列的引用。如果你覺得這種理解很晦澀,那你就不妨用typedef來簡化宣告中的複雜的運算子優先順序關係,比如下面的形式就很好理解,其效果是和最初的那個例子是一樣的:
#include using namespace std;typedef int INTARR[100]; //這個,這個...也可以用表示式來理解,有點“GNU is not UNIX“的味道是吧?void Foo(INTARR &arr) //noh,這樣看就很明白了,就是傳了個引用進去{ cout << "pass by reference: " << sizeof(arr) << endl;}void main(){ INTARR a; //用類型別名來定義a INTARR &a_ref=a; //用類型別名來定義引用a_ref cout << "In main function : " << sizeof(a) << endl; Foo(a); system("pause");}
===大結局===
吐沫星亂飛了半天,大家感覺還好吧,快結束了,大家再忍耐一下。看看下面這段程式:
#include using namespace std;void main(){ int a[100]; int * pa = a; int (&a_ref)[100] = a; cout << sizeof(a) << endl; cout << sizeof(pa) << endl; cout << sizeof(a_ref) << endl; system("pause");}
怎麼樣,是不是對輸出結果感到很自然呢?如果是,那就好辦了。我總結一下就下課哈!^_^ 陣列名在表示式中,往往被當作是指向首元素a[0]地址的指標,但是在sizeof(a)中,返回的結果是陣列a佔用記憶體的大小;pa是指向a的指標,他也指向a[0],但是sizeof(pa)中,返回結果是pa這個指標所佔記憶體空間的大小,之所以這樣,因為pa這個指標和陣列a的結合不夠緊密,屬於訪問陣列a的第二被選方案;a_ref這個引用,就是對陣列a的引用,就像“惡鬼附體”一樣,一旦附體附上了,你怎麼也甩不掉它,對它的任何操作,全部都反映在a上。在看本文最初的那個例子,比這個例子所增加的操作就是函式實參到形參的傳遞,我們在上面說過了,從實參到形參的傳遞可以看作是把實參賦值給形參。所以本文最初的那個例子,其實際的操作過程就和本文最後的這個例子是一樣的。所以,並非函式把陣列給“降階”了,而是它原原本本就該這樣,千萬不必奇怪。 :p
意猶未盡,在PS一段:在C語言中,沒有引用,是怎麼解決這種問題呢。下面是常用的幾種作法:
1.傳遞陣列的時候,在增加一個引數,用來記錄陣列的元素個數或者長度。main(int argc, char ** args)就是這種做法;這種方法還可以防止溢位,安全性比較高。
2.在陣列的最後一個有效元素後面作一個標誌,指明陣列已經結束了。C語言中用char陣列表示字串,傳給相關的字串函式,用的就是這種做法。這種方法保證了C的所謂字串是無限長度的,因為用一個變量表示陣列的長度的話,終歸會受到這個變數型別的限制,比方說這個變數是unsigned byte型的,那麼字串長度就不能超過256,否則這個變數就溢位了。
3.對於多維陣列,通常的方法是在最後一個有效維後面做一行標誌,比如a[3][3]={{1,0,2},{2,2,5},{-1,-1,-1}}。如果我的程式用不到-1,我可以拿-1來填充最後一行,作為標誌。這樣在函式內部檢測到某一維的元素都是-1,就說明到底了。
方法是靈活多變的,關鍵看人怎麼用了。C老爹Dennis Ritchie曾經說過:C詭異離奇,缺陷重重,卻獲得了巨大的成功。
注1:本文將不再引用“降階”這個術語,原因是我認為這個“降階”的概念有種把類似2維陣列壓扁到1維的意思,其實本文討論的並不是這個問題,本文討論的是陣列形參傳遞過程中陣列長度損失的問題(這麼說也不準確,還是看文中的討論吧)。
注2:C語言的編譯器遇到陣列元素arr[i],就會替換成*(arr+i)的形式。
int main(int argc, char* argv[]) 第一次知道C++竟然不能區分這樣的陣列元素arr1[i]和i[arr1].兩個迴圈都可以輸出陣列arr1的元素。剛知道,差點被雷倒!! |
1.變長一維陣列
這裡說的變長陣列是指在編譯時不能確定陣列長度,程式在執行時需要動態分配記憶體空間的陣列。實現變長陣列最簡單的是變長一維陣列,你可以這樣做:
//檔名: array01.cpp
#include<iostream>
using namespace std;
int main()
{
int len;
cin>>len;
//用指標p指向new動態分配的長度為len*sizeof(int)的記憶體空間
int *p=new int[len];
...........
delete[] p;
return 0;
}
注意int *p=new int[len];這一句,你不能這樣做:
int p[len];
C++編譯器會報錯說len的大小不能確定,因為用這種形式宣告陣列,陣列的大小需要在編譯時確定。而且這樣也不行:
int p[]=new int[len];
編譯器會說不能把int*型轉化為int[]型,因為用new開闢了一段記憶體空間後會返回這段記憶體的首地址,所以要把這個地址賦給一個指標,所以要用int *p=new int[len];
array01.cpp實現了一個變長的一維陣列,但是要養成一個好習慣,就是注意要登出指標p,使程式釋放用new開闢的記憶體空間。
當然使用C++標準模版庫(STL)中的vector(向量)也可以實現變長陣列:
//檔名: array02.cpp
#include<iostream>
#include<vector>
using namespace std;
int main()
{
int len;
cin>>len;
vector<int> array(len);//宣告變長陣列
for(int i=0;i<len;i++)
{
array[i]=i;
cout<<array[i]<<"\t";
}
return 0;
}
這裡的變長陣列讓我聯想到了java的java.util包中的vector和C#中的ArrayList,它們也可以在各自的語言中實現變長陣列。不過C++中的vector不能像C#一樣有託管的垃圾回收機制回收被佔用的記憶體空間,但是你可以在使用完vector後呼叫~vector()解構函式釋放記憶體。
2.變長n維陣列
變長的n維陣列實現起來有些麻煩,但是在工程與軟體設計應用中常使用的是二維陣列,所以在這裡著重介紹變長的二維陣列,變長的n維陣列可以按照類似的方法實現。首先看一個經典的用C實現變長二維陣列的例子:
//檔名: array03.c
#include <stdio.h>
#include <malloc.h>
void main()
{
int x,y,i,j;
float **a,*b;
printf("請輸入你所求解的線性方程組的行數x:x=");
scanf("%d",&x);
printf("請輸入你所求解的線性方程組的列數y:y=");
scanf("%d",&y);
a=(float **)malloc(sizeof(float *) *x);
b=(float *)malloc(sizeof(float) *x);
for(i=0;i<x;i++)
{
*(a+i)=(float *)malloc(sizeof(float) *y);
}
/*讀入資料*/
printf("請按行的順序依次輸入係數的值(共%d項):",x*y);
for(i=0;i<=x-1;i++)
for(j=0;j<=y-1;j++)
scanf("%f",&a[i][j]);
printf("請按列的順序依次輸入常數的值(共%d項):",x);
for(j=0;j<=x-1;j++)
scanf("%f",&b[j]);
printf("您輸入方程組的增廣矩陣為:\n");
for(i=0;i<=x-1;i++)
{
for(j=0;j<=y-1;j++)
printf("%.5f ",a[i][j]);
printf("%.5f ",b[i]);
printf("\n");
}
free(b);
for(i=0;i<x;i++)
free (*(a+i));
}
那麼用C++怎樣實現呢?在C++中可以通過new和delete運算子動態開闢和釋放空間,其中new與C中malloc函式的功能相似,delete與C中free函式的功能相似。用C++實現變長二維陣列時可以採用兩種方法:雙指標方法和使用STL中vector(向量)的方法。
首先介紹一下雙指標方法,在這裡雙指標就是指像指標的指標,比如你可以這樣宣告一個數組:
int **p = new int*[num1];
而對每一個*p(一共num1個*p)申請一組記憶體空間:
for(int i=0; i<num1; ++i)
p[i] = new int[num2];
其中,num1是行數,num2是陣列的列數。測試的源程式如下:
//檔名: array04.cpp
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
int num1,//行數
num2;//列數
cout<<"Please enter the number for row and column: "<<endl;
cin >> num1 >> num2;
//為二維陣列開闢空間
int **p = new int*[num1];
for(int i=0; i<num1; ++i)
p[i] = new int[num2];
for(int j=0;j<num1;j++)
{
for(int k=0;k<num2;k++)
{
p[j][k]=(j+1)*(k+1);
cout<<setw(6)<<p[j][k]<<':'<<setw(8)<<&p[j][k];
}
cout<<endl;
}
//釋放二維陣列佔用的空間
for(int m=0;m<num1;m++)
delete[] p[m];
delete[] p;
return 0;
}
以下是執行結果:
Please enter the number for row and column:
4 5
1:004915F0 2:004915F4 3:004915F8 4:004915FC 5:00491600
2:00491180 4:00491184 6:00491188 8:0049118C 10:00491190
3:00491140 6:00491144 9:00491148 12:0049114C 15:00491150
4:00491100 8:00491104 12:00491108 16:0049110C 20:00491110
Press any key to continue
程式清單array04.cpp可以顯示分配的記憶體空間單元的地址,大家可以看到,由於陣列空間是動態分配的,陣列行之間的地址空間是不連續的,因為不同行的陣列元素的地址空間是用不同的new來分配的。而每一行之中列之間的地址空間是連續的。
那麼用vector(向量)怎樣實現二維陣列呢?以下給出源程式:
//檔名: array05.cpp
#include <iostream>
#include <vector>
#include <iomanip>
using namespace std;
int main()
{
int i,
j,
m, //行數
n; //列數
cout << "input value for m,n:";
cin>>m>>n;
//注意下面這一行:vector<int後兩個">"之間要有空格!否則會被認為是過載">>"。
vector<vector<int> > vecInt(m, vector<int>(n));
for (i = 0; i < m; i++)
for (j = 0; j < n; j++)
vecInt[i][j] = i*j;
for (i = 0; i < m; i++)
{
for (j = 0; j < n; j++)
cout<<setw(5)<<vecInt[i][j]<<":"<<setw(9)<<&vecInt[i][j];
cout<<endl;
}
return 0;
}
以下是執行結果:
input value for m,n:3 4
0: 00491180 0: 00491184 0: 00491188 0: 0049118C
0: 00491140 1: 00491144 2: 00491148 3: 0049114C
0: 00491100 2: 00491104 4: 00491108 6: 0049110C
Press any key to continue
大家可以看到,這裡vector中元素的記憶體的地址分配也有同雙指標實現的二維陣列有同樣的特點。不過用vector的方法比使用雙指標簡單地多,分配記憶體空間時會更安全,陣列初始化程式碼也更簡單,所以本人建議使用STL中的vector來實現變長多維陣列。以下是一個變長三維陣列:)
//檔名: array06.cpp
#include <iostream>
#include <vector>
#include <iomanip>
using namespace std;
int main()
{
int i,
j,
k,
m, //一維座標
n, //二維座標
l; //三維座標
cout << "input value for m,n,l:";
cin>>m>>n>>l;
vector<vector<vector<int> > > vecInt(m, vector<vector<int> >(n, vector<int>(l)));
for (i = 0; i < m; i++)
for (j = 0; j < n; j++)
for(k = 0; k < l; k++)
vecInt[i][j][k] = i+j+k;
for (i = 0; i < m; i++)
{
for (j = 0; j < n; j++)
{
for(k = 0; k<l; k++)
cout<<setw(5)<<vecInt[i][j][k]<<":"<<setw(9)<<&vecInt[i][j][k];
cout<<endl;
}
cout<<endl;
}
return 0;
}
執行結果:
input value for m,n,l:2 3 4
0: 00492FE0 1: 00492FE4 2: 00492FE8 3: 00492FEC
1: 00492FA0 2: 00492FA4 3: 00492FA8 4: 00492FAC
2: 00492F60 3: 00492F64 4: 00492F68 5: 00492F6C
1: 00492EC0 2: 00492EC4 3: 00492EC8 4: 00492ECC
2: 00492E80 3: 00492E84 4: 00492E88 5: 00492E8C
3: 00492E40 4: 00492E44 5: 00492E48 6: 00492E4C