주소 값 자체를 처리할 데이터로 생각해보자. 주소를 저장한 포인터도 하나의 변수이고, 따라서 그 주소를 구할 수 있으며 또 다른 포인터에 저장하고 가리키는 것도 가능하다. 쉽게 말하자면 찾아간 주소 위치에 또 주소 값이 있다는 말이다!
이중 포인터
포인터도 메모리에 저장 공간을 갖는 하나의 변수이다. 따라서 주소 연산으로 포인터의 주소도 구할 수 있다.
예를 들어 어떤 변수를 가리키는 포인터 pi가 있다고 가정해보자.
이 포인터 pi가 할당된 메모리의 시작 위치가 200번지일 때 그 주소를 구한다고 해보자.
그러면 이 주소를 저장하는 포인터가 바로 이중 포인터다.
포인터의 주소는 이중 포인터에 저장하고, 포인터를 가리킨다. 포인터의 주소를 저장한 이중 포인터에 간접 참조 연산을 수행하면 가리키는 대상은 포인터를 쓸 수 있다.
int **ppi // 여기서 별 (*) 은 각각 다른 의미를 가진다.
첫 번째 별는 가리키는 변수의 자료형을 나타내고, 두 번째 별은 자신의 자료형을 의미한다. ( 자신이 포인터임을 뜻함)
이중 포인터를 선언하여 메모리에 저장 공간이 할당되면 그 이후에 이중 포인터를 사용할 때는 변수명을 쓴다.
pi = &a; // int형 변수의 주소를 저장한 포인터
ppi = π //포인터의 주소를 저장한 이중 포인터
- pi 와 ppi에 &연산을 하면 자신의 주소 값을 반환함
- ppi에 * 연산을 하면 pi를 뜻한다.
- ppi에 ** 연산을 하면 ppi가 가리키는 pi가 가리키는 대상, 변수 a를 반환한다.
*** 이중 포인터 ppi로 변수 a를 사용하려면 간접 참조 연산자를 두 번 써야한다.
이중 포인터의 형태
포인터가 가리키는 것의 형태와 자신의 형태를 명확하게 구분해야한다.
예를 들어 int 형 변수의 주소를 저장하는 포인터는 가리키는 자료형이 int 이고 자신의 형태는 (int *)형이 된다.
포인터도 변수이므로 형태가 있는대 주소를 저장하므로 일반 변수와 형태를 구분해야하기 때문이다.
double a = 3.5;
double *pi = &a;
double **ppi;
ppi = π
ppi 가 가리키는 것은 double* 형이며 pi가 가리키는 것은 double 형이다.
주소와 포인터의 차이
포인터는 변수이므로 주소 연산자를 사용하여 주소를 구할 수 있지만, 상수인 주소에는 주소 연산자를 쓸 수 없다.
다중 포인터
이중 포인터 역시 변수이므로 주소 연산자를 사용하면 그 주소를 구할 수 있다.
이중 포인터 이상의 포인터를 다중 포인터라고 부른다.
이중 포인터의 활용
이중 포인터는 포인터의 값을 바꾸는 함수의 매개변수에 사용한다.
바꾸고자 하는 변수가 포인터일 경우 함수의 인수로 포인터를 주고 그 값을 받는 매개변수로 이중포인터를 선언한다.
포인터 배열을 매개변수로 받는 함수
이중 포인터는 포인터 배열을 매개변수로 받는 함수에도 사용한다. 배열명은 첫 번째 배열 요소의 주소이므로 int형 배열의 이름은 int 형 변수의 주소이다. 마찬가지로 int형 포인터 배열의 이름은 int형 포인터의 주소가 된다. 따라서 배열명을 인수로 받는 함수의 매개변수는 이중 포인터를 선언해야한다.
배열 요소의 주소와 배열의 주소
배열 전체를 하나의 변수로 생각하고 주소를 구해보자.
int ary[5];
배열의 주소 &ary와 주소로 쓰이는 ary와 어떤 차이가 있을까?
- ary가 주소로 쓰일 때와 ary에 주소 연산자를 사용한 &ary의 값은 모두 배열의 시작 위치이다.
그러나 가리키는 대상이 다르므로 두 주소에 1을 더한 결과가 다르게 나타난다.
ary 자체가 주소로 쓰일 때는 첫 번째 요소를 가리키므로 가리키는 대상의 크기가 4이다.
반면 배열의 주소 &ary는 배열 전체를 가리키므로 대상의 크기가 20이다. ( 4바이트 * 5 )
배열도 일반 변수처럼 크기와 형태에 따른 정보를 가진다.
1) 배열은 전체가 하나의 논리적인 변수이다.
- 배열 역시 일반 변수처럼 크기와 형태에 대한 정보를 가진다. 다만, 배열은 다양한 선언이 가능하여 선언 방법에 따라 크기와 형태가 다를 수 있다. 예를 들어 int ary[5]; 배열이 있다고 해보자.
이때 배열의 크기는 20바이트며 int형 변수 5개의 배열이란 자료형의 정보를 가진다. 배열 전체가 하나의 변수와 같은 역할을 하므로 변수에 사용하는 연산자를 배열에도 쓸 수 있다.
다만 배열은 논리적인 변수이므로 일반 변수와 같이 대입 연산자 왼쪽에 사용하는 것이 불가능하다.
2) 배열의 주소에 정수를 더하면 배열 전체의 크기를 곱해서 더한다.
2차원 배열과 배열 포인터
2차원 배열은 1차원 배열로 만든 배열이므로 논리적인 배열 요소가 1차원 배열이다. 또, 배열명은 첫 번째 요소의 주소이므로 다음과 같이 정리할 수 있다.
2차원 배열의 이름은 1차원 배열의 주소이며 배열을 가리키는 포인터에 저장한다.
배열 포인터는 배열을 가리키는 포인터로 2차원 배열의 이름을 저장할 수 있다.
2차원 배열의 이름을 저장할 배열 포인터의 선언을 살펴보자.
변수명 앞에 별을 붙여 포인터임을 표시하고 괄호로 묶는다.그리고 양 옆에 배열의 형태를 나누어 적는다.
int (*pa)[4];
위 포인터가 가리키는 것은 int 4개의 1차원 배열이다. 선언된 배열 포인터는 일반 포인터처럼 메모리에 저장 공간이 확보되므로 그 이후부터는 이름으로 사용한다.
2차원 배열 요소의 두 가지 의미
2차원 배열에는 두 가지 의미의 배열 요소가 있다.
개념적으로 2차원 배열의 요소는 1차원 배열이지만 실제로 데이터가 저장되는 공간은 1차원 배열의 요소이다.
따라서 2차원 배열에서 '배열의 요소'는 논리적으로는 1차원의 부분 배열을 뜻하고 물리적으로는 실제 데이터를 저장하는 부분배열의 요소를 뜻한다.
2차원 배열의 요소를 참조하는 원리
2차원 배열은 1차원 배열과 같이 모든 저장 공간이 메모리에 연속적으로 할당된다. 이 공간을 2차원의 논리적 공간으로 사용할 수 있는 것은 배열명이 1차원 배열의 주소로 1차원 배열 전체를 가리키기 때문이다.
따라서 배열 포인터를 쓰면 1차원의 물리적 공간을 2차원의 논리적 구조로 쓸 수 있다.
ary + 1 은 100 + (1 * sizeof(ary[0])) = 116 이다. 이 결과는 두 번째 부분배열 전체를 가리키는 주소이다. 즉, 주소 ary에 정수를 더한 값도 주소이고 가리키는 자료형도 변하지 않는다. 따라서 116번지에 간접 참조 연산자를 사용하여 두 번째 부분배열을 구하는 과정이 필요하다.
*(ary +1) 은 ary[1]과 같다. 여기에 2를 더하면 124번지를 구할 수 있다. (2* sizeof(ary[1][0]))
여기서 sizeof(ary[1][0])은 두 번째 부분배열의 첫 번째 배열 요소의 크기이다.
연산의 결과값 124번지는 7번째 물리적 배열 요소의 주소이므로 간접 참조 연산을 한 번 더 해주면! 요소를 쓸 수 있다.
*(*(ary+1) +2) 는 ary[1][2] !
***첫 번째 부분배열의 주소인 ary를 사용하면 배열의 모든 공간과 값을 사용할 수 있다.
&ary // 2차원 배열 전체의 주소
ary // 첫 번째 부분배열의 주소
&ary[0] //첫 번째 부분배열의 주소
ary[0] // 첫 번째 부분 배열의 첫 번째 배열 요소의 주소
&ary[0][0] // 첫 번째 부분배열의 첫 번쨰 배열 요소의 주소
위 좀 헷갈린다 많이 봐둬야겠음..ㅋ
'공부 STUDY > C | C++' 카테고리의 다른 글
[ C ] 변수 사용 영역 | 지역 변수 | 전역 변수 | 정적 지역 변수 | 레지스터 변수 (0) | 2023.01.29 |
---|---|
[ C ] 주소와 포인터 차이? | 포인터의 크기 | 포인터 자료형 | 포인터 기능 (0) | 2023.01.28 |
[ C ] 포인터란? 메모리 주소 | 포인터 연산자( &, *) (0) | 2023.01.28 |
[ C ] 문자를 저장하는 배열 | char 형 배열 | 문자열 대입 방법 | strcpy(), gets(), puts() (0) | 2023.01.28 |
[ C ] 배열 | 배열의 선언과 사용 (0) | 2023.01.28 |