[C] 필요할 때 갖다 쓰세요.

장세민·2022년 8월 25일
0

📝 TIL

목록 보기
15/40

이제 드디어 C 언어에서 어렵고 어렵고 어렵다는 포인터에 들어간다.
차근차근 해보자 ㅎㅇㅌ..


지금까지 변수 선언으로 메모리에 공간을 확보하고,
그곳을 데이터를 넣고 꺼내 쓰는 공간으로 사용 했다.

그러나 변수는 선언된 블록 { }, 함수 내부로 사용이 제한된다.
포인터 개념은 사용 범위를 벗어난 경우도 데이터를 공유할 수 있는 방법이다.

📌 메모리의 주소

책꽂이에 책을 꽂아놓기는 했는데, 그 책을 어디에 꽂아 놨는지 찾을 수 없다면 읽을 수 없다.
메모리 역시 그 위치를 식별할 수 있어야 데이터를 넣고 꺼내 쓸 수 있을 것이다.

메모리의 위치는 주소 값으로 식별하고,
식별하는 주소 값은 바이트 단위로 구분된다.

예를 들어, int형 변수 a가 100번지부터 할당 되었다면 103번지까지 4바이트에 할당된다.
변수 선언 이후 4바이트 전체를 'a'라는 이름으로 사용한다.

📌 주소 연산자 &

저장된 공간은 이름이 아닌 주소로 사용할 수 있다.

🔔 여기서 주소란?

변수가 할당된 메모리 공간의 시작 주소를 의미한다.
시작 주소를 알면 그 위치부터 변수의 크기만큼 메모리를 사용할 수 있다.
주소는 주소 연산자 &를 사용해서 구한다.

&변수명

# 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;
}

주소 연산자 &는 단항 연산자 이며, 변수만을 피연산자로 사용하여 시작 주소를 구한다.

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

주소는 보통 16진수로 표기하기 때문에, 전용 변환 문자 %p를 사용해 출력하는 것이 좋다.
%p는 주소값의 데이터 크기에 따라 자릿수를 맞춰 16진수 대문자로 출력한다.


📌 포인터와 간접 참조 연산자 *

메모리의 주소는 필요할 때마다 계속 주소 연산을 수행하는 것보다
한 번 구한 주소를 저장해서 사용하면 편리한데,

자료형 *변수명;

포인터가 바로 변수의 메모리 주소를 저장하는 변수이다.
따라서 주소를 저장할 포인터도 변수 앞에 *만 붙여 변수처럼 선언하고 사용한다.

포인터 변수가 선언되면 일반 변수와 마찬가지로 메모리에 저장 공간이 할당되고
그 이후에는 변수명으로 사용할 수 있다.

# include <stdio.h>
 
int main(void)
{
	int a;			// 일반 변수 선언
	int *pa;		// 포인터 선언
 
	pa = &a;		// 포인터에 a의 주소 대입
	*pa = 10;		// 포인터로 변수 a에 10 대입
 
	printf("포인터로 a값 출력: %d\n", *pa);
	printf("변수명으로 a값 출력: %d\n", a);
 
	return 0;
}

이제 포인터 pa는 변수 a가 메모리 어디에 할당되었는지 기억하고 있다.
이렇게 포인터가 어떤 변수의 주소를 저장한 경우 가리킨다 라고 하고

pa → a
둘의 관계를 화살표로 간단히 표현할 수 있고, pa는 포인터이며 변수 a의 주소를 저장하고 있다는 뜻이다.

간접 참조 연산자 (*) = 포인터 연산자

포인터가 어떤 변수를 가리키면 간접 참조 연산자(*)를 사용하여 포인터로 가리키는 변수를 사용할 수 있다.

그런데!

입력할 때는 생각해 볼 문제가 있다.
scanf 함수는 입력할 변수가 메모리 어디에 할당되었는지 저장 공간의 위치를 알아야 한다.
따라서 입력할 변수의 주소를 인수로 준다.

포인터 pa를 통해 변수 a에 입력할 때도 마찬가지이다.
앞서 정의한 pa와 a의 관계를 통해

**pa == a
&a == &*pa
임을 알 수 있다.

즉, pa가 a의 주소를 저장하고 있으므로 바로 pa를 사용한다.

scanf("%d", &a);  // &a로 변수 a의 저장 공간 찾기
scanf("%d", pa);  // pa로 변수 a의 저장 공간 찾기

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

포인터가 어떤 변수를 가리키게 되면,
그 이후에는 간접 참조 연산자를 통해 가리키는 변수를 자유롭게 쓸 수 있다.

# 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;
}

int *pa, *pb;  // 7행. 포인터 동시 선언

가리키는 변수의 형이 같은 경우 포인터를 연속으로 선언할 수 있다.
즉, pa와 pb가 모두 int형 변수의 주소를 저장하는 포인터라면 콤마를 사용하여 한 번에 선언한다.

단, 각 변수가 포인터임을 뜻하는 기호 *는 변수마다 붙여야 한다.

만약 *을 붙이지 않는다면 포인터가 아닌 일반 변수로 선언된다.


int *pt = &total;     // 8행. 포인터 선언과 동시에 주소로 초기화
double *pg = &avg;    // 9행.

8, 9 행과 같이 포인터의 선언과 동시에 초기화 하는 것도 가능하다.
또한, 포인터는 가리키는 변수의 자료형과 동일하게 선언한다.

9행의 포인터 pg는 6행에 선언된 변수 avg의 주소를 저장하므로
가리키는 자료형은 'avg'와 동일한 double을 사용한다.

pa = &a;   // 11행. 포인터 pa에 변수 a의 주소 대입
pb = &b;   // 12행. 포인터 pb에 변수 b의 주소 대입

이렇게 선언한 포인터에 각각 'a'와 'b'의 주소를 저장하고 나면,
이후부터는 포인터에 간접 참조 연산자를 사용하여 가리키는 변수를 사용할 수 있다.

따라서

pt = pa + *pb;   // 14행. a값과 b값을 더해 total에 저장
쉽죠?


📌 const를 사용한 포인터

const 예약어를 포인터에 사용하면 이는 가리키는 변수의 값을 바꿀 수 없다는 의미로,
변수에 사용하는 것과는 다른 의미를 가진다.

  1. # include <stdio.h>
  2.  
  3. int main(void)
  4. {
  5. int a = 10, b = 20;
  6. const int *pa = &a;
  7.  
  8. printf("변수 a값: %d\n", *pa);
  9. pa = &b;
  10. printf("변수 b값: %d\n", *pa);
  11. pa = &a;
  12. a = 20;
  13. printf("변수 a값: %d\n", *pa);
  14.  
  15. return 0;
  16. }

6행에서 포인터 pa를 선언할 때 const로 상수화 했다.
만약 const가 일반 변수처럼 포인터 값을 고정시킨다면 9행에서 pa는 다른 변수의 주소를 저장할 수 없다.


그러나 출력 결과에서는 pa는 const의 사용과는 무관하게 변수 b의 주소를 저장하고
그 값을 간접 참조하여 출력하고 있다.

포인터에 사용된 const의 의미

바로 pa가 가리키는 변수 a는 pa를 간접 참조하여 바꿀 수 없다는 것이다.

만약 12행에서

*pa = 20;
과 같이 pa를 통해 a값을 바꾸고자 한다면 다음의 에러 메시지가 나타난다.

error C1266: l-value가 const 개체를 지정합니다.

+ 포인터에 const를 사용하는 이유

변수 a는 포인터를 통해서만 바꿀 수 없고 변수 a 자체를 사용하면 바꿀 수 있다.
포인터에 const를 사용하는 대표적인 예는 문자열 상수를 인수로 받는 함수이다.

문자열 상수는 값이 바뀌면 안되는 저장 공간이므로 함수의 매개변수를 통해서 값을 바꿀 수 없도록
매개변수로 선언된 포인터에 const를 사용한다.

ok?

profile
분석하는 남자 💻

0개의 댓글