목차
- 주소와 포인터의 차이
- 주소와 포인터의 크기
- 포인터 연산
- 포인터로 swap하기
1. 주소와 포인터의 차이
좋습니다.
포인터에 대해 더 상세히 알아보죠...
이전에 말했지만, 컴퓨터 메모리는 이렇게 자료를 저장합니다.
흠, 음...
포인터는 일종의 포인터 변수라고 말했죠.
예를 들어 &a가 130이라면, 포인터 변수에 이를 저장한다면..
p = &a가 될 거고, p는 주소 130을 가리킬 겁니다.
주소 값은 컴퓨터 메모리 자체의 값이기 때문에 바꿀 수 없어요.
하지만 p가 가리키는 값 130은 바꿀 수 있죠.
가령 다음과 같은 코드가 있다고 생각해볼까요?
int a , b;
int* ap = &a;
int* bp = &b;
그러면 메모리에 할당된 상황은 다음과 같습니다.
이때 변수 a의 주소는 0000 1010이고, b의 주소는 0000 1020입니다.
이 주소는 프로그램 실행 도중에는 바뀌지 않아요.
하지만 포인터 ap, ab의 값은 변수이고, 이는 바뀔 수 있습니다.
예를 들어 bp = &a;로 ap와 bp 모두 a를 가리키게 할 수 있죠.
주소는 포인터처럼 간접 참조가 가능하지만, 본질적으로 주소는 상수이므로 대입 연산자의 왼쪽에는 올 수 없습니다.
2. 주소와 포인터의 크기
포인터 또한 변수의 한 종류이므로 크기가 있습니다.
포인터의 크기는 컴파일러에 따라 다르지만.. 본질적으로 모든 포인터들의 자료형의 관계없이 포인터의 크기는 같습니다.
이유는 위쪽 사진처럼, 포인터는 메모리의 주소값을 저장하고.. 주소값은 크기가 어느 메모리든 동일하기 때문이죠.
한번 sizeof 연산자로 알아봅시다.
#include <stdio.h>
int main(void)
{
char ch;
int i;
double db;
char* chp = &ch;
int* ip = &i;
double* dbp = &db;
printf("char형 변수의 주소 크기 : %d, %d\n", sizeof(&ch), sizeof(chp));
printf("int형 변수의 주소 크기 : %d, %d\n", sizeof(&i), sizeof(ip));
printf("double형 변수의 주소 크기 : %d, %d\n", sizeof(&db), sizeof(dbp));
return 0;
}
좋아요. 출력하면...
char형 변수의 주소 크기 : 8, 8
int형 변수의 주소 크기 : 8, 8
double형 변수의 주소 크기 : 8, 8
가끔 4가 나오는 경우도 있습니다. 컴파일 아키텍쳐가 32비트 기반이면 4가 나오고, 64비트 시스템이면 8이 나와요.
주소 출력을 보면 다음과 같이 나오는데, 이것이 16진수라는걸 생각하면..
0000 00BF 75FF F764
로 나눌 수 있고, 16진수는 0000이 2바이트(16비트)이므로 16자.. 8비트네요!
만약 32비트 기반이라면 0000 00BF 로 나오겠죠.
즉 변수형의 크기에 상관없이 포인터형의 주소값이 동일하답니다.
그러니까.. 저런 거에요.
3. 포인터 연산
포인터 연산의 규칙
음.. 포인터의 크기가 동일한 것을 이용해서 대입 연산을 쉽게 할 수 있을거 같지만.
실제로 포인터는 가리키는 변수의 형태가 같을 때만 사용해야 해요.
char 형태의 포인터는 char 변수형에만,
int 형태의 포인터는 int 변수형에만,
double 형태의 포인터는 double 변수형에만 말이죠.
이유는.. 위에 사진을 보시는 것처럼 각 포인터가 가리키는 공간이 다르기 때문이에요.
int 형태의 포인터로 char 변수형을 가리키면.. 뒤쪽의 3바이트를 포인터가 인식해버리겠죠?
이거 말고도 데이터 정렬(Alignment)에 관한 문제점도 있기 때문에, 포인터의 형태와 변수형을 잘 맞추도록 합시다.
형변환을 통한 포인터 연산
아, 물론 형변환을 통해서 대입하는 것은 문제없어요.
double a = 3.1415;
double* ap = &a;
int* bp = (int*)&a;
음.. 3번째 줄에서 int* bp = &int(a) 가 맞지 않나요?
라고 하기엔, int(a)는 임시값(temporary)을 생성해요. 이러면 컴파일 에러가 납니다.
int* bp = (int*)&a; 는
&a 를 사용해서 a의 주소에 접근하고,
이 주소연산을 (*)하고 int로 형변환하는거에요.
이후에 *bp를 하면 a의 메모리 앞 4바이트를 int로 읽어낼 수 있죠.
실수형을 이용한 포인터 연산
한 가지 묘수가 더 있는데, double이나 float같은 실수형 저장 방식이 정수부와 소수부로 구분된다는걸 기억하시나요?
바로 이 부분이죠.
잘 하면 double형의 4바이트만 잘라서 가져다 쓸 수 있을거 같아요.
위쪽 방법처럼, double 변수 주소를 int*로 캐스팅하면 int 포인터는 4바이트 단위로 접근하고..
double가 8바이트이므로, 이걸 2조각으로 나눌 수 있죠.
이러한 방식은 비트 단위로 double를 분석할 때 유용해요.
하지만 이런 경우에도 캐스팅을 명확히 해야 하고, 메모리 정렬에 유의해야 하며, 혹은 union을 사용해야...
4. 포인터로 swap하기
포인터의 장점 중 하나는, 함수 안에서 원본 변수의 값을 바꿀 수 있다는 거에요.
가령 변수 a와 b의 값을 각각 교환하는 방법을 알아볼까요?
#include <stdio.h>
int main(void)
{
int a = 10;
int b = 20;
printf("swap 전 : %d %d\n", a, b);
printf("swap 후 : %d %d\n", a, b);
return 0;
}
다음과 같은 함수가 있습니다.
이걸 어떻게 a와 b로 바꿀 수 있을까요?
임시 변수 temp를 추가해서?
물론 그것도 가능하겠지만.. 우리는 main 함수가 깨끗하기를 원해요.
새 swap 함수를 만들어서 값을 교환해봅시다.
#include <stdio.h>
void swap(int a, int b)
{
int temp;
temp = a;
a = b;
b = temp;
}
int main(void)
{
int a = 10;
int b = 20;
printf("swap 전 : %d %d\n", a, b);
swap(a, b);
printf("swap 후 : %d %d\n", a, b);
return 0;
}
이렇게 하면 값이 잘 바뀌겠네요! 결과를 보죠.
swap 전 : 10 20
swap 후 : 10 20
어라..아무것도 안 바뀌었네요..
이유는 간단해요, 함수는 a와 b의 사본을 전달받았고, main 함수의 a와 swap 함수의 a는 다르다는 겁니다.
그래서 swap 함수 내에서는 값이 바뀌지만, 함수가 종료된 후에는 main 함수의 a , b값에 영향을 줄 수 없어요.
물론 전역변수를 이용하면 되기는 하는데... 여기서는 포인터로 접근해 보죠.
전역변수는 오래 쓰기 좋지는 않습니다. 실제 프로그램에서도 포인터에 기반한 것이 훨씬 유용해요.
코드는 다음과 같아요.
#include <stdio.h>
void swap(int *ap, int *bp)
{ //포인터값을 한번 더 받아 정수로 전환
int temp;
temp = *ap; // temp에 *ap(10) 저장
*ap = *bp; // *ap(10)에 *bp(20) 저장
*bp = temp; // *bp(20)에 temp(10)저장
}
int main(void)
{
int a = 10;
int b = 20;
printf("swap 전 : %d %d\n", a, b);
swap(&a, &b); //포인터값(a, b의 주소)을 넘김
printf("swap 후 : %d %d\n", a, b);
return 0;
}
결과는 다음과 같아요.
swap 전 : 10 20
swap 후 : 20 10
성공이다! 아니.. 잠깐.. 주석을 보세요
본질적으로 위쪽에 실패했던 것이랑 같지 않나요..?
라기엔 조금 달라요.
위쪽은 a를 변수로 직접 받았다면 지금은 a가 아닌 a의 주소값을 받아 swap 함수가 사용하고 있어요.
즉, 위쪽에서 a를 변수로 직접 받고 swap 함수 내에서 a를 변환해 사용했다면..
지금은 a의 주소를 받아 swap 함수 내에서 a의 주소값을 참조해 사용해요.
메모리는 컴퓨터가 공통적으로 사용하죠?
위에서는 main의 a와 swap의 a가 서로 다르지만..
지금은 main의 a의 주소값을 swap에서 접근해서 사용하고 있는 거에요.
그럼 포인터 말고 함수의 반환값을 사용하는 거는요?
음.. C에서 함수의 반환값은 아쉽게도 하나밖에 주지 않아요. 이것도 결국 불가능하답니다...
물론 나중에 배울 구조체(struct)도 가능하기는 한데..
이건 번거로운 방법이라 그냥 포인터를 이용하거나 main 함수 내에서 처리하도록 합시다.
'프로그래밍 > C' 카테고리의 다른 글
배열을 처리하는 함수 (0) | 2025.06.06 |
---|---|
배열과 포인터의 관계 (2) | 2025.06.05 |
포인터의 개념 (0) | 2025.06.03 |
문자를 저장하는 배열 (0) | 2025.06.02 |
배열의 선언과 사용 (0) | 2025.06.01 |