배열은 자료형이 같은 변수를 메모리에 연속으로 할당한 것입니다. 따라서 각 배열 요소는 일정한 간격으로 주소를 갖습니다.
예를 들어 int arr[4];의 배열이 메모리 100번지부터 할당되어 있다면 메모리의 구조는 이렇게 구성될 것입니다.
int형 변수는 4바이트이기 때문에 각 배열 요소의 주소는 100, 104, 108, 112번지가 됩니다.
결국 첫 번째 요소의 주소를 알면 나머지 요소의 주소도 쉽게 알 수 있습니다. 따라서 컴파일러는 첫 번째 요소의 주소를 쉽게 사용하도록 배열명을 첫 번째 배열 요소의 주소로 변경합니다.
즉, 배열의 이름은 배열의 시작 주소 값입니다.
|
실행결과 배열의 이름 : 005AFA14 첫 번째 요소 : 005AFA14 두 번째 요소 : 005AFA18 세 번째 요소 : 005AFA1C |
배열의 첫 번째 주소값이 005AFA14인데, 배열의 이름을 출력한 결과도 005AFA14로 결과가 같습니다.
주소값을 저장할 수 있는 변수는 포인터 변수뿐입니다. 여기서 일단 배열의 이름은 포인터 변수임을 알 수 있습니다.
하지만 배열의 이름은 값의 저장이 불가능하고(대입연산자의 피연산자가 될 수 없습니다.), 주소 값의 변경이 불가능합니다.
그래서 배열의 이름을 '상수 형태의 포인터', '포인터 상수'라고 부르기도 합니다.
포인터 연산은 +, -, ++, -- 연산자를 사용하여 포인터 값을 증가/감소시키는 연산입니다. (포인터로 곱셈이나 나눗셈은 안됩니다. 주소값을 곱하거나 나누면 안되겠죠~)
예를 들어 크기가 4바이트인 int형 변수의 주소 100번지에 1을 더한 결과는 101이 아닌 104가 됩니다. 그리고 연산 결과 또한 주소입니다.
|
|
|
|
||||
실행결과 0번지, 0번지, 0번지 1번지, 4번지, 8번지 각 포인터들은 타입에 맞게 1, 4, 8바이트씩 증가했습니다. |
이러한 포인터의 연산 특성으로 인해서 배열 접근이 가능합니다.
7행에 선언된 포인터 변수 ptr은 int형 포인터이므로 값을 1 증가시킬 때마다 실제로는 4가 증가합니다.
아래 예제에서 *ptr, *(ptr+1), *(ptr+2), *(ptr+3), *(ptr+4)의 출력결과는 arr[0], arr[1], arr[2], arr[3], arr[4]의 출력결과와 모두 동일한 출력결과를 보입니다.
|
실행결과 1 2 3 4 5 1 2 3 4 5 |
배열의 이름과 포인터 변수는 상수, 변수의 차이만 있을 뿐, 사실은 동일합니다.
따라서 ptr에 저장된 값이 arr의 주소값이기 때문에 결과를 확인하면 4문장 모두 같은 값을 나타내는 것을 확인할 수 있습니다.
|
실행결과 1 2 3 1 2 3 1 2 3 1 2 3 |
|
|
|
|
포인터 배열이란 배열 요소로 포인터 변수를 가지는 배열을 의미합니다. 즉, 포인터 변수로 이루어진 '배열'을 의미합니다.
포인터 배열은 기본 자료형 배열과 크게 다르지 않습니다. 다만 주소값을 저장할 수 있는 포인터 변수를 대상으로 선언된 배열일 뿐입니다.
포인터 배열을 선언할 때는 배열 요소의 자료형이 포인터형이므로 배열이름 앞에 *을 붙이면 됩니다.
|
실행결과 주소값 : 00EFFB24 00EFFB18 00EFFB0C 값 : 1 2 3 포인터 배열은 메모리에 연속해서 위치하지만 |
포인터 배열과 변수의 관계를 그림으로 표현하면 다음과 같습니다.
포인터 배열은 다양한 응용이 가능합니다. 포인터 배열은 문자열을 저장할 수 있습니다.
문자열 배열은 문자열의 주소값을 저장하는 배열로서 사실 char형 포인터 배열입니다.
" " (큰 따옴표)로 묶인 문자열은 메모리 공간에 저장된 후 그 주소값이 반환됩니다. 그리고 저장된 메모리 공간의 시작 주소를 알려줍니다.
반환된 문자열의 주소값은 문자열 첫번째 문자의 주소입니다. 이렇게 char형 포인터 배열에 저장이 가능합니다.
|
실행결과 sejong university computer |
포인터 배열의 각 요소는 문자열 상수가 저장된 메모리에서 첫번째 문자열의 주소값을 저장하고 그 문자를 가리킵니다.
따라서 이 값을 변환문자 %s로 출력하면 null 문자가 나올 때까지 모든 문자를 차례로 출력하므로 하나의 문자열이 출력되는 것입니다.
배열 포인터란 배열을 가리킬 수 있는 '포인터'를 의미합니다.
1차원 배열은 이름 자체가 포인터이기 때문에 배열 포인터가 필요없습니다. 하지만 2차원 배열은 이름 자체로 포인터의 역할을 할 수 없습니다.
배열 포인터를 사용하는 이유는 2차원 이상의 배열을 가리킬 때 포인터를 배열처럼 사용하기 위함입니다.
따라서 배열 포인터는 2차원 이상의 배열에서만 의미를 가집니다.
arr2d[0]과 arr2d[1]은 각 부분 배열의 시작 주소를 가리킵니다. arr2d[0]은 2차원 배열의 첫 번째 행을 의미하고, arr2d[1]은 두 번째 행을 의미합니다.
|
||||
실행결과 10 40 |
배열 포인터는 다음과 같이 정의합니다.
|
|
- int : 가리킬 수 있는 대상에 대한 정보(즉, int형 변수를 가리키는 포인터)를 의미합니다. - (*ptr) : ptr은 포인터임을 의미합니다. - [3] : 포인터 연산에 따른 증가/감소의 폭입니다. |
다음 예제는 배열 포인터를 사용하여 배열과 같은 인덱싱 방법으로 배열 요소를 참조하는 예제입니다.
포인터에 배열명을 저장하면 배열 포인터를 2차원 배열처럼 쓸 수 있습니다.
|
실행결과 10 20 30 40 50 60 |
앞서 1차원 배열에서 arr[i] == *(arr+i) 라는 문장이 성립한다고 했습니다. 마찬가지로 2차원배열에서도 arr[i] == *(arr+i) 입니다.
아래 수식들은 모두 같은 표현입니다.
arr[i][j] | *(arr+i)[j] | *(arr[i]+j) | *(*(arr+i)+j) |
결과를 확인하면 4문장 모두 같은 값을 나타내는 것을 확인할 수 있습니다.
|
실행결과 10 10 10 10 20 20 20 20 30 30 30 30 40 40 40 40 50 50 50 50 60 60 60 60 |
포인터 배열(Array of Pointer)과 배열 포인터(Pointer to Array)는 비슷해 보입니다.
- 포인터 '배열'은 주소값들을 저장하는 '배열'입니다.
- 배열 '포인터'는 배열의 시작주소값을 저장할 수 있는 '포인터'입니다.
int *ptr[3]; | int (*ptr)[3]; |
첫 번째는 int형 변수로 이루어진 int형 포인터 배열입니다.
두 번째는 가로길이가 3인 int형 2차원 배열을 가리키는 포인터 변수입니다.
( )
(괄호)의 유무가 중요하며, 포인터 배열과 배열 포인터 사이의 차이점을 정확히 이해하고 넘어가시기 바랍니다.