C언어 공부 0008
포인터
포인터란
- 메모리의 주소를 가진 변수
- 변수의 주소는
&
연산자를 이용하여 알 수 있다.
- 변수의 주소는
- 포인터를 통해 메모리에 직접 접근이 가능하다.
- 메모리는 바이트 단위로 access된다.
- 자료형에 따라 메모리에서 차지하는 공간이 다르다.
- 이전의 변수 관련 포스트를 참고하세요.
- 포인터 변수의 선언은
*
연산자를 변수명 앞에 붙여주는 것으로 사용이 가능하다.int *p;
와 같이 사용
- 예
int i = 10;
int *p; // 포인터 변수 선언
p = &i; // 변수 i의 주소가 포인터 p로 대입
- 위 예시에서 p는 변수를 가리키는 포인터이며, *p의 값은 10이 되게된다.
포인터 사용시 주의점
- 초기화가 안된 포인터를 사용하면 런타임 에러가 발생한다.
int *p; // 포인터 p는 초기화가 안되어 있음
*p = 100; // 에러발생
- 포인터가 아무것도 가리키고 있지 않는 경우에는 NULL로 초기화해야 한다.
- NULL 포인터를 가지고 간접 참조하면 하드웨어로 감지할 수 있다.
- 포인터의 타입과 변수의 타입은 일치하여야 한다.
포인터 연산
- 증가, 감소, 덧셈, 뺄셈 연산이 가능하며, 포인터가 가리키는 객체 크기만큼 값이 바뀌게 된다.
- 예를들어 int형의 경우 4, char형의 경우 1 만큼 바뀐다.
- 예
#include <stdio.h>
int main(void)
{
char *pc;
int *pi;
double *pd;
pc = (char *)10000;
pi = (int *)10000;
pd = (double *)10000;
printf("증가 전 pc = %d, pi = %d, pd = %d\n", pc, pi, pd);
pc++;
pi++;
pd++;
printf("증가 후 pc = %d, pi = %d, pd = %d\n", pc, pi, pd);
printf("pc+2 = %d, pi+2 = %d, pd+2 = %d\n", pc+2, pi+2, pd+2);
return 0;
}
- 위 예시의 결과는 다음과 같다.
증가 전 pc = 10000, pi = 10000, pd = 10000
증가 후 pc = 10001, pi = 10004, pd = 10008
pc+2 = 10003, pi+2 = 10012, pd+2 = 10024
- char형인 pc는 증가를 1*1, 1*2만큼, int형인 pi는 증가를 4*1, 4*2 만큼, double형인 pd는 증가를 8*1, 8*2만큼 하는 모습을 볼 수 있다.
*
연산자와 증감연산자 중에서 증감연산자가 우선순위가 더 높다.
수식 | 의미 |
---|---|
v = *p++; |
p가 가리키는 값을 v에 대입한 후에 p를 증가한다. |
v = (*p)++; |
p가 가리키는 값을 v에 대입한 후에 가리키는 값을 증가한다. |
v = *++p; |
p를 증가시킨 후에 p가 가리키는 값을 v에 대입한다. |
v = ++*p; |
p가 가리키는 값을 가져온 후에 그 값을 증가하여 v에 대입한다. |
포인터와 배열의 관계
- 포인터와 배열은 아주 밀접한 관계를 가질 수 있다.
- 배열은 메모리상에 연속적으로 자료가 저장되는 구조이고, 포인터는 메모리에 주소를 통해 직접 접근할 수 있기 때문이다.
- 예
#include <stdio.h>
int main(void)
{
int a[] = {10, 20, 30, 40, 50};
printf("a = %u\n", a);
printf("a + 1 = %u\n", a + 1);
printf("*a = %d\n", *a);
printf("*(a+1) = %d\n", *(a+1));
return 0;
}
- 위 예시의 결과는 다음과 같다.
a = 1245008
a + 1 = 1245012
*a = 10
*(a+1) = 20
- 배열의 이름 a의 주소와 a+1의 주소가 4만큼 차이나는 것을 통해, 우리는 int형 배열의 한 칸이 4만큼의 공간을 차지하는 것을 알 수 있고, 배열은 메모리에 연속적으로 값이 저장되는 구조이므로, 이를 통해 배열의 각 원소에 접근할 수 있다는 힌트를 얻을 수 있다.
- 그리고 *a값이 10, *(a+1)의 값이 20인 것을 보아 a의 주소는 배열의 첫 원소를 가리키고, 포인터 연산을 통해 다음 원소에도 접근할 수 있다는 것을 알 수 있다.
함수 호출 시 인수 전달 방법
- call by value : 인수의 값이 복사되어 전달
- call by reference : 포인터를 이용하여 흉내내는것으로, 인수의 주소값이 복사되어 전달
- 배열 또한 이 방식을 따른다.
Swap 함수 작성해보기
- 잘못된 예
void swap(int x, int y)
{
int temp = x;
x = y;
y = temp;
}
- 위 코드를 이용하여 main함수에서 두 값을 바꾸게 되면, 값이 바꿔지지 않는다. 그 이유는, 기본적으로 C언어는 call-by-value에 의한 argument value를 전달하므로, swap함수 안에서만 값이 바뀌고 이 결과가 main함수에 반영되지 않는것이다.
- 올바른 코드
void swap(int *x, int *y)
{
int temp = *x;
*x = *y;
*y = temp;
}
포인터를 반환하는 함수
- 함수가 종료되더라도 남아 있는 변수의 주소를 반환하여야 한다.
- 함수의 지역변수 주소를 반환하면, 함수가 종료되면 사라지기 때문에 오류가 발생한다.
함수 포인터
- 포인터는 함수를 가리킬 수 있다.
- 함수 또한 메모리상에서 공간을 가지고, 코드로 존재하기 때문이다.
- 형태
자료형 (*함수포인이름)(매개변수);
- 예
#include <stdio.h>
int add(int a, int b);
int main()
{
int (*pf)(int a, int b);
pf = add;
.
.
.
}