본문 바로가기
혼공학습단 C

C언어 W6(9강 포인터)

by 알래스카코코넛 2023. 8. 16.
반응형

*본 포스팅은 한빛미디어의 '혼자공부하는 C언어(1판)'기준으로 작성되었습니다.

 

9-1 포인터의 기본 개념 

 포인터.... 여기부터 C언어가 고통스러워지는 지점이다. 이유는 일단 컴퓨터구조를 모르고 들어가면 이해하기가 너무 어렵고, 왜 굳이 써야하는지도 의문이 들 수 있다. 그러나 고급 C 코딩에서는 포인터를 사용하고, 무엇보다 자료구조로 넘어가면 연결리스트 등에서 사용하지 않을 수가 없어서 두고 갈 수는 없는 개념이다. 포인터 처음 배울때는 이게 어려운지 모르고 넘어가는게 베스트인데, 우리 교수님은 배운 그 주에 과제로 원형연결리스트 구현을 내셔서 상당히 원망스러웠다.... 혹시 이걸 보고 계신 교수님이 계시면 그러지 마십쇼. 학부생 울어요.


 

우선 내가 정리한 것을 알아보기 전에, 인공지능 친구의 포인터에 대한 설명을 들어보자. 

#include <stdio.h>

int main() {
    int num = 42;       // 정수 변수 선언과 초기화
    int *ptr;           // 정수형 포인터 선언

    ptr = &num;         // 포인터에 변수 num의 주소를 할당

    printf("num의 값: %d\n", num);       // num의 값 출력
    printf("num의 주소: %p\n", &num);    // num의 주소 출력
    printf("ptr이 가리키는 값: %d\n", *ptr);  // ptr이 가리키는 값 출력
    printf("ptr의 값(주소): %p\n", ptr);   // ptr의 값(주소) 출력

    *ptr = 100;         // 포인터를 통해 변수 num의 값을 변경

    printf("변경된 num의 값: %d\n", num);    // 변경된 num의 값 출력

    return 0;
}

상당히 훌륭한 설명이기는 하나, 개념을 완전 처음부터 알려주지는 않아서 &나 *의 의미를 모르겠다. 덕분에 컴퓨터교육과(내 전공) 밥먹고 산다. 그럼 처음부터 혼공C를 통해 포인터에 대해 알아보자. 


 포인터를 간단히 말하자면, 메모리의 주소 값을 변수 대신 이용한다는 것이다. 그동안 출력을 

 printf("%d", a);

이렇게 사용했는데, 여기서 a라는 변수를 직접 사용하는 대신, a의 주소 값을 변수처럼 사용한다. 예를 들어, 서울시 동대문구 123번지에 혼자 사는 김민수씨에게 택배가 온다면(단, 동명이인은 없다), 이 택배를 '김민수'에게 보내는 것과 '서울시 동대문구 123번지에 사는 사람'에게 보내는 것은 같은 의미다. 전자가 변수, 후자가 포인터다. 

 

그럼 이 '주소'는 어떻게 알아요? 프로그래머가 정하는 건가요? 라는 의문이 들 것이다. 답은 '프로그래머가 직접 정하지는 않는 대신, 코드를 통해 컴퓨터에게 주소를 알아낼 수 있다.' 이다. 아래 예시 코드를 통해, 주소를 알아내는 방법과 &기호를 살펴보자. 

#include <stdio.h>

int main(){
    int a; 
    printf("정수형 변수 a의 주소:%u", &a);  
    return 0;
}

이 코드를 보면, 이전에 우리가 사용한 

printf("%d", a);

에서 맨 뒤의 a 대신 &a를 사용한 것을 알 수 있다. 여기서 &가 바로 변수의 주소를 나타내는 기호이다. 즉, '&a'는 'a의 주소'와 동치다. 

 

그러면 이 주소를 변수에 저장하고 싶을 때는 어떻게 할까? 이전에 하던 대로 

int a;
int address_a;
address_a = &a

이렇게 변수에 숫자나 문자열 할당하듯이 하면 될까? 큰일난다. 주소를 변수에 할당할 때는, 기호 '*'(asterisk라고 부른다)를 사용해서 할당해준다. 

int a;
int *address_a;  //*로 address_a는 주소를 나타내는 변수입니다! 표현 

address_a = &a; address_a에 a의 주소 할당
*address_a = 10; //(=a) *address_a는 a처럼 쓰이며, 곧 a=10이라는 뜻이다.

이 부분이 살짝 혼란스러운데, 천천히 꼼꼼히 읽어보면 좀 낫다. 

포인터 유명한 밈. int가 있고, 그걸 가리키는게 int*이다.

그러면 여기서 그간 우리가 계속 입출력때 사용했던 

scanf("%d", &a);

가 

scanf("%d", address_a);

와 같은 의미라는 것을 알 수 있다. 왜? 위에 'address_a = &a'라고 정의했기 때문이다. 

이제 조금 더 긴 예시코드를 해석해보자. 

#include <stdio.h>

int main() {
    int a = 10, b = 15, sum; //int형 변수 a,b,sum 선언
    double avg;
    int *pa, *pb; //변수 a, b,의 포인터 *pa, *pb 마련 
    int *psum = &sum; //sum 의 포인터 *psum 
    double *pavg = &avg;// avg의 포인터 *pavg;
    
    pa = &a; //pa, pb란 무엇인지 정의 
    pb = &b;
    
    *psum = *pa + *pb; // = a+b = sum
    *pavg = *psum / 2.0; // = avg = psum/2.0
    
    printf("a와 b의 값: %d, %d\n", *pa, *pb);
    printf("a와 b의 합: %d\n", *psum);
    printf("a와 b의 평균: %.2f\n", *pavg);

    return 0;
}

//a와 b의 값: 10, 15
//a와 b의 합: 25
//a와 b의 평균: 12.50

이 코드를 주석 없이 해석할 수 있으면 포인터에 대한 이해가 어느 정도 되었다는 의미다!

9-2. 포인터 완전 정복을 위한 포인터 이해하기 

여기서 다시 포인터와 주소의 개념을 잡고가자. 헷갈리는 부분의 95%는 개념에서 나온다.

주소 변수에 할당된 메모리 공간의 시작 주소 값, 
포인터 그 값을 저장하는 또 다른 메모리 공간

모든 포인터의 크기는 같고, 모든 주소의 크기도 같다. 따라서 포인터끼리는 대입 연산을 수행할 수 있다!

단, 규칙이 있으니 반드시 지켜줘야 한다.

  1. 포인터는 가리키는 변수의 형태가 같을 때만 대입할 수 있다. 
#include <stdio.h>
int main(){
    int a = 10;
    int *p = *pa;
    
    double *p_error; //a와 p는 정수형인데, p_error은 실수형이다. 
    
    p_error = p;  //실수형 포인터에 정수형 변수 대입 - error
    
    printf("%lf\n", *pd);
    
    return 0;
}

위의 이 예시처럼 실수형 포인터에 정수형 변수를 대입하면 오류가 발생한다. 

 

     2. 형 변환을 사용한 포인터의 대입은 언제나 가능하다.

불가피하게 새로운 포인터 변수를 선언하기보다는 기존의 변수를 사용해야 할 때, 위의 법칙에 위배되지 않으려면 포인터의 자료형을 변환해주면 된다. 

double *p_error;
int *p1;
p1 = (int *)p_error;

이런 식으로 double형으로 시작했더라도, int형으로 바꾸겠다는 표시가 있으면 변수를 사용할 수 있다.

 

포인터를 사용하는 이유

사실 앞에서 언급했듯 포인터를 다른 단원들에서 배운 변수, 입출력처럼 흔하게 사용하지는 않는다. 그러나 임베디드 프로그래밍에서는 메모리에 직접 접근하거나 동적 할당을 사용하기 때문에 포인터가 반드시 필요하다. (그 외에는 사실 학부생 과정에서 시험기간에 제일 많이 쓰고, 실무에서는 크게 필요성을 못느낀다. 험험...)

이상으로 기본미션 포인터의 핵심 내용을 정리하고 공유하기는 끝!

 


추가미션 - Ch.09 도전 예제(p.261)

#include <stdio.h>

void swap(double *pa, double *pb); //두 실수를 바꾸는 함수 
void line_up(double *maxp, double *midp, double*minp); //함수 선언 

int main() {
    double max, mid, min;
    
    printf("실수값 3개 입력: ");
    scanf("%lf%lf%lf", &max, &mid, &min);
    line_up(&max, &mid, &min);
    printf("정렬된 값 출력: %.1lf, %.1lf %.1lf\n", max, mid, min);

    return 0;
}

//swap 함수
void swap(double *pa, double *pb){
    double temp;
    
    temp = *pa;
    *pa = *pb;
    *pb = temp;
}

//line_up 함수 
void line_up(double *maxp, double *midp, double *minp) {
    if (*maxp < *midp) {  
        swap(maxp, midp);
    }
    if (*maxp < *minp) {
        swap(maxp, minp);
    }
    if (*midp < *minp) {
        swap(midp, minp);
    }
}

아이디어를 내는게 조금 어렵지, 구현 자체는 간단하다. max가 mid보다 작으면 두 수를 swap 함수로 바꾸고, mid가 min보다 작으면 또 swap 함수로 바꾸고... 이런 느낌이다. 

 

이상으로 혼공학습단 C언어 모든 기본미션+추가미션이 끝났다!

얏호!

어느새 다다음주 월요일이면 신나는 개강이다 와

다행히 방학이 한 주 남았으니 담주는 혼공학습단 회고로 뵙겠습니당. 여러가지 후일담과 오프 더 레코드로 투 비 컨티뉴...

 

반응형

'혼공학습단 C' 카테고리의 다른 글

혼공학습단 10기 활동 회고  (0) 2023.08.23
C언어 W5(8강 배열)  (0) 2023.08.08
C언어 W4(7강 함수)  (0) 2023.07.24
C언어 W3(5 ~ 6강)  (0) 2023.07.20
C언어 W2-2(4강)  (0) 2023.07.13