C/C++ - 2차원 배열과 포인터의 관계

Programming 2006/12/26 03:45
int arr[5][2];

위 코드는 int형 메모리 공간을 2차원 형태 2 × 5 로 만들어 주는 정적 배열 선언이다.
그리고 위 코드를 실행하게 되면, 아래의 그림과 같이 메인 메모리에 40byte의 메모리를 할당 받게 된다.
그 그림은 인간이 이해하기 쉽게 가상으로 구성한 2차원 공간의 메모리 구조이다.



실제적으로 메모리는 선형구조로 구성되어지기 때문에, 2차원적인 access를  위해서 다음과 같은 구조를 구성해야 한다. (즉, 1차원 배열들을 선형적으로 bind 시켜 놓은 것이라 생각 할 수 있다.)

정적 2차원 배열인 경우, 컴파일러가 자동으로 다음과 같은 구조를 만들어 준다.
  그러나 동적 2차원 배열을 만들기 위해서는 개발자가 manual로 다음과 같은 구조를 만들어야 한다.



위 그림에서 알 수 있듯이, 2차원 배열은 두 번 참조를 한다는 사실을 알 수 있을것이다.
이것은 일차원 배열과 같은 방법으로 접근 할 수 있다. 즉, 배열의 시작 주소 + 오프셋(OffSet)을 통해서 접근 할 수 있다.

예제로써 arr[2][1]이 어떻게 참조 되는 지 한번 알아보자.
첫 번째 참조는 arr[2] 이다. 이것은 3번째 일차원 배열의 시작 주소를 참조한다.
두 번째 참조는 배열의 시작 주소 (0x0024) 로부터 OffSet(1) 만큼 떨어진 주소에서 4byte 크기만큼 메모리의 내용을 읽어 온다. 그리고 OffSet 계산법은 일차원 배열의 OffSet 계산법과 동일하다. (즉, (sizeof(int) * 1);)

※ 요약 :                                                arr[i] [j]
           i + 1 번째 일차원 배열의 시작 주소 ─―──┘ └─ OffSet(j) 연산
Creative Commons License
Creative Commons License
명언 한마디
천시(天時)는 지리(地利)만 못하고, 지리는 인화(人和)만 못하다.
맹자
top

C/C++ - Double 포인터

Programming 2006/12/23 17:30
int   val = 100;
int  *pt1 = &val; // Single 포인터
int **pt2 = &pt1; // Double 포인터
 

위 코드를 실행하게 되면 컴파일러는 아래의 그림과 같은 코드를 메인 메모리에 만들 것이다.
그림에서 볼 수 있듯이, Single 포인터 pt1은 한번 "지시"를 하고, Double 포인터 pt2는 두 번 "지시"를 한다. 참조 할 때에도 마찬가지이다. pt1은 한번 참조를 통해 100 이라는 값을 얻고 (즉, *pt1;), pt2는 두 번 참조를 통해 100 이라는 값을 얻는다 (즉, **pt2;). 어떻게 하면 두 번 "지시"를 할 수 있을까?
그 해답은 아주 간단하다. Single 포인터의 주소를 저장하면 우리는 두 번 "지시"를 할 수 있다. 이것이 Double 포인터의 기능이다.

Double 포인터는 어떤 용도로 사용 하는가?
  동적인 2차원 배열을 만들 때 주로 사용 되어 진다.





위의 예제 코드에서, Single 포인터는 int형의 주소를 저장하고 Double 포인터는 int*형의 주소 (즉, Single 포인터의 주소)를 저장한다.

포인터에 대해 도저히 감히 잡히지 않는 다면은 아래의 코드와 같이 괄호를 사용하여 묶어 보라. 괄호를 제외한 부분이 저장되는 주소이다. 첫 번째 Single 포인터의 경우 int 가 남는다. 따라서 int형의 주소가 저장 된다. 그리고 두 번째 Double 포인터의 경우는 int* 가 남는다. 따라서 int*형의 주소 (즉, Single 포인터의 주소)가 저장 된다.

int     val = 100;
int  (*pt1) = &val; // Single 포인터
int *(*pt2) = &pt1; // Double 포인터
 

Triple 포인터도 위와 마찬가지로 이해하면, 우리는 쉽게 int**형의 주소 (즉, Double 포인터의 주소)가 저장 된다는 것을 알 수 있다.

괄호를 묶는 방법은 가급적 쓰지 마라.
  실질적으로
괄호 묶는 방법을 사용하는 개발자는 잘 없기 때문에, 다른 개발자가 쓴 Code를 이해하
  지 못하는 경우가  생긴다.
Creative Commons License
Creative Commons License
명언 한마디
칭찬, 그것은 때로는 삶의 활력소가 되기도 하지만 때로는 추진력을 잃게도 만든다.
벤자민 프랭클린
top

C/C++ - 배열과 포인터의 관계

Programming 2006/12/20 05:07
int arr[5]; // 배열 선언

위 코드는 int형 메모리 공간을 다섯 개 만들어 주는 배열 선언이다.
그리고 위 코드를 실행하게 되면, 아래의 그림과 같이 메인 메모리에 20byte의 메모리를 할당 받게 된다.



int형 데이터 타입은 4byte의 메모리를 차지하기 때문에
위의 그림에서와 같이, 메모리 주소는 4만큼 차이가 난다.
이것을 일반적으로 오프셋(OffSet)이라고 한다.

① 오프셋(OffSet)과 인덱스의 관계

배열의 첫 번째 원소를 참조하기 위해서 아래와 같은 코드를 쓴다.

arr[0]; // 1 번째 원소의 내용을 참조
 

여기서 인덱스는 오프셋과 많은 연관이 있다.
첫 번째 원소를 참조하기 위해 사용되어진 인덱스 0은
arr라는 배열의 시작 주소로 부터 Offset(0) 라는 의미
가 된다.
그리고 시작 주소로부터 OffSet(0) 만큼 떨어진 주소에서 4byte 크기 만큼 메모리의 내용을 읽어 온다.

arr[1]; // 2 번째 원소의 내용을 참조
위와 동일하게 배열의 시작 주소로 부터 Offset(4) 이라는 의미가 된다.
시작 주소로부터 OffSet(4) 만큼 떨어진 주소에서 4byte 크기 만큼 메모리의 내용을 읽어 온다.
그리고 OffSet 계산 법은 다음과 같다. ((sizeof(int) * 1);),

arr[2]; // 3 번째 원소의 내용을 참조
위와 동일하게 배열의 시작 주소로 부터 Offset(8) 이라는 의미가 된다.
시작 주소로부터 OffSet(8) 만큼 떨어진 주소에서 4byte 크기 만큼 메모리의 내용을 읽어 온다.
그리고 OffSet 계산 법은 다음과 같다. ((sizeof(int) * 2);), 이라는 의미가 된다.

arr[3]; // 4 번째 원소의 내용을 참조
위와 동일하게 배열의 시작 주소로 부터 Offset(12)  이라는 의미가 된다.
시작 주소로부터 OffSet(12) 만큼 떨어진 주소에서 4byte 크기 만큼 메모리의 내용을 읽어 온다.
그리고 OffSet 계산 법은 다음과 같다. ((sizeof(int) * 3);), 이라는 의미가 된다.

arr[4]; // 5 번째 원소의 내용을 참조
위와 동일하게 배열의 시작 주소로 부터 Offset(16) 이라는 의미가 된다.
시작 주소로부터 OffSet(16) 만큼 떨어진 주소에서 4byte 크기 만큼 메모리의 내용을 읽어 온다.
그리고 OffSet 계산 법은 다음과 같다. ((sizeof(int) * 4);), 이라는 의미가 된다.

※ cl 컴파일러를 쓰는 Microsoft사의 Visual C++은 정적 배열을 선언 할 때 배열 원소들을 0xcccccccc
  로 초기화 시킨다.

② 배열과 포인터의 관계

int arr[i];
 

위의 arr 배열의 시작 주소를 알아 내면 OffSet연산을 통해 배열의 원소값을 참조 할 수 있다.
포인터는 메모리의 주소를 담을수 잇는 변수 이며, 포인터의 주소 연산을 통해 주소 값을 증가, 또는 감소 시킬 수 있고, 내용 참조 연산을 통해 주소가 지시하는 메모리의 내용을 참조 할 수 있다 .

배열의 시작 주소는 배열의 이름 이다.
int* pt = arr; // 배열의 시작 주소를 포인터 변수 pt에 저장

배열의 인덱스 연산 : 시작 주소 위치 + Offset
포인터의 주소 연산 : 시작 주소 위치 + Offset

pt + i; // i 번째 원소에 접근
위 코드는 배열의 시작 주소로 부터 i 만큼 떨어 진 메모리의 위치 이다.
여기서 OffSet의 계산법도 앞서 설명한 방법과 동일 하다. (sizeof(int) * i;)

포인터의 내용 참조 연산은 포인터 변수 앞에 "*"만 붙여 주면 포인터의 데이터 타입 크기의 만큼의 메모리를 참조 할 수 있다.

*(pt + i); // i 번째 원소의 내용을 참조

arr + 0 == pt + 0;     arr[0] == *(pt + 0);
arr + 1 == pt + 1;     arr[1] == *(pt + 1);
arr + 2 == pt + 2;     arr[2] == *(pt + 2);
arr + 3 == pt + 3;     arr[3] == *(pt + 3);
arr + 4 == pt + 4;     arr[4] == *(pt + 4);

위의 코드에서 보여 지는 바와 같이 포인터와 배열은 동일한 방법으로 처리되어 진다.
즉, 포인터와 배열은 같다.
단지 내용 참조를 할 때 written 코드만 틀리다.

배열의 이름은 상수 포인터 이다.
  일반적인 포인터는 포인터 변수이다.


arr = arr + i; //정적 배열은 시작 주소가 고정, 즉 상수 값
               // 컴파일 시키면 에러
pt = pt + i;  //포인터로 만든 배열은 시작 주소가 바뀔 수 있다, 즉 변수 값
 

32bit 컴퓨터에서는 주소는 4byte로 표현, 64bit 컴퓨터에서는 주소는 8byte로 표현 되어진다.
sizeof(pt); // 주소의 크기 확인
Creative Commons License
Creative Commons License
명언 한마디
양초는 남을 밝게 해 주며. 자신을 소비한다.
H.G. 보운
top