포인터

코딩하는 기린·2022년 3월 6일
0

C

목록 보기
9/13

포인터 개요

'포인터(pointer)'는 변수의 일종이지만, 다른 변수와는 조금 다릅니다. 포인터는 특정한 값을 가지는게 아니라, 어떤 데이터가 저장된 '기억공간의 주소(번지)'를 값으로 가집니다.

int i = 100;
int *p = &i;

정수형 변수 i가 차지하는 기억공간이 있고, 모든 기억공간에는 주소가 존재하며, 그 주소를 값으로 가지는 변수가 포인터 변수이고, 선언시 앞에 '*'를 붙여 선언합니다. 그리고 그 주소(포인터 값)를 표현하기위해 '주소 연산자(&)'를 사용합니다.

포인터 변수의 선언과 참조

int i = 100;
int *p = &i;				//포인터 변수 p를 선언하고 i의 주소값을 대입합니다.

printf(" i = %d\n",  i);	//i의 값 100
printf("*p = %d\n", *p);	//포인터 변수 p가 가리키는 주소(&i)에 있는 내용(자료값(*p))
printf("\n");
printf("&i = %d\n", &i);	//i의 주소값(&i)
printf(" p = %d\n",  p);	//포인터 변수 p의 값(i의 주소값(&i))
printf("\n");
printf("&p = %d\n", &p);	//p의 주소값(&p)
 i = 100
*p = 100

&i = 0xAC
 p = 0xAC

&p = 0xB1

일반 변수는 선언시 int i = 100;의 형태로 선언합니다.
포인터 변수는 선언시 int *p = &i;의 형태로 선언합니다.

정수형 변수 i는 기억공간을 가집니다.
해당 기억공간은 i가 갖고있는 정수형 값 100을 가집니다.
모든 기억공간은 주소가 있고, 정수형 변수 i의 주소값(&i)은 0xAC입니다.

포인터 변수 p는 기억공간을 가집니다.
해당 기억공간은 p가 가리키는 i의 주소값(&i) 0xAC를 가집니다.
모든 기억공간은 주소가 있고, 포인터 변수 p의 주소값(&p)은 0xB1입니다.

이때 포인터 변수 p가 가리키는 주소에 있는 내용(자료값)은 '*p'로 알 수 있습니다.
이렇게 어떤 포인터 변수가 가리키는 내용(자료값)을 읽거나 값을 대입하는 것을 '포인터 변수를 참조(reference)한다'라고 합니다.

int a =10, b, *p;
p = &a;
b = *p;

printf("a = %d, b = %d, *p = %d", a, b, *p);
a = 10, b = 10, *p = 10

void형 포인터

포인터는 선언시 자료형을 명시해 주어야합니다. 하지만 컴파일시 자료형을 결정하지 못하고 프로그램 실행시 자료형이 결정되는 경우에 'void형 포인터'를 사용합니다.
void *포인터명;의 형태로 선언하며 void형 포인터는 어떤 형태의 포인터라도 모두 저장할 수 있지만, 어떠한 자료형을 가지는 기억공간의 주소를 넘겨주는지 알려주기위해 명시적 형변환으로 자료형을 알려주어야합니다.

int a = 10;
char c = 'b';
void *p = NULL; //void형 포인터 p를 선언하고 아무것도 카리키지않게 NULL로 초기화

p = (int*)&a;
printf("*p = %d\n", *(int*)p);

p = (char*)&c;
printf("*p = %c\n", *(char*)p);
*p = 10
*p = b

포인터 연산

기억공간은 byte 단위로 이루어집니다. 자료형에 따라 가지는 기억공간의 용량이 다르며 대표적으로 char형은 1byte, int형은 4byte, double형은 8byte를 가집니다.

char *p 의 주소가 100 번지일때
char *(p+1) 의 주소는 100+(1*1)이 되어 101번지가 됩니다.


int *p 의 주소가 100 번지일때
int *(p+1) 의 주소는 100+(1*4)이 되어 104번지가 됩니다.

double *p 의 주소가 100 번지일때
double *(p+1) 의 주소는 100+(1*8)이 되어 108번지가 됩니다.

위처럼 포인터 변수에 +, -, ++, --의 연산자를 사용하는 연산을 '포인터 연산'이라고합니다.
(포인터 연산을 사용하여 계산된 숫자)*(자료형 byte 크기) 만큼의 주소를 이동합니다.

주의해야 할 점은
*(p+1)은 (p번지+1) 번지의 내용을 가리키고,
*p+1은 p번지의 내용에 1을 더한 값을 가리킨다
는 것 입니다.

포인터와 배열

char형 포인터

앞서 말한 것 처럼, C에서는 문자열이라는 자료형을 제공하지않기에, char형 포인터를 사용하여 문자열을 표현할 수 있습니다.

char *cp = "hello";								//char형 포인터 cp를 선언하고 문자열 hello를 대입합니다.

for(int i=0; *(cp+i) != '\0'; i++){				//cp가 1byte씩 커질때 NULL이 아닌 내용이 있으면 루프를 계속 돕니다.
    printf("*(cp+%d) : %c\n", i, *(cp+i));
}
*(cp+0) : h
*(cp+1) : e
*(cp+2) : l
*(cp+3) : l
*(cp+4) : o

cp를 char형 포인터로 선언하고, cp의 값은 문자열이 시작하는 주소가 됩니다.
그리고 cp는 char형 포인터이므로, *cp의 값은 문자열의 첫번째 문자인 'h'입니다.

포인터와 배열의 관계

앞서 배열은 '동일한 자료형을 갖는 자료를 연속으로 기억공간에 나열한 것'이라고 했습니다.
위의 예시를 참고하여 배열과 포인터로 문자열을 사용하는 것을 비교해보면

char s[3] = "hi"; : char형 자료를 연속으로 기억공간에 나열한 배열
char *s = "hi"; : char형 자료를 연속으로 기억공간에 나열한 포인터

즉, 위의 두가지는 작성 방식의 차이이지 비슷한 성질을 가짐을 알 수 있습니다.

배열은 선언시 배열명 자체가 배열의 시작 주소를 갖는다는 특징이 있습니다.

int i, array[5] = {1,2,3,4,5};

즉, array 자체는 int형 포인터고, 값은 array의 시작주소, 즉, array[0]의 주소와 같습니다.
따라서 다른 원소들의 주소도 array+i == &array[i]로 표현 가능합니다.
값을 얻고싶다면 *(array+i) == array[i]로 사용할 수 있습니다.

따라서 배열과 포인터는 서로 변환하여 표현할 수 있습니다.

char s[6] = "hello";

for(int i=0; i<5; i++){
    printf("s[%d] : %c\n", i, s[i]);
}
printf("\n");

for(int i=0; i<5; i++){
    printf("*(s+%d) : %c\n", i, *(s+i));
}
s[0] : h
s[1] : e
s[2] : l
s[3] : l
s[4] : o

*(s+0) : h
*(s+1) : e
*(s+2) : l
*(s+3) : l
*(s+4) : o

포인터 배열

'포인터 배열'은 포인터들을 배열로 나타낸 것 입니다. 동일한 자료형의 포인터가 여러개 사용될 때 이를 배열로 나타낸 것 입니다. 따라서 포인터 배열은 2차원 배열과 유사하지만, 각 포인터의 연속된 원소 수가 다르다면, 포인터 배열은 기억공간 절약 효과를 얻을 수 있습니다.

char *fruit[3];

fruit[0] = "apple";
fruit[1] = "banana";
fruit[2] = "melon";

fruit[0]의 시작 주소가 100번지라면
fruit[1]의 시작 주소는 106번지
fruit[2]의 시작 주소는 113번지가 됩니다.

100번지
a p p l e \0

106번지
b a n a n a \0

113번지
m e l o n \0

하지만 2차원 배열을 사용하면 제일 긴 문자열에 맞추어 기억공간을 일괄적으로 할당해야합니다.

char fruit[3][7] = {"apple", "banana", "melon"};

fruit[0]의 시작 주소가 100번지라면
fruit[1]의 시작 주소는 107번지
fruit[2]의 시작 주소는 114번지가 됩니다.

100번지
a p p l e \0 []

107번지
b a n a n a \0

114번지
m e l o n \0 []

(※ []는 빈칸입니다.)

제일 긴 문자열인 banana에 맞추어 7바이트씩 할당해주어야하고, apple과 melon은 6바이트가 필요함에도 7바이트씩 할당 받았기때문에 각각 1byte라는 기억공간의 낭비가 발생합니다.

이중 포인터

'이중 포인터(pointer to pointer)'는 포인터 변수에 다시 포인터를 할당합니다.
자료형 **포인터명;의 형태로 사용합니다. 이중 포인터가 가리키는 곳에는 또 포인터가 있으므로 주소값이 들어있습니다. 그리고 다시 그 포인터가 가리키는 주소를 찾아가 마침내 자료를 참조하게됩니다.

int i = 100, *p, **pp;

p = &i;
pp = &p;

printf("i = %d", **pp);
i = 100
profile
Coding Giraffe.

0개의 댓글