이글은 "전문가를 위한 c++(개정4판)"을 학습한 내용을 직접 실습해보며 정리한 Review용 글입니다.
포인터(Pointer)
힙에 데이터를 저장하려면 포인터를 선언해야합니다. 포인터 선언 방법은 아래와 같이 타입 바로 뒤에 별표(*)를 붙입니다.
int* myIntegerPointer;
위 포인터의 의미는 myIntegerPointer의 변수는 int타입의 메모리 주소를 가리킨다라는 뜻입니다. 즉 myIntegerPointer변수 안에는 데이터가 들어 있는 힙메모리 주소값이 들어가게됩니다. 그렇다면 int*로 선언한 변수의 크기와 int로 선언한 변수의 크기는 다를까요? 그래서 테스트를 해봤습니다.
#include <iostream>
using namespace std;
int main()
{
int myInteger; // 정수형 변수 선언
int* myIntegerPointer; // 정수형 포인터 변수 선언
cout <<"myInterger의 크기 : " << sizeof(myInteger) << "바이트" << endl;
cout <<"myIntegerPointer의 크기 : "<< sizeof(myIntegerPointer) << "바이트" << endl;
return 0;
}
참고로 코드에서 사용한 sizeof()함수는 바이트 단위로 크기를 리턴합니다.
결과
myInteger의 크기 : 4바이트
myIntegerPointer의 크기 : 8바이트
사실 변수의 크기는 선언한 자료형의 크기와 같기 때문에 myIntegerPointer의 크기는 int* 의 크기와 같습니다. 그래서 다음과 같이 작성해도 됩니다.
#include <iostream>
using namespace std;
int main()
{
cout <<"int의 크기 : " << sizeof(int) << "바이트" << endl;
cout <<"int*의 크기 : "<< sizeof(int*) << "바이트" << endl;
return 0;
}
결과
int의 크기 : 4바이트
int*의 크기 : 8바이트
자료형에 대한 포인터의 크기는 운영체제가 32비트냐 64비트냐에 따라 다를 수는 있으나 자료형의 종류에 따라 그 크기가 변하지는 않습니다. 다음은 자료형에 따른 포인터의 크기를 테스트한 예제입니다.
#include <iostream>
using namespace std;
int main()
{
cout << "64비트 운영체제 메모리 주소(포인터) 크기" << endl;
cout << "-----------------------------------------" << endl;
cout <<"char*의 크기 : " << sizeof(char*) << "바이트" << endl;
cout <<"int*의 크기 : "<< sizeof(int*) << "바이트" << endl;
cout << "long*의 크기 : " << sizeof(long*) << "바이트" << endl;
cout << "float*의 크기 : " << sizeof(float*) << "바이트" << endl;
cout << "double*의 크기 : " << sizeof(double*) << "바이트" << endl;
return 0;
}
결과
64비트 운영체제 메모리 주소(포인터) 크기
-----------------------------------------
char*의 크기 : 8바이트
int*의 크기 : 8바이트
long*의 크기 : 8바이트
float*의 크기 : 8바이트
double*의 크기 : 8바이트
필자의 운영체제는 window 10 64비트 이며 visual studio또한 64비트(x64)로 빌드했습니다. 32비트(x86)로 테스트하고 싶으면 하위호환은 가능하기에 visual studio를 32비트로 빌드하시면 됩니다. 다음은 32비트로 테스트한 결과입니다.
결과
32비트 운영체제 메모리 주소(포인터) 크기
-----------------------------------------
char*의 크기 : 4바이트
int*의 크기 : 4바이트
long*의 크기 : 4바이트
float*의 크기 : 4바이트
double*의 크기 : 4바이트
포인터 변수를 선언할 때는 반드시 초기화를 해야합니다. 초기화 하지 않고 사용하면 메모리의 어디를 가리키는 지 알 수 없어서 대부분의 프로그램에서 크러쉬(crush)가 발생합니다. 그래서 포인터 변수는 항상 선언하자마자 초기화합니다. 포인터 변수에 메모리 주소를 당장 할당하고 싶지 않은 경우에는 다음과 같이 널 포인터(nullptr)로 초기화 해야합니다.
int* myIntegerPoint = nullptr;
널 포인터란 정상적인 포인터라면 절대로 가지지 않을 특수한 값이며, 0x0000000000000000와 같이 표시되는 포인터입니다. 이 값은 부울 표현식에서는 false로 취급합니다. 예를 들면 다음과 같습니다.
#include <iostream>
using namespace std;
int main()
{
int* myIntegerPoint = nullptr;
if (!myIntegerPoint) { //결과값이 true이므로 아래 문장 실행
cout << "널 포인터입니다.";
}
else {
cout << "널 포인터가 아닙니다.";
}
return 0;
}
결과
널 포인터입니다.
힙메모리를 사용하기 위해서는 new 키워드를 사용하여 다음과 같이 포인터에 메모리를 동적할당 해야합니다.
int* myIntegerPoint = nullptr;
myIntegerPoint = new int;
이렇게 하면 정수값 하나에 대한 메모리 주소가 myIntegerPoint변수에 할당됩니다. myIntegerPoint에 들어 있는 주소가 가리키는 메모리 공간에는 아직 값이 들어있지 않습니다. 만약 이 공간에 값을 넣고 싶다면 다음과 같이 역참조(dereference)를 해야합니다.
*myIntegerPoint = 10;
역참조란 포인터가 힙에 있는 실제값을 가리키는 화살표를 따라간다는 뜻입니다. 그냥 복잡하게 생각하지 마시고 포인터변수앞에 별표(*)를 붙이면 값을 나타낸다라고 이해하셔도 무방합니다. 바로 다음과 같은 경우가 가능하기 때문입니다.
int a = *myIntegerPoint;
포인터를 역참조하려면 반드시 메모리가 할당되어 있어야합니다. 널 포인터나 초기화하지 않은 포인터를 역참조하면 이상한 값이 나오거나 프로그램이 멈출 수도 있습니다.
포인터는 힙뿐만 아니라 스택과 같은 다른 종류의 메모리도 가리킬 수 있습니다. 원하는 변수의 포인터값을 알고 싶다면 주소 참조 연산자인 &을 사용합니다.
int a = 10;
int* myIntegerPoint = &a; //a변수의 메모리 주소를 할당
&a의 값은 정수값이 들어 있는 메모리 주소값이므로 myIntegerPoint 변수는 정수형 포인터 타입으로 선언해야합니다.
포인터 변수의 자료형이 구조체나 클래스라면 멤버에 접근할 때는 다음과 같이 사용합니다.
Employee* anEmployee = getEmployee();
cout << anEmployee->salary << endl;
Employee 구조체 안에 salary라는 멤버가 있다고 가정하고 anEmploee는 포인터 변수이므로 멤버에 접근할 때는 (->)연산자를 사용해야합니다. 만약 anEmploee를 역참조해서 접근하고자 한다면 다음과 같이 씁니다.
Employee* anEmployee = getEmployee();
cout << (*anEmployee).salary << endl;
역참조로 접근할 때 연산자는 (.)사용합니다.
'Program Language > c++' 카테고리의 다른 글
c++ 기초(스마트 포인터) (1) | 2023.01.13 |
---|---|
c++ 기초(동적 배열 할당) (0) | 2023.01.13 |
c++ 기초(스택과 힙) (0) | 2023.01.11 |
C++ 기초(이니셜라이저 리스트) (0) | 2023.01.10 |
c++ 기초(반복문(while, do/while, for, 범위 기반 for)) (0) | 2023.01.09 |
댓글