2차원 배열을 선언 후 함 수 이자로 넘겨줄 필요가 있는 경우




이케 받으면 오류가 난다.

그 이유는?
이중배열은 실제로 메모리에 저장될 때는 일차원으로 저장된다. 

int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};


이렇게 선언한 배열은 


int a[12]={1,2,3,4,5,6,7,8,9,10,11,12};


라기 보다는 


int a[3]={int[4], int[4], int[4]}


이것과 같다고 볼 수 있다.


무슨말이냐






이중 배열의 주소체계는 이러하다. 

a자체는 총 3개의 int[4]원소를 가진 배열의 시작주소를 나타내고

a의 첫번째 원소 a[0]( *(a+0)과 같음 )=int[4]={1,2,3,4}

      두번째 원소 a[1]=int[4]={5,6,7,8}

      세번째 원소 a[2]=int[4]={9,10,11,12}가 된다. 

따라서 a[0]의 주소와 a[1]의 주소는( 4byte(int 크기)*4=)16byte 차이가 난다. 


a[0]은 총 4개의 int원소를 가진 배열의 시작주소를 나타내므로 

a[0]의 첫번째 원소 a[0][0]( *(*(a+0)+0)과 같음 )=int={0}

         두번째 원소 a[0][1]=int={1}

                        ...

과 같다. 

따라서 a[0][0]의 주소와 a[0][1]의 주소는 4byte차이가 난다. 


이를 코드로 확인해보면 



>>결과


이케 된다. 

a의 주소와 그 다음 주소 값을 확인해보면 16진수로 48byte(3*(int(4byte)*4))=(0x30) 차이가 나는 것을 알 수 있다

a의 첫번째 원소(*a=a[0])의 주소값과 두번째 원소(*(a+1)=a[1])의 주소값은 16byte(int*4)=(0x10) 차이가 나고

a[0]의 첫번째 원소(*(*(a+0)+0)=a[0][0])의 주소값과 두번째 원소(*(*(a+0)+1)=a[0][1])의 주소값은 4byte(int)=(0x04) 차이가 난당


여기서 나는 헷갈렸던게 a는 int 포인터 타입의 변수인줄 알았다.

즉 a는 배열을 메모리에 할당하고 그 주소값만 가지고 있는 변수라고 생각했는데

그게 아니라 그냥 그 배열 자체를 나타내는 변수였던 것이다

이게 무슨 말이냐면 변수를 사용할 때 

int a=4;

이케 선언하면 4라는 값을 메모리에 할당한 후 그 메모리의 주소값을 나타내는 애가 a이다. 

a라는 변수를 사용해서 4를 할당해준 메모리에 접근할 수 있는 것이다. 

(이게 변수를 만들어 주는 이유. 우리가 항상 메모리 주소를 기억하고 사용하기 불편하므로 변수를 사용해서 쉽게 사용하는 것)

a는 4를 갖고 있는 메모리자체라고 볼 수 있음

나는 이걸 a는 4라는 값을 갖는 int 자체를 나타내는 변수 라고 표현했다.

근데 포인터형 변수는 그 메모리 자체를 나타내는 변수가 아니라 메모리 주소값만을 갖는 변수이다

int* b=&a

라고 선언하면 a는 메모리의 주소값을 포인터형 변수인 b에게 알려주는 것이다.



그림을 보면 a는 4값을 담고 있는 메모리이고 b는 4를 담고 있는 메모리 주소를 담는 메모리이다.(아...쓰면서도 헷갈려)

b는 0xb4라는 주소를 따로 갖는 4를 담는 메모리와는 별개의 변수이지만 a는 4를 담는 메모리 자체인것이다.

부디 나중에 보면서도 이해되길....


다시 처음으로 돌아가서 왜

함수에서 

int a[3][4]로 선언한 배열을 

int** arr 로 받으면 에러가 나는가?


a는 배열을 가지고 있는 메모리 자체를 나타내는 변수고

arr은 정수값을 갖는 메모리를 찾아가기 위한 주소 값을 갖는 포인터형 변수이기 때문이다.





arr로 넘어오는 것은 a의 주소값인 0x00 이다. 

arr은 0x64번지에 메모리를 할당해주고 0x00이라는 주소값을 담고 있는 변수!

또 arr은 이중 포인터이기 때문에 처음에 0x00을 따라간 후에 0x00번지에 담겨 있는 값도 

8byte의 주소값(64bit에선 8byte, 32bit에선 4byte)으로 인식을 한다. 

0x00번지부터 0x08번지까지에 있는 binary값을 읽은 다음 그값을 16진수의 주소(0x21)로 인식을 하는 것이다. 

그래서 이를 따라간 다음 int 값으로 읽기 위해 4byte의 binary 값을 읽으면 값이 이상해진다.

(간단하게 그림을 나타내기 위해 정수값을 주소값으로 변환하는걸 그림에서는 야매로 0x21로 나타냈는데 

실제 컴퓨터에서는 정수값 1과 2를 연달아 binary값으로 읽은 다음 16진수 주소값으로 바꾸면

0x0000000200000001이 된다. 

정수값 1은 0x00000001로 저장 되고 정수값 2는 64bit에서는 0x00000002로 저장되는데 

little endian으로 8byte를 읽으면서 0x0000000200000001 이 되는 것이다)

그래서 제대로 넘겨주기 위해선



이케 써야된다 



뭐가 다르냐면....
int(*arr)[4]로 넘겨주는 것은 
arr이 가리키고 있는 건(*arr)
"int형 데이터 4개를 갖고 있는 배열(int[4]"을 담고 있는 메모리 자체라는 것을 알려주는 것이다. 
(*arr)이 int형 데이터 4개를 갖는 배열이라는 것을 알기 때문에 (*arr)은 16byte의 크기라는 것도 알고 있고
(*arr)에서 1을 더하면 arr주소에서 16byte를 더한 메모리로 이동할 수 있는 것이다. 
또 (*arr)이 int형 데이터를 4개 가지고 있는 배열이라는 것도 알기 때문에 (*arr)의 첫번째 원소를 읽기 위해서 
(*arr)[0]을 하면 시작 주소에서부터 int의 크기인 4byte를 읽을 수도 있다.
메모리 자체에 접근 가능
여기서 또 헷갈렸던게....@.@

내가 디버깅하며 테스트한 코드




디버깅하며 확인한 변수의 주소와 그 안에 들어있는 값


>>결과 




int(*arr2)[4]로 넘겨준 arr2를 보면 0x000000020bff8fbd0번지에 할당되어 

a의 주소값 0x000000020bff8fbf8을 갖고 있는 포인터형 변수라는 것을 알 수 있다. 

따라서 arr2의 사이즈는 8byte (64bit운영체제에서의 주소크기)이고 arr2가 가리키는 것은(0x000000020bff8fbf8번지에 있는 데이터=(*arr))

int[4]라는 것을 알려줬기 때문에 *(arr2)의 사이즈는 16byte라는 것을 알고 있다.

그리고 *(arr)이 가리키는 데이터(0x000000020bff8fbf8번지에 있는 데이터=*(*arr))는 int형이니까 4byte로 출력된다. 

(arr2=*arr2=*(*arr2)인 이유는 arr2가 이중 배열을 가지고 있는 메모리 자체를 나타내는 변수 이기 때문. 위에 설명해놨다)

이렇게 잘 넘어와서 넘겨받은 배열의 내용이 잘 출력된다.

반면 int** arr로 받은 arr을 보면 0x000000020bff8fbd0번지에 할당되어 
a의 주소값 0x000000020bff8fbf8을 갖고 있는 포인터형 변수라는 것을 알 수 있다. 
arr의 사이즈는 8byte이고 arr이 가리키는 것은 다시 포인터형이기 때문에 *arr의 사이즈는 주소크기인 8byte가 되고 
*arr을 int형 데이터가 4개 담겨있는 배열로 읽는 것이아니라 
*arr에 있는 값을 주소 형태인 8byte단위로 읽는다. 
그래서 *arr에 있는 데이터  (4byte 숫자 1(0x00000001)과 arr[0][1]에 있던 4byte 숫자 2(0x00000002))를 읽어
0x0000000200000001 형태의 주소로 읽는다. (*arr=0x0000000200000001)
(*arr+1(arr[1])은 그 다음 8byte 데이터인 0x0000000400000003(정수 3과 정수 4를 8byte 주소로 읽은 것)으로 읽는 것 봐라...절레절레...)
*(*arr)(=0x0000000200000001이 가리키는 데이터)는 당연히 없다. 



이중배열을 
int** b;
이렇게 선언해주고 동적할당 해주면 
함수인자로 
int printArr(int** arr)
이렇게 넘겨줘도 에러가 안난다.
무슨 차이인지는 2탄에 정리....



+ Recent posts