본문 바로가기

Code.D IT 강좌/C언어

[C언어 강좌] 포인터 변수/ Call by Value vs Call by Reference

C언어 강좌

포인터 변수



포인터

C언어에서 가장 어렵고도 중요한 개념입니다.

우리는 지금까지 '주소'의 존재를 간과하고 주로 데이터값을 중점적으로 프로그래밍 해왔습니다.

그러나 컴퓨터의 관점에서 변수를 참조할 때, 주소를 먼저 참조한 뒤 데이터값을 참조하죠.

우리는 택배 내용물만 관심이 있었지 택배를 보낼 주소에는 관심이 없었습니다.

포인터 변수는 그 '주소'를 저장해줍니다.

여기서 주소란 데이터의 저장 위치를 나타내며 &(엠퍼센트)로 표현합니다.



포인터를 사용해야 하는 이유

포인터는 데이터에 직접 접근하는 것이 아니라 간접적으로 접근합니다.

(포인터 이외에도 함수 호출, 재귀함수 등등에서도 간접적으로 접근)

데이터에 접근하기 전에 주소에 먼저 접근한 뒤 데이터를 꺼내옵니다.

따라서 직접적인 사고방식보다 간접적인 사고방식에 중점을 두는데

이때, 필요한 능력이 '논리력'입니다. 포인터를 자유자재로 활용하기 위해서는

'논리력'이 필요하며 포인터를 사용하면 할수록 논리적으로 사고할 수 있는 능력치가 점점 증가하게 되죠.

논리력뿐만 아니라 코드를 효율적으로 짜기 위해서도 포인터는 필요합니다.

나중엔 하드웨어를 공부하면서 포인터는 더욱 중요해진다고 하는데요,

서로 다른 코드가 같은 결과물을 내더라도 프로그래밍 속도의 차이가 날 수 있으니

포인터는 프로그래머에게 꼭 필요한 개념입니다.



포인터 변수의 선언

포인터 변수 앞에 '*'를 붙여주면 포인터 변수의 선언이 됩니다.

*는 변수에 저장된 메모리 공간에 접근할 때 사용하는 연산자입니다.

*연산자는 변수의 메모리 공간의 위치를 가리킵니다.

Ex)

int *p;

char* p;

char *p; → *의 위치는 자료형 바로 뒤에 와도 되고 변수 바로 앞에 붙어도 상관없습니다.

double *p;

→ 포인터 변수 앞에 '*'을 붙여줍니다.



포인터 변수의 초기화

포인터는 NULL 값으로 초기화해주어야 합니다.

형식  자료형 *포인터변수명 = NULL;

Ex) int *pointer = NULL;

NULL 값으로 초기화해주지 않으면 쓰레기값이 출력됩니다.

NULL값은 아스키코드에서 0값과 같으므로 0으로 초기화해주어도 됩니다.

아스키코드 표 보러 가기

Ex) int *pointer1 = NULL;

int *pointer2 = 0;

포인터 변수를 NULL값 또는 0으로 초기화해주면 아직 저장된 주소 값이 없다는 의미입니다.



포인터 주의사항

포인터 변수에는 주소만 저장됩니다.

주의해야 할 사항을 포인터의 자료형이 일치하는 변수의 주소만을 갖습니다.

Ex)

int a = 10;

char b = 'A';

int *p = NULL;

p = &a; (o)

p = &b; (x)

--> b의 주소를 왜 갖지 못할까요?

포인터 변수는 int형으로 선언했기 때문에 자료형이 다른 b의 주소를 갖지 못합니다.



int a, b, c; 를 포인터 변수로 모두 선언하기 위해서는

int *a, b, c;라고 표현하면 될까요?

위의 코드는 변수 a만을 포인터로 선언되고 b와 c는 일반 int 자료형으로 저장됩니다.

변수 a, b, c를 모두 포인터로 선언하기 위해서는

int *a, *b, *c;라고 표현해야겠죠.



포인터 자료형의 크기

포인터 자료형에 상관없이

32비트 운영체제에선 4byte의 메모리 크기를,

64비트 운영체제에선 8byte의 메모리 크기를 가집니다.



#include <stdio.h>

int main(void){

     printf("%d %d %d %d %d \n", sizeof(char), sizeof(int), sizeof(float), sizeof(double), sizeof(long double));
     printf("%d %d %d %d %d \n", sizeof(char*), sizeof(int*), sizeof(float*), sizeof(double*), sizeof(long double*));

     return 0;

}



다음은 출력 결과입니다.






보신 바와 같이 포인터는 자료형과 관계없이 4byte or 8byte의 메모리 크기를 가집니다.

저의 컴퓨터는 64비트 운영체제인데 이클립스의 버전을 보니 32비트로 깔려있네요.(왜 그랬지)

(어쩐지 자꾸 4byte만 출력돼서 헷갈렸습니다;)

제 컴퓨터에 설치된 이클립스는 32비트 버전이므로 포인터 자료형은 4byte의 크기를 가지게 됩니다.



다음은 포인터를 이용한 예제입니다.



#include <stdio.h> int main(void){ int num = 10; //일반 변수 선언 및 초기화 int* ip = NULL; //포인터 변수 선언 및 초기화 ip = # //num의 주소 값을 포인터 변수 ip에 저장 printf("%d\n", num); //변수 num의 값 printf("%p\n", &num); //변수 num의 주소 값 printf("%p\n", ip); //포인터 변수 ip의 값(=&num) printf("%p\n", &ip); //포인터 변수 ip의 주소 값 printf("%d\n", *ip); //포인터 변수 ip가 가리키는 곳의 값(=num) printf("%p\n", *&ip); //포인터 변수 ip의 주소가 가리키고 있는 곳의 값(=&num) //*& → 서로 상쇄 return 0; }



다음은 출력 결과입니다.






일반 변수와 포인터 변수를 각각 선언한 뒤 초기화합니다.

일반 변수의 주소 값(&num)을 포인터 변수(ip)에 저장합니다.

일반 변수와 포인터 변수를 출력해줍니다.

위의 출력결과에서 *&는 상쇄되어 기호가 없는것과 같습니다.

ip의 주소(&ip)에 있는 데이터값을 출력하는 것이므로

ip=&num의 값이 출력됩니다.



참고

서식문자 %p는 주소를 표현할 때 쓰입니다.



Call by Value와 Call by Reference

Call by Value는 값에 의한 전달을 의미하며,

Call by Reference는 주소에 의한 전달을 의미합니다.



Call by Value

데이터값을 전달하는 기본적인 호출방법입니다.



#include 

//함수 func 선언
int func(int i);

int main(void){

     int n=10;
     int result=0;

      //함수 func 호출
      //값(n)을 전달 (Call by Value)
     result = func(n);

     printf("%d \n", result);
     printf("%d \n", n);

     return 0;

}

//함수 func 정의
int func(int i){

     i=i+1;

     return i;

}



다음은 출력 결과입니다.






Call by Reference

주소를 참조해서 함수를 호출합니다.

데이터값을 전달하는 것이 아닌, 값이 들어있는 '주소'를 전달합니다.

연산 결과에 따라 변수에 저장된 값이 변경될 수 있습니다.




#include 

int func(int *i);
int main(void){

     int n=10;
     int result=0;

     //함수 func 호출
     //주소(&n)를 전달(Call by Reference)
     result = func(&n);

     printf("%d \n", result);
     printf("%d \n", n);    //기존변수에 저장된 값이 변경이 됩니다.

     return 0;

}

int func(int *i){

     *i=*i + 1; 

     return *i;

}



다음은 출력 결과입니다.