포인터는 C에서 가장 중요하고, 또 가장 혼란을 많이 겪는 부분. 이 중요성 때문에 세장에 걸쳐, 11, 12, 17장에서 다룬다.

챕터11은 포인터의 기본을 중점적으로, 12, 17은 advanced uses를 소개


11.1 memory address와 pointer variable간의 관계

11.2 address, indirection operators

11.3 pointer assignment

11.4 how to pass pointers to functions

11.5 returning pointers from functions



11.1 Pointer Variables


pointer가 machine level에서 어떤 것을 나타내는가?

main memory는 bytes로 나누어지고, 각각의 byte는 여덟개의 bit를 저장한다.


 0

1

0


각각의 byte는 메모리의 다른 바이트로부터 구분하기 위한 고유의 address를 가진다.

만약 메모리에 n byte가 있다면, 0부터 n-1까지를 각 바이트의 주소라고 가정해 볼 수 있다.


Address

Contents 

 0

 01010011

 1

 01110101

 2

 01110011

 3

 01100001

 4

 01101110

 

 ...

 n-1

 01000011



executable program은 code와 data로 이루어짐. 각각의 프로그램은 하나 이상의 byte를 메모리에서 점유한다.

변수가 점유한 바이트 중 첫번째 바이트가, 그 변수의 주소이다.


 

...

 

 2000

 

 } i

 2001

 

 

 ...

 


위 그림처럼, 변수 i가 주소 2000, 2001의 바이트를 점유하고 있다면, i의 주소는 2000이 된다.

비록 주소가 숫자로 표현되기는 하지만, 그 값의 범위가 integer type이 허용하는 범위와 다를 수 있기 때문에 - 범위를 초과할 수 있기 때문에 -  평범한 integer 변수에 주소를 저장해서는 안된다. 대신, 특별한 pointer variable에 저장한다. 

pointer variable p에 변수 i의 주소를 저장할 때, p "points to" i라고 말한다. 다시 말해, 포인터는 그냥 주소이다. 그리고 포인터 변수는 그 주소를 저장하는 변수이다.



Declaring Pointer Variables


int *p;


평범한 변수를 정의하는 것과 비슷하다. 단지 별표가 변수 이름 앞에 와야 한다.

위 선언은, p가 type int의 object를 가리키는 포인터 변수라는 것을 나타낸다.

p가 변수에 속하지 않는 메모리 영역을 가리킬 수도 있을 때, variable 대신 object용어를 사용할 것임. 자세한 것은 17, 19장에서 설명됨


int i, j, a[10], b[20], *p, *q;


이렇게 다른 변수들과 같이 선언하는 것도 가능하다.

모든 포인터 변수는 오로지 지정된 타입의 object만을 가리켜야 한다(the referenced type). 


int *p;         /* points only to integers    */

double *q;      /* points only to doubles    */

char *r;        /* points only to characters */

referenced type에 대한 제한은 없다. 심지어, 포인터 변수는 다른 포인터를 가리킬 수도 있다.



11.2 The Address and Indirection Operators


포인터 만을 위한 연산자가 두 개 있다. 

1. 변수의 주소를 찾을 때, & (address) operator를 사용한다. 만약 x가 변수라면,  &x는 그 변수가 있는 메모리의 주소이다. 

2. 포인터가 가리키는 object에 접근하기 위해서는 * (indirection) operator를 사용한다. p가 포인터라면, *p는 p가 가리키는 object를 나타낸다.


The Address Operator


포인터를 선언하면 포인터를 위한 공간을 set(?) 하지만, 그 포인터는 어떤 object도 가리키지 않는다.

int *p;    /* points nowhere in particular */


p를 사용하기 전에 초기화하는 것이 중요하다. 포인터를 초기화하는 방법 하나는, 어떤 변수의 주소(더 일반적으로 말하자면 lvalue)를 할당하는 것이다. 이 때 &연산자를 사용한다.


int i, *p;

...

p = &i;    /* i의 주소를 변수 p에 할당함으로써, 이 statement는 p가 i를 가리키도록 만든다. */


포인터를 선언하면서 초기화하는 것도 가능하다.


int i;

int *p = &i;


//심지어 i가 먼저 선언되었다는 전제 하에, i의 선언과 p의 선언/초기화를 동시에 할 수도 있다.

int i, *p = &i;


The Indirection Operator


포인터 변수가 object를 가리키게 되면, * (indirection) operator를 사용해 그 object에 저장된 것에 접근할 수 있다.

예를 들어 p가 i를 가리키고 있다면, 다음과 같이 i의 값을 print 할 수 있다.


printf("%d\n", *p);


이러면 printf 함수는 i의 주소가 아닌 i의 값을 표시해 준다.

변수에 &연산자를 적용하면, 그 변수의 포인터가 된다.

포인터에 *연산자를 적용하면, 다시 원래 변수가 된다.

j = *&i;    /* same as j = i; */


p가 i를 가리킨다면, *p는 i의 다른 표현방법이다. *p는 i와 같은 값을 가지고 있을 뿐 아니라, *p의 값을 바꾸는 것 또한 i의 값을 바꾸게 된다.

(*p는 lvalue이기 때문에, 그것에 assign하는 것은 legal이다.)


주의:

초기화되지 않은 포인터 변수에 * 연산자를 적용해서는 안 된다. -> undefined behavior.

다음과 같은 코드는 쓰레기값을 띄우거나, 프로그램이 깨지거나, 또는 다른 이상한 결과를 발생시킬 수 있다.

int *p;

printf("%d", *p);    /*** WRONG ***/


*p에 값을 할당하는 것은 특히 위험하다. 만약 p가 우연히 유효한 메모리 값을 가지고 있었다면, *p에 값을 할당하는 것은 그 주소의 원래 데이터 값을 바꾸게 된다.

int *p;

*p = 1;    /*** WRONG ***/



11.3 Pointer Assignment


포인터를 복사하기 위해 assignment operator를 쓰는 것이 허용된다. 단 그 변수들이 같은 type 이어야만 한다.

int i, j, *p, *q;

p = &i;

q = p;


이제 *p를 바꾸든, *q를 바꾸든 i의 값이 따라서 바뀌게 된다.

위에서 q=p; 와 *q = *p; 는 전혀 다르다.


p = &i;

q = &j;

i = 1;

*q = *p;


이 코드는 p가 가리키는 변수의 값(즉 i의 값)을 복사해서 q가 가리키는 변수(j)에 할당하게 된다.



11.4 Pointers as Arguments


9.3에서 변수가 argument로 함수로 전달될 때, 그 변수의 값은 변하지 않음을 다뤘었다(because C passes arguments by value).

이 특성은 함수로 변수의 값을 바꾸고 싶을 때 골칫거리가 된다.

그러나 포인터를 활용하면 이 문제를 해결할 수 있다. 변수 자체가 아닌 변수의 포인터를 전달하면 된다.

함수를 통해 변수 x의 값을 바꾸고 싶을 때, 함수의 argument로 x가 아닌 &x, 즉 x의 포인터를 전달한다. 그리고 대응하는 파라미터 p(포인터)를 정의한다.

함수가 호출되었을 때, p는 &x의 값을 가지고 *p(p가 가리키는 object)는 x의 alias가 된다. function의 body에 나오는 *p는 x의 indirect reference가 되며, 함수가 x의 값을 읽기도 하고 수정할 수도 있게 한다.


// 배열에서 가장 작은 원소와 큰 원소 찾기

void max_min(int a[], int n, int *max, int *min);


int main(void) {

int b[N], i, big, small;

printf("Enter %d numbers: ", N);

for (i = 0; i < N; i++)

scanf("%d", &b[i]);


max_min(b, N, &big, &small);

printf("Largest: %d\n", big);

printf("Smallest: %d\n", small);

return 0;

}


void max_min(int a[], int n, int *max, int *min) {

int i;

*max = *min = a[0];

for (i = 1; i < n; i++) {

if (a[i] > *max)

*max = a[i];

else if (a[i] < *min)

*min = a[i];

}

}



Using const to Protect Arguments


x의 값을 함수에 전달하고는 싶지만, x의 값이 변하지 않게 하려면?

(굳이 변수의 값을 바꾸고 싶지도 않은데 포인터를 이용하는 이유는 효율성 때문이다. 변수가 많은 양의 저장공간을 필요로 한다면, 변수의 '값'을 전달하는 것은 시간과 공간을 낭비할 수 있다.)

변수의 주소는 함수에 전달되지만, 값을 바꾸지 않게 하고 싶다면 변수 선언시 파라미터 앞에 const를 써 준다.

void f(const int *p) {

*p = 0;    // WRONG: expression must be a modifiable lvalue라고 뜬다.

}



11.5 Pointers as Return Values


인터를 함수로 pass할 뿐 아니라, 함수가 포인터를 return할 수 있다. 이러한 함수들을 챕터13에서 다룰 것.


int *max(int *a, int *b) {

if (*a > *b)

return a;

else

return b;

}

int main(void) {

int *p, i, j;

p = max(&i, &j);

}


위 함수는 두 정수의 포인터를 받아서, 더 큰 쪽의 변수를 리턴한다.

max 함수를 호출하면, 두 정수 변수의 포인터가 전달되고 결과의 포인터가 리턴된다.

함수는 external variable의 포인터, 또는 static으로 선언된 local variable의 포인터를 리턴할 수도 있다.






+ Recent posts