특정 언어에 국한되는 문제는 아니지만, c/c++ 이하 c에서의 일반 변수와 포인터 변수와 메모리에 대한 얘기를 해볼까 합니다. 일단 메모리는 선형구조를 갖습니다. 그리고 메모리 각각의 주소를 가지고 있어요. 현실세계의 주소의 의미와 같습니다. 메모리라는 나라가 있다면 각 바이트는 그 나라의 국민이라고 하면 될까요?
앞서 살짝 언급했지만, 메모리의 주소는 바이트단위로 나누어집니다. 시작주소는 0이구요. 0부터 쭈욱 뻗어나갑니다. c에서는 이를 16진수로 표현하고 있습니다. 16진수 2자리가 1바이트라는건 아시죠? 그렇다면 0x00000000부터 ...까지 되어있겠네요. 한계는 거의없다고 봐도 과언이 아닙니다. 이게 물리적인 메모리(램)의 크기를 따라가는 것이 아니기 때문입니다. (실례로 어떤 프로그램 2개를 띄웠을때 같은 주소를 참조하고 있는데 값이 다른경우가 있습니다. 이 문제는 관련 책자를 찾아보세요.) 하지만 최대값은 0xFFFFFFFF이 되겠습니다. 여기서 잠깐, 왜 주소를 4바이트로 사용할까요? 그 이유는 4바이트만으로도 메모리내부의 주소를 모두 참조할 수 있기 때문입니다. 그래서 적정량을 4바이트로 두는 것이죠. 이것은 포인터 변수의 크기이기도 합니다. 1
일반변수의 포인터변수 모두는 메모리에 저장됩니다. 단, 저장되는 구조가 조금 다릅니다. 하지만 공통적으로 선언됨과 동시에 메모리의 어딘가에 해당변수의 크기만큼 배정됩니다.(해당변수를 sizeof한 만큼이 배정됩니다.) 하지만 저장됨과 동시에 일반변수는 값이 저장되는데 반해, 포인터변수는 메모리의 주소가 저장됩니다. 2
예를 들어 아래처럼 두 변수가 각각 0x0015F7FC, 0x0015F7F0위치에 배정되었다고 칩시다.
int n; // 0x0015F7FC
int* p; // 0x0015F7F0
int* p; // 0x0015F7F0
변수가 초기화되어있지 않았기 때문에 쓰레기값이라고 불리는 값 0xCCCCCCCC가 들어있을겁니다. 두 변수 모두 예외는 아닙니다. 일단 n의 주소로 가보면(메모리의 주소 입력부에서 &n입력),
역시 cc cc cc cc로 채워진 걸 볼 수 있습니다. 시작주소 + 변수가 차지하는 바이트수를 읽으시면 되는데, int형이기 때문에 4바이트를 차지하니까 0x0015F7FC부터 +4바이트를 읽으시면 됩니다.
마찬가지로 p의 주소에 가봅시다. (메모리의 주소부터 &p를 입력)
역시 cc cc cc cc로 채워진 걸 볼 수 있습니다. 위와 마찬가지로 시작주소 + 변수가차지하는 바이트수까지 읽어오시면 되는데, 포인터 변수이기 때문에 0x0015F7F0부터 +4바이트를 읽으시면 됩니다.
이번엔 다음과 같이 초기화를 사용해봤습니다.
재컴파일을 했더니 위치가 약간 달라졌네요. 각각 0x002AFE60, 0x002AFE54위치에 배정되었다고 칩시다.
int n = 321; // 0x002AFE60
int* p = &n; // 0x002AFE54
int* p = &n; // 0x002AFE54
먼저 n의 주소로 가봅시다.(&n)
0x002AFE60부터 4바이트를 읽어옵시다.
41 01 00 00 이 나오게 되는데 이걸 10진수로 바꿔보면 321이 아닌 1090584576이 나오게 됩니다..
이 문제는 리틀인디안 빅인디안(big-endian) 문제로, 인텔에선 리틀인디안(little-endian) 방식을 채택했기 때문에 다소 이상하게 나오는 것처럼 보이는 겁니다. 뒤에서부터 읽어오시면 됩니다. 00 00 01 41 그러면 321이 나오게 됩니다.
이번엔 p의 주소로 가봅시다. (&p)
0x002AFE54부터 4바이트를 읽어옵시다.
위와 마찬가지로 리틀인디안 방식으로 읽어오면,
00 2a fe 60이 됩니다. 바로 이것이 n의 주소가 됩니다. &n = 0x002AFE60
즉, 일반변수의 경우 일반적인 값이 저장되고, 포인터변수의 경우는 메모리의 주소가 저장되는 셈입니다. 변수의 주소라고 쓰려다가 고쳤는데, 그 이유는 다음과 같이 주소를 적당히 대입해도 가능하기 때문입니다. 하지만 이 방법은 다소 위험한 방식으로 거의 사용하진 않습니다만.. 일부 임베디드 프로그래밍에서는 자주 사용되는 듯 합니다.
int* p = (int*)0x002AFE60;
정리해보면 일반변수와 포인터 변수와의 차이는 내부적으로 저장되는 값의 형태 차이입니다.
int n;
int* p;
n = 100;
p = &n;
printf("%d", n ); // n의 값
printf("%d", p ); // p의 값(n의 주소값)
int* p;
n = 100;
p = &n;
printf("%d", n ); // n의 값
printf("%d", p ); // p의 값(n의 주소값)
여기서 잠깐, 주소를 표현하는데 있어서 아스트릭 연산자를 써서 해당주소의 값을 가져올 수 있습니다.
단, 가져올 때는 해당 변수의 자료형의 크기만큼 가져옵니다.
/* 321이 나옵니다. 먼저 (&n을 이용해서 잠깐 주소값으로(포인터변수로) 변형되는데, 그후 아스트릭 연산자가 있으므로 포인터 변수의 원형(int)의 크기만큼 값을 읽어옵니다.) */
int n = 321;
printf("%d", *&n );
/* 포인터 변수 자체가 주소값이기 때문에 아스트릭 연산자를 이용해 값을 가져올 수 있습니다.
p가 저장하고 있던 값(n의 주소)부터 p의 원형(int)의 크기만큼 가져옵니다.
321이 나옵니다.*/
int* p = &n;
printf("%d", *p );
/* 아래처럼 하면 포인터 변수의 값(n의 주소값을 가져옵니다.)
먼저 p의 주소를 가져오게되는데, 따라서 이중포인터형으로 잠깐 변형됩니다. 그 후 아스트릭 연산자가 있으므로 해당 포인터 변수의 원형(int*)의 크기만큼 값을 읽어옵니다. */
printf("%d", *&p);
// 아래처럼하면 321이 나옵니다.(n의 주소에 있는 값)
printf("%d", **&p);
// 아래처럼 하면 어떤값이 나올까요?
// p의 주소의 값(n의 주소)를 char*형으로 바꾼 후, 그 주소의 값을 가져옵니다.
// 방금 원데이터형의 크기만큼 가져온다고 했는데, 원래 int형이었지만, char형으로 바뀌었기 때문에,
// 1바이트만 가져오게 됩니다.
// 00 00 01 41 을 읽어와야 정상이지만, char형으로 캐스팅했기 때문에 41만 읽습니다.
// 즉 65가 출력됩니다.
printf("%d", *(char*)*&p );
int n = 321;
printf("%d", *&n );
/* 포인터 변수 자체가 주소값이기 때문에 아스트릭 연산자를 이용해 값을 가져올 수 있습니다.
p가 저장하고 있던 값(n의 주소)부터 p의 원형(int)의 크기만큼 가져옵니다.
321이 나옵니다.*/
int* p = &n;
printf("%d", *p );
/* 아래처럼 하면 포인터 변수의 값(n의 주소값을 가져옵니다.)
먼저 p의 주소를 가져오게되는데, 따라서 이중포인터형으로 잠깐 변형됩니다. 그 후 아스트릭 연산자가 있으므로 해당 포인터 변수의 원형(int*)의 크기만큼 값을 읽어옵니다. */
printf("%d", *&p);
// 아래처럼하면 321이 나옵니다.(n의 주소에 있는 값)
printf("%d", **&p);
// 아래처럼 하면 어떤값이 나올까요?
// p의 주소의 값(n의 주소)를 char*형으로 바꾼 후, 그 주소의 값을 가져옵니다.
// 방금 원데이터형의 크기만큼 가져온다고 했는데, 원래 int형이었지만, char형으로 바뀌었기 때문에,
// 1바이트만 가져오게 됩니다.
// 00 00 01 41 을 읽어와야 정상이지만, char형으로 캐스팅했기 때문에 41만 읽습니다.
// 즉 65가 출력됩니다.
printf("%d", *(char*)*&p );
'C/C++ > 프로그래밍 일반' 카테고리의 다른 글
컴파일러에 암시 또는 힌트 (0) | 2010.07.08 |
---|---|
리다이렉션(< or >)을 활용한 파일입출력 (0) | 2010.05.31 |
ZeroMemory (1) | 2010.04.29 |
memset, memcpy, memmove, memcmp 멤형제들 (0) | 2010.04.29 |
알수없는 위치에 메모리릭이 발생하는 경우 검출법(vs2008기준) (0) | 2010.04.20 |