반응형
[ 포인터연산 ]
포인터로 선언한 변수에는 메모리 주소가 들어있다. 이 포인터 변수에서 연산을 할 수 있다.
마찬가지로 메모리 주소에 일정 숫자를 더하거나 빼면 메모리 주소가 증가,감소 한다. 즉, 포인터 연산을 하면 다른 메모리 주소에 접근할 수 있으며 메모리 주소를 손쉽게 옮겨 다니기 위해서 사용한다.
여기서 메모리 주소가 커지는 상황을 순방향 이동(forward), 메모리 주소가 작아지는 상황을 역방향이동(backward)라 하겠다.
포인터연산으로 메모리 주소 조작하기
#include <stdio.h>
int main(){
int numArr[5] = { 11, 22, 33, 44, 55 };
int *numPtrA;
int *numPtrB;
int *numPtrC;
numPtrA = numArr; // 배열 첫 번째 요소의 메모리 주소를 포인터에 저장
numPtrB = numPtrA + 1; // 포인터 연산
numPtrC = numPtrA + 2; // 포인터 연산
printf("%p\n", numPtrA); // 00A3FC00 : 메모리주소. 컴퓨터 마다, 실행할 때마다 달라짐
printf("%p\n", numPtrB); // 00A3FC04 : sizeof(int)*1 이므로 numPtrA에서 4가 증가함
printf("%p\n", numPtrC); // 00A3FC08 : sizeof(int)*2 이므로 numPtrA에서 8이 증가함
return 0;
}
# 실행 결과
00A3FC00 ( 메모리주소. 컴퓨터 마다, 실행할 때마다 달라짐 )
00A3FC04
00A3FC08
- 포인터 연산은 특별한 것이 없고 포인터 변수에 정수 값을 더하거나 빼면 된다. 단, 연산하는 값이 메모리 주소이므로 곱하거나 나누는 연산은 의미가 없음
- 포인터 연산은 포인터 자료형의 크기만큼 더하거나 뺀다.
- 여기서 numPtrA가 4바이트 크기의 int형이다. 따라서 numPtrA+1은 메모리주소에서 4바이트만큼 1번 순방향 이동한다는 뜻 즉, 계산식은 sizeof(자료형) * 더하거나 빼는 값이 된다.
이번에는 포인터 뺄셈을 해보겠다.
#include <stdio.h>
int main(){
int numArr[5] = { 11, 22, 33, 44, 55 };
int *numPtrA;
int *numPtrB;
int *numPtrC;
numPtrA = &numArr[2]; // 배열 세 번째 요소의 메모리 주소를 포인터에 저장
numPtrB = numPtrA - 1;
numPtrC = numPtrA - 2;
printf("%p\n", numPtrA); // 00A3FC00 : 메모리주소. 컴퓨터 마다, 실행할 때마다 달라짐
printf("%p\n", numPtrB); // 00A3FC04 : sizeof(int) * -1 이므로 numPtrA에서 4가 감소함
printf("%p\n", numPtrC); // 00A3FC08 : sizeof(int) * -2 이므로 numPtrA에서 8이 감소함
return 0;
}
# 실행 결과
00A3FC08 ( 메모리주소. 컴퓨터 마다, 실행할 때마다 달라짐 )
00A3FC04
00A3FC00
numPtrA = &numArr[2];
와 같이 배열에[ ](대괄호)
를 사용하여 요소에 접근한뒤&(주소연산자)
를 사용하면서 해당 요소의 메모리 주소를 구할 수 있다.
- 포인터 연산은 char는 1바이트, short는 2바이트, int는 4바이트, long long은 8바이트만큼 메모리 주소에서 순방향, 역방향으로 이동한다.
포인터연산과 역참조
- 포인터 연산으로 조작한 메모리주소도 역참조 연산을 사용하여 메모리에 접근할 수 있다.
#include <stdio.h>
int main(){
int numArr[5] = { 11, 22, 33, 44, 55 };
int *numPtrA;
int *numPtrB;
int *numPtrC;
numPtrA = numArr; // 배열 첫 번째 요소의 주소를 포인터에 저장
numPtrB = numPtrA + 1; // 포인터 연산. numPtrA + 4바이트
numPtrC = numPtrA + 2; // 포인터 연산. numPtrA + 8바이트
printf("%d\n", *numPtrB); // 22. 역참조로 값을 가져온다. numArr[1]과 같음
printf("%d\n", *numPtrC); // 33. 역참조로 값을 가져온다. numArr[2]와 같음
return 0;
}
# 실행 결과
22
33
- 포인터 연산과 동시에 역참조 연산을 할 수 있다. 포인터 연산을 한 부분을
( )괄호
로 묶어 준 뒤 맨 앞에*(역참조 연산자)
를 붙이면 된다.
#include <stdio.h>
int main(){
int numArr[5] = { 11, 22, 33, 44, 55 };
int *numPtrA;
numPtrA = numArr; // 배열 첫 번째 요소의 주소를 포인터에 저장
printf("%d\n", *(numPtrA + 1)); // 22. numPtrA에서 순방향으로 4바이트만큼 떨어진 메모리에 주소에 접근. numArr[1]과 같음
printf("%d\n", *(numPtrA + 2)); // 33. numPtrA에서 순방향으로 8바이트만큼 떨어진 메모리에 주소에 접근. numArr[2]와 같음
return 0;
}
# 실행 결과
22
33
만약 포인터 연산을 괄호로 묶어주지 않으면 역참조 연산자가 먼저 실행되어 값을 가져 온 뒤 연산을 하게 된다.
ex.
printf("%d\n", *numPtrA + 1);
의 값은 11 + 1이 되어 12가 된다.
구조체포인터로 포인터 연산
- 구조체 포인터로 포인터 연산을 해보자
#include <stdio.h>
struct Data{
int num1;
int num2;
};
int main(){
struct Data d[3] = { {10, 20}, {30, 40}, {50, 60} }; // 구조체 배열 선언과 값 초기화
struct Data *ptr; // 구조체 포인터 선언
ptr = d; // 구조체 배열 첫 번째 요소의 메모리 주소를 포인터에 저장
printf("%d %d\n", (ptr + 1)->num1, (ptr + 1)->num2); // 30 40. 구조체 배열에서 멤버의 값 출력
// d[1].num1, d[1].num2와 같음
printf("%d %d\n", (ptr + 2)->num2, (ptr + 2)->num2); // 50 60. 구조체 배열에서 멤버의 값 출력
// d[2].num1, d[2].num2와 같음
return 0;
}
# 실행 결과
30 40
50 60
- 구조체 포인터는
(ptr+1)->num1
과 같이 포인터 연산을 한 뒤 갈호로 묶어준다. 그리고 화살표 연산자를 사용하여 멤버에 접근할 수 있다. - 구조체 Data의 크기는 4바이트짜리 int형 멤버가 두 개 들어있으므로 8바이트이다. 따라서 포인트연산을 하면 8바이트씩 메모리 주소에서 연산을 한다.
- 만약 구조체가 커져서 int형 멤버가 10개가 된다면 40바이트씩 더하거나 빼게 된다.
이번에는 void 포인터에 구조체 3개 크기만큼 동적 메모리를 할당한 뒤 포인터 연산을 해보자
((struct 구조체이름 *)포인터 + 값) -> 멤버
((struct 구조체이름 *)포인터 - 값) -> 멤버
#include <stdio.h>
#include <stdlib.h> // malloc, free
#include <string.h> // memcpy
struct Data{
int num1;
int num2;
};
int main(){
void *ptr = malloc(sizeof(struct Data) * 3); // 구조체 3개 크기만큼 동적 메모리 할당
struct Data d[3];
((struct Data *)ptr) -> num1 = 10; // 포인터 연산으로 메모리에 값 저장
((struct Data *)ptr) -> num2 = 20; // 포인터 연산으로 메모리에 값 저장
((struct Data *)ptr + 1) -> num1 = 30; // 포인터 연산으로 메모리에 값 저장
((struct Data *)ptr + 1) -> num2 = 40; // 포인터 연산으로 메모리에 값 저장
((struct Data *)ptr + 2) -> num1 = 50; // 포인터 연산으로 메모리에 값 저장
((struct Data *)ptr + 2) -> num1 = 60; // 포인터 연산으로 메모리에 값 저장
memcpy(d, ptr, sizeof(struct Data) * 3); // 동적 메모리가 구조체 배열의 형태와 같은지 확인하기 위해 동적 메모리의 내용을 구조체 배열에 복사
printf("%d %d\n", d[1].num1, d[1].num2); // 30 40. 구조체 배열의 멤버 출력
printf("%d %d\n", ((struct Data *)ptr + 2)->num1, ((struct Data *)ptr +2)-> num2); // 50. 60. 포인터 연산으로 메모리의 값 출력
free(ptr); // 동적 메모리 해제
return 0;
}
# 실행 결과
30 40
50 60
- 문법이 복잡해보이지만 어렵지 않아요
((struct Data *)ptr->num1)
은 앞에서 배운 구조체 포인터로 변환하는 방법이다. 이 상태에서 포인터 연산을 하려면((struct Data *)ptr + 1)->num1
과 같이 ptr을 구조체 포인터로 변환한 뒤 값을 더해주면 된다. (->(화살표연산자)
를 사용하려면 반드시 괄호로 묶어준다) - 이제 포인터 연산을 통해 메모리에 값을 저장한다. 만약
(ptr + 1)->num1
처럼 ptr에 포인터 연산을 하더라도 ptr은 void 포인터라 Data 구조체의 형태를 모르기 때문에 멤버에 접근할 수 없고 컴파일 에러가 발생한다. - 그리고 포인터 연산으로 값을 저장한 결과가 Data 구조체 배열의 형태와 같은지 확인하기 위해
memcpy(d, ptr, sizeof(struct Data)*3);
처럼 동적 메모리의 내용을 구조체 배열 d에 복사했다. - 즉, 동적 메모리에 저장된 값의 위치가 구조체 배열의 형태와 같고, 동적 메모리 내용을 그대로 복사했기 때문에 같은 값이 나온다. 또한, 포인터 연산으로도 동적메모리의 값을 출력할 수 있다.
반응형
'프로그래밍 언어 > [ C ]' 카테고리의 다른 글
[ C ] 08. 구조체 (0) | 2020.08.13 |
---|---|
[ C ] 07. 열거형 (0) | 2020.08.13 |
[ C ] 05. 메모리와 포인터의 사용 (0) | 2020.08.13 |
[ C ] 04. 포인터의 형변환 (0) | 2020.08.13 |
[ C ] 03. 포인터와 역참조 연산자 (0) | 2020.08.13 |