[혼공C] 6주차 : 포인터
본문 바로가기

혼공 스터디/혼자 공부하는 C언어

[혼공C] 6주차 : 포인터

728x90
반응형

 

 

 

Chap 9 포인터

1. 포인터의 기본 개념

 

  • 변수 선언으로 메모리 공간을 확보하고, 데이터를 넣고 꺼내 쓰는 공간으로 사용
  • 변수명: 메모리 공간을 식별할 수 있는 이름
  • 하지만 함수 내부(선언된 블록)에서만 그 변수를 사용할 수 있음
  • 같은 변수명을 사용해도 블록이나 함수가 다르면 별도의 저장 공간을 확보하므로
    전혀 다른 변수로 사용되는 것!
  • 포인터:: 사용 범위를 벗어난 경우에도 데이터를 공유할 수 있게 도와줌

 

1) 메모리의 주소

  • 메모리: 데이터를 넣고 꺼내 쓰는 공간, 그 위치를 식별할 수 있어야 함
  • 프로그램이 사용하는 메모리의 위치는 주소 값으로 식별할 수 있음
  • 메모리의 위치를 식별하는 주소 값은 바이트 단위로 구분됨!
  • 이 값은 0부터 시작하고, 1씩 증가!!
  • 따라서 2바이트 이상의 크기를 갖는 변수는 여러 개의 주소 값에 걸쳐 할당됨

  • int형 변수a가 메모리 100번지부터 할당되었다면 100번지부터 103번지까지 4바이트에 걸쳐 할당됨
  • 변수 선언 이후에는 4바이트 전체를 a라는 이름으로 사용!

 

  • 따라서 a = 10;과 같은 문장은 메모리의 100번지부터 103번지까지 4바이트의 공간에 10을 저장하며,
    a + 20;과 같은 수식은 메모리 100번지부터 103번지까지 4바이트에 저장된 값과 20을 더하는 연산을 수행
  • 변수명으로 메모리 공간이나 값을 간단히 사용할 수 있음!!

 

2) 주소 연산자

  • 변수를 이름이 아닌 주소로 사용하는 방법에 대해 알아볼 참임
  • 주소: 변수가 할당된 메모리 공간의 시작 주소를 의미
  • 시작 주소를 알면 그 위치부터 변수의 크기만큼 메모리를 사용할 수 있음.
  • 주소는 주소 연산자 &를 사용해서 구함.
  • 예제를 통해 &의 사용법을 익히고 변수가 할당된 메모리의 상태를 확인하자!

변수의 메모리 주소 확인 // 9-1.c
#include <stdio.h>

int main(void)
{
int a;
double b;
char c;

printf("int형 변수의 주소 : %u\n", &a);
printf("double형 변수의 주소 : %u\n", &b);
printf("char형 변수의 주소 : %u\n", &c);

return 0;
}
int형 변수의 주소 : 2425354020
double형 변수의 주소 : 2425354056
char형 변수의 주소 : 2425354084

  • int형 변수 a의 주소를 출력한 것이므로
    변수 a는 20번지부터 23번지까지 4바이트에 할당되었음
  • double형 변수는 56번지부터 63번지까지 8바이트가 할당되었으며
  • char형 변수는 84번지 한 바이트에 할당되었다

 

[ 메모리 주소의 출력 변환 문자 ]

  • 주소는 보통 16지수로 표기
  • 주소를 출력할 때는 전용 변환 문자인 %p를 사용하는 것이 좋다.
  • %p는 주소값의 데이터 크기에 따라 자릿수를 맞춰 16진수 대분자로 출력함.
  • 설명의 편의를 위해 10진수로 출력하여 %u 변환문자를 사용했음.

 

3) 포인터와 간접 참조 연산자

  • 변수에 할당된 메모리 주소를 활용하는 방법을 알아보자.
  • 메모리의 주소는 필요할 때마다 계속 주소 연산을 수행하는 것보다 한 번 구한 주소를 저장해서 사용하면 편리함.
  • 포인터가 바로 변수의 메모리 주소를 저장하는 변수이다.
  • 따라서 주소를 저장할 포인터도 변수처럼 선언하고 사용함
  • 선언할 때는 변수 앞에 *만 붙여 주면 됨.
포인터의 선언과 사용
#include <stdio.h>

int main(void)
{
int a;
int* pa;

pa = &a;
*pa = 10;

printf("포인터로 a값 출력 : %d\n", *pa);
printf("변수명으로 a값 출력 : %d\n", a);

return 0;
}

  • 만약 변수 a가 메모리 100번지부터 103번지까지 할당되었다면 주소 값 100이 pa에 저장됨
  • 포인터 pa는 변수 a가 메모리 어디에 할당되었는지 그 위치를 기억하고 있음.
  • 이렇게 포인터가 어떤 변수의 주소를 저장한 경우 '가리킨다'고 말하며
    둘의 관계를 pa → a 처럼 화살표로 간단히 표현
  • pa는 포인터이며 변수 a의 주소를 저장하고 있음
  • 이를 간접 참조 연산자(*) 또는 포인터 연산자라고 함

 

 

4) 여러 가지 포인터 사용해 보기

  • 포인터가 어떤 변수를 가리키게 되면 그 이후에는 간접 참조 연산자를 통해 가리키는 변수를 자유롭게 쓸 수 있음
포인터를 사용한 두 정수의 합과 평균 계산
#include <stdio.h>

int main(void)
{
int a = 10, b = 15, total;
double avg;
int* pa, * pb;
int* pt = &total;
double* pg = &avg;

pa = &a;
pb = &b;

*pt = *pa + *pb;
*pg = *pt / 2.0;

printf("두 정수의 값 : %d, %d\n", *pa, *pb);
printf("두 정수의 합 : %d\n", *pt);
printf("두 정수의 평균 : %.1lf\n", *pg);

return 0;
}

 

'5) const를 사용한 포인터

  • const 예약어를 포인터에 사용하면 이는 가리키는 변수의 값을 바꿀 수 없다는 의미
  • 변수에 사용하는 것과는 다른 의미를 가짐
포인터에 const 사용
#include <stdio.h>

int main(void)
{
int a = 10, b = 20;
const int* pa = &a;

printf("변수 a 값 : %d\n", *pa);
pa = &b;
printf("변수 b 값 : %d\n", *pa);
pa = &a;
a = 20;
printf("변수 a 값 : %d\n", *pa);

return 0;
}

 

  • 포인터에 사용된 const의 의미
    :: pa가 가리키는 변수 a는 pa를 간접 참조해 바꿀 수 없다는 뜻

 

 

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

  • 포인터:: 주소를 저장하는 일정한 크기의 메모리 공간
  • 다른 주소를 저장하거나 포인터까지 대입 가능
  • 일반 변수와는 달리 대입 연산에 엄격한 기준이 적용됨

 

1) 주소와 포인터의 차이

  • 주소는 변수에 할당된 메모리 저장 공간의 시작 주소 값 자체
  • 포인터는 그 값을 저장하는 또 다른 메모리 공간
  • 특정 변수의 주소 값은 바뀌지 않지만, 포인터는 다른 주소를 대입해 그 값을 변경할 수 있음

  • 변수 a의 주소는 100이고, b의 주소는 200으로 프로그램 실행 중에는 그 값이 바뀌지 않는다.
  • 포인터 p는 a, b 중 어떤 주소를 대입하느냐에 따라 가리키는 변수가 바뀜
  • 주소는 상수, 포인터는 변수
  • 두 포인터가 같은 주소를 저장하는 일도 가능

 

2) 주소와 포인터의 크기

  • 포인터도 저장 공간이므로 그 크기가 있음
  • 포인터의 크기는 저장할 주소의 크기에 따라 결정됨
  • 크기가 클수록 더 넓은 범위의 메모리를 사용
  • 포인터의 크기는 컴파일러에 따라 다를 수 있으나 모든 주소와 포인터는 가리키는 자료형과 
    관계 없이 그 크기가 같다는 것에 변함이 없음
  • 주소와 포인터의 크기는 sizeof 연산자로 확인할 수 있음
주소와 포인터의 크기
#include <stdio.h>

int main(void)
{
char ch;
int in;
double db;

char* pc = &ch;
int* pi = &in;
double* pd = &db;

printf("char형 변수의 주소 크기 : %d\n", sizeof(&ch));
printf("int형 변수의 주소 크기 : %d\n", sizeof(&in));
printf("double형 변수의 주소 크기 : %d\n", sizeof(&db));

printf("char * 포인터의 크기 : %d\n", sizeof(&pc));
printf("int * 포인터의 크기 : %d\n", sizeof(&pi));
printf("double * 포인터의 크기 : %d\n", sizeof(&pd));


printf("char * 포인터가 가리키는 변수의 크기 : %d\n", sizeof(&pc));
printf("int * 포인터가 가리키는 변수의 크기 : %d\n", sizeof(&pi));
printf("double * 포인터가 가리키는 변수의 크기 : %d\n", sizeof(&pd));

return 0;
}

 

 

3) 포인터의 대입 규칙

[ 규칙 1 ] 포인터는 가리키는 변수의 형태가 같을 때만 대입해야 한다

  • 포인터끼리 대입 연산을 수행하면 여러 개의 포인터로 같은 데이터를 다루는 것이 가능
  • 그러나 규칙을 지키지 않는 대입 연산은 그 결과를 예상할 수 없다
허용되지 않는 포인터의 대입
#include <stdio.h>

int main(void)
{
int a = 10;
int* p = &a;
double* pd;

pd = p;
printf("%lf\n", *pd);

return 0;
}
-92559592117432107884277659021957555520241347761778250032873472.000000
  • 변수 p와 변수 pd는 모두 포인터지만 가리키는 자료형이 다르다
  • 컴파일러는 p에 저장된 값을 int형 변수의 주소로 생각하고,
    pd에 저장된 값을 double형 변수의 주소로 생각한다.
  • 따라서 pd에 p를 대입한 후에 간접 참조 연산을 수행하면 변수 a에 할당된 영역 이후의 
    할당되지 않은 영역까지 사용하게 됨.

 

[ 규칙 2 ] 형 변환을 사용한 포인터의 대입은 언제나 가능

  • 포인터가 가리키는 자료형이 다른 경우라도 형 변환 연산자를 사용하면 경고 메시지 없이 대입 가능
  • 물론 대입한 후에 포인터를 통한 사용에 문제가 없어야 함
double a = 3.4;
double *pd = &a;
int *pi;
pi = (int *)pd;
  • pi에 간접 참조 연산을 수행하면 변수 a의 일부를 int형 변수처럼 사용할 수 있다.
  • 이런 사용 방법은 포인터로 메모리를 직접 쪼개 쓰는 것이므로
    데이터가 메모리에 저장되는 방식을 충분히 이해하고 있어야 함.
  • 만약 *pi=10;과 같이 a의 일부분에 정수를 저장하면 정수와 실수의 데이터 크기와 저장 방식이 다르므로
    a에 저장한 실수 값은 사용할 수 없다.

 

 

4) 포인터를 사용하는 이유

  • 변수를 사용하는 가장 쉬운 방법은 이름을 쓰는 것이지만 
    임베디드 프로그래밍을 할 때 메모리에 직접 접근하는 경우나 동적 할당한 메모리를 사용하는 경우
    포인터가 반드시 필요하다.

 

(1) 두 변수의 값을 바꾸며 포인터 이해하기

포인터를 사용한 두 변수의 값 교환
#include <stdio.h>

void swap(int* pa, int* pb);

int main(void)
{
    int a = 10, b = 20;

    swap(&a, &b);
    printf("a:%d, b:%d\n", a, b);

    return 0;
}

void swap(int* pa, int* pb)
{
    int temp;

    temp = *pa;
    *pa = *pb;
    *pb = temp;
}

 

 

(2) 포인터 없이 두 변수의 값을 바꾸기

  • swap 함수에서 main 함수의 a, b를 이름으로 직접 사용하는 방법
다른 함수의 변수 사용하기
#include <stdio.h>

void swap(void);

int main(void)
{
int a = 10 b = 20;

swap();
printf("a:%d, b:%d\n" a, b);

return 0;
}

void swap(void)
{
int temp;

temp = a;
a = b;
b = temp;
}
1>C:\studyC\HonGongChap9\HonGong.c(165,28): error C2059: 구문 오류: ')'
1>C:\studyC\HonGongChap9\HonGong.c(174,10): error C2065: 'a': 선언되지 않은 식별자입니다.
1>C:\studyC\HonGongChap9\HonGong.c(175,4): error C2065: 'a': 선언되지 않은 식별자입니다.
1>C:\studyC\HonGongChap9\HonGong.c(175,7): error C2065: 'b': 선언되지 않은 식별자입니다.
1>C:\studyC\HonGongChap9\HonGong.c(176,4): error C2065: 'b': 선언되지 않은 식별자입니다.
변수의 값을 인수로 주는 경우
#include <stdio.h>

void swap(int x, int y);

int main(void)
{
int a = 10, b = 20;

swap(a, b);
printf("a:%d, b:%d\n", a, b);

return 0;
}

void swap(int x, int y)
{
int temp;

temp = x;
x = y;
y = temp;
}

 

728x90
반응형