자라나라
[C언어]포인터와 배열의 크기 본문
포인터란?
메모리의 주소값을 저장하는 변수이다. 포인터 변수라고도 부른다.
char형 변수에 문자를, int형에 정수를 저장하는 것처럼 포인터는 주소값을 저장한다.
ex) int a = 20; // 변수 선언
int *ptr = &n; // 포인터 선언
여기서 &는 주소연산자, *는 참조 연산자가 되는 것이다.
*붙은 녀석이 주소를 저장한다 생각하면 되겠다.
길을 찾아가주는 반딧불이라고 생각하자. 왠지 그렇게 생겼다
포인터의 선언
타입* 포인터이름;
ex) int* ptr;
포인터의 선언과 동시에 초기화를 하는 것이 좋다 !
타입* 포인터이름 = &변수이름; 또는,
타입* 포인터이름 = 주소값;
(*의 위치는 타입에 붙어있어도 되고 포인터 앞에 붙어있어도 되고 그 사이에 있어도 되고 상관없다)
여기서 주소값은 어떻게 알 수 있을까?
포인트 변수의 주소를 16진수로 출력해주는 %p가 있다. 값은 00000000 ~ FFFFFFFF 로 출력된다.
이전에 scanf를 배웠을 당시, &를 쓴 적이 있다.
그때는 백프로 이해하지 못하고 그저 '입력값을 임시로 기억해주는 메모리장치' 정도로 생각하고 넘어갔다. 무책임하게 넘긴 것은 아니다. 그 당시엔 이게 요점이 아니었기 때문ㅎㅎ..
이젠 이해했으니 한번 그 주소값을 출력해보자!
변수 A의 메모리 주소가 16진수로 출력이 되었다. 이는 디버깅할 때마다 값이 다르다 (RAM이 휘발성이기때문)
이번엔 배운내용을 바탕으로 변수와 포인터를 선언해서 그 주소값을 출력해보자
잘 출력됐다.
numptr을 %d로도 출력해도 특정값이 나오는데, 이는 10진수로 출력된 것이고
%p와 동시에 출력했을 때 두 값은 진법만 다른 같은 수이다.
우연의 일치일까 몇 번 돌려봤는데 아님
이건 중요하지 않고 !
정리하자면
또한
이중 포인터
포인터를 가르키는 포인터이다.
- [초대장]안녕 ! 널 위한 선물을 준비했어 아래 주소로 꼭 와주라! ---이동---> 선물 뙇!!!(비행기티켓이면 좋겠다)
초대장은 포인터고, 선물은 가르키는 값이 되겠다. - [쪽지]잘 잤어? 식탁 위를 확인해봐 --이동--> [초대장] --이동--> 선물
여기서 쪽지가 바로 이중포인터가 되는 것이고 결국 가리키는 값은 똑같다
여기서 주의할 점은 *쪽지 = 테이블의 주소가 아니라 선물의 주소라는 것!
결국 다중포인터에서 그들이 가리키는 주소는 다 똑같다.
이중포인터를 쓰는 이유는 뭘까?
라는 질문은 사실, 굳이 왜 포인터를 쓸까?와 같다.
그렇다면 포인터를 쓰는 이유는?
C언어에서는 함수를 호출할 때 Pass-by-value(Call-by-value) 의 방식만 쓰는데 이는 복사된 데이터를 전달하여 구성함으로써, 값을 수정하여도 원본의 데이터에는 영향을 주지 않도록 하는 방식이다.
아래 예시를 보자.
angel 함수에 대입한 수에는 a 값이 1004로 바뀌었어야 하는데 그대로 10이 나왔다.
angel을 호출할 때 전달되는 인자 a는 그 변수 자체가 아니라 복사된 값을 전달하는 것이기 때문에
main함수와 angel 함수 속 변수 a는 서로 다른 메모리에 존재하는 별개의 변수로 존재하는 것이다.
그렇게 angel 함수가 종료되면서 그 안에서의 변화는 사라지고, main함수로 돌아왔을 때 a는 그대로 10이 되는 것이다.
그럼 여기서 포인터를 써보자.
성공적으로 1004가 출력되었다.
이번엔 a라는 변수를 복사한 게 아니라 그 주소값의 위치를 복사하여 그곳에 20을 저장했기 때문이다.
C언어에서는 제공하지 않지만 pass by reference의 동작 방식이 있는데, 이는 실제값이 아닌 실제값의 주소가 쌓이게 된다. (자세한 내용은 https://mangkyu.tistory.com/107 참고)
swap 함수
위에서 배운 내용으로 바탕으로 함수를 한 번 만들어 보자
변수 a와 b를 넣으면 그 값이 바뀌어 나오는 swap 함수
temp는 임시로 x의 값을 저장해놓을 변수이다. 양 손에 음식이 든 접시가 있을 때, 접시를 바꾸려면 잠시 올려놓을 곳이 필요하다. temp가 테이블의 역할을 한다.
a와 b는 지역변수이기 때문에 main을 벗어나면 값이 소실된다. 그러므로 그 주소값을 swap에 넘겨줘야한다. swap(&a, &b)
주소값을 저장할 수 있는 변수는 포인트 변수이므로 파라미터에는 int *x, int *y가 들어가게 된다.
배열과 포인터
앞에서 printf를 배우며 잠시 다뤘던 문자열은, 배열과 연관이 깊었다. 아니, 문자열 자체가 문자의 배열이다.
C언어에서 문자열(string)은 메모리에 저장된 연속된 문자(char)의 집합이기 때문이다.
일단 함 보자.
아래는 문자열에서 첫번째와 두번째 주소 값을 출력하는 코드이다.
원하는 대로 출력이 됐다. 이번엔 다른 방법으로 출력해보자.
이전에 우린 sizeof로 각 자료형의 크기를 구한적이 있다
보시다시피, 문자는 1바이트이다. 문자열의 배열에서 한 글자당 1바이트를 차지하는 것이다.
위의 hello 예시에서 *ptr과 *ptr2는 메모리 주소이다. *ptr의 주소가 001 이라면 ptr2는 1바이트 다음인 002가 될 것이다. 그렇다면 *ptr2대신에 *ptr+1 을 대입해보면 어떨까?
이런 뜬금없이 i 가 나왔다. 혹시 알파벳순일까 해서 -1을 넣었더니 g가 나왔다. 역시 알파벳순이 맞았다.
이유가 뭔가하고 찾아보니 연산자 우선순위의 문제였다.
위 자료를 보면 포인터는 2순위고 +-는 4순위이다. 즉 *ptr + 1은 *ptr= h에서 ascii 상의 다음 코드값인 i를 내놓은 것이다.
다시 제대로, 우선순위를 조정해서 출력해주면
원하는 값이 나왔다.
이번엔 int형 배열의 주소를 확인해보자.
10진수가 더 보기 편하니까 %d로 출력ㅎㅏ겠슴다
역시 4바이트의 갭을 두고 붙어있다.
그렇다면 변수 arr 자체의 주소는??
배열의 주소는 배열의 [0]의 주소값과 같은 것을 알 수 있다.
이번엔 arr + 1을 출력해보자
4바이트 차이가 난다 즉 arr + 1 은 arr[1]의 주소값과 같게 되는 것이다.
정리하면,
arr = &arr[0]
arr + 1 = &arr[0] + 1 = &arr[1]
arr + 2 = &arr[2]
arr + i = &arr[i]
... 증명해보자
arr + i = &arr[i] 는 증명되었다.
같은 원리로
*(arr + i) = arr[i] 도 맞나 확인해보자.
arr + i = &arr[i]
*(arr + i) = arr[i]
마지막으로 아래의 예시도 살펴보자
int *ptr = arr
*(ptr + n) = arr + n = &arr[n]
위의 세가지 증명을 정리하면
- arr + i = ptr + i = &arr[i]
- *(arr + i) = *(ptr + i) = arr[i]
추가적으로,
a[b] = *(a + b)로 바꿔서 처리를 한다.
이를 응용해 덧셈의 교환법칙을 이용하면
ptr[i] = *(ptr + i) = *(i +ptr) = i[ptr] 이 되어
이렇게도 출력이 된다. (물론 실무에선 이렇게 쓰지 않는다)
a[b] = *(a + b)의 개념이 잘 이해는 안 되지만 일단 알아둬야겠다.
'C언어' 카테고리의 다른 글
[C언어]배열(array)[2] (0) | 2022.05.08 |
---|---|
[C언어] 배열(array)[1] 선언, 초기화 (0) | 2022.04.15 |
[C언어] 함수 (0) | 2022.04.12 |
[C언어] if else , break/continue, random, switch (0) | 2022.04.01 |
[C언어] 반복문 (0) | 2022.03.24 |