카테고리 없음

[c++] - 포인터

chyam_eun 2025. 4. 9. 19:46

 

 

C++ 07.07 - 포인터 소개 (Introduction to pointer)

07.07 - 포인터 소개 (Introduction to pointer) '01.02 - 변수, 초기화 및 할당' 포스트에서 변수는 값을 보유하고 있는 메모리 조각의 이름이라는 것을 배웠다. 프로그램이 변수를 인스턴스화 할때 사용 가

boycoding.tistory.com

위의 블로그를 참고하였습니다! 

 

 

주소 연산자(&)를 사용하면 변수에 할당된 메모리 주소를 확인할 수 있습니다.

#include <iostream>
using namespace std;

int main(){
    int x = 5;
    cout << x << endl; // 5
    cout << &x << endl; // x의 메모리 주소 
    
    return 0;
}
  • x는 스택 영역에 저장됩니다. 

역참조 연산자(*)를 사용하면 특정 주소에 저장된 값을 불러올 수 있습니다.

#include <iostream>
using namespace std;

int main(){
    int x = 5;
    cout << x << endl; // 5
    cout << &x << endl; // x의 메모리 주소 
    cout << *&x << endl; // 메모리 주소에 저장된 값인 5가 출력됨
    
    return 0;
}

 

포인터

  • '메모리 주소'를 저장하는 변수입니다.
  • 자료형* 변수이름;   형식으로 선언됩니다.
  • 선언 시 초기화 되지 않습니다.
int* ptr;
double* dPtr;

int* ptr2, *ptr3; // 여러 포인터 선언 시 별표가 각 변수에 붙어있어야함

 

int val = 5;
int* ptr = &val; // 변수 값의 주소로 초기화
  • ptr도 스택에 저장됩니다.
  • ptr이 동적이라면 ptr은 스택에 저장되고, *p는 힙에 있는 공간을 가리키게 됩니다.
#include <iostream>
using namespace std;

int main(){
    int x = 5;
    int* ptr = &x;
    
    cout << x << endl; // 5
    cout << ptr << endl; // x의 메모리 주소 
    cout << *ptr << endl; // 메모리 주소에 저장된 값인 5가 출력됨
    
    return 0;
}
  • 포인터 변수의 자료형은 가리키는 변수의 자료형과 같아야 합니다.

자료형 반환

#include <iostream>
#include <typeinfo>
using namespace std;

int main(){
    int x = 5;
    cout << typeid(&x).name(); // int*를 반환한다.
    
    return 0;
}

 

재할당

int val1 = 5;
int val2 = 8;

int* ptr;

ptr = &val1;
cout << *ptr << endl; // 5

ptr = &val2;
cout << *ptr << endl; // 8

*ptr = 10;
cout << val2 << endl; // 10
  • 할당한 후에 다른 값으로 재할당할 수 있습니다.
  • 마지막에 *ptr은 val2와 같기때문에 *ptr값이 변경되면 val2값도 변경됩니다.

포인터의 크기

char* cPtr; // 1byte
int* iPtr; // 4byte
struct Something{ // 12byte
    int nX,nY,nZ;
}

cout << sizeof(cPtr) << endl; // 4
cout << sizeof(iPtr) << endl; // 4
cout << sizeof(Something) << endl; // 4
  • 포인터의 크기는 항상 같습니다. 
  • 32비트 실행 파일은 32비트의 메모리 주소를 사용하여 메모리 크기가 4바이트가 되고, 64비트 실행파일은 8바이트가 됩니다.

 

Null Pointer

  • null값을 가진 포인터를 의미합니다.
  • 아무것도 가리키지 않는다는 것을 의미합니다.
  • 다른 값을 지정하지 않는다면 null로 초기화를 해줘야합니다.
int* ptr {0};

int* ptr2;
ptr2 = 0;
  • ptr과 ptr2 둘다 null값으로 초기화합니다.
  • 아래처럼 할 때, *ptr2 = 0; 을 하면 안됩니다. 왜냐하면 값을 0을 저장 할 주소가 없기 때문입니다.
  • 포인터가 null이면 false로, null이 아니면 true로 반환됩니다.

C++11 이후의 Null Pointer

int* ptr {nullptr};
  • 드문 경우, 값 0을 사용할 경우 컴파일러에서 null pointer인지 정수 0을 의미하는지 확인할 수 없으므로 문제가 발생할 수 있습니다. 
  • nullptr을 포인터 모든 유형으로 변환해줍니다. 

배열과 포인터 

#include <iostream>
using namespace std;

int main()
{
    int array[3] = { 9, 7, 5 };
    cout << array << endl; // 1000번지 주소
    cout << &array[0] << endl; // 1000번지 주소

    return 0;
}
  • 배열 변수는 배열의 첫번째 요소의 주소를 가집니다.
  • 따라서 위의 array와 &array[0]를 출력하면 같은 주소가 나옵니다.
#include <iostream>
using namespace std;

int main()
{
    int array[3] = { 9, 7, 5 };
    cout << *array << endl; // 9
    
    int* ptr = array;
    cout << *ptr << endl; // 9

    return 0;
}
  • array는 고정크기의 배열입니다.

고정배열과 포인터의 차이

#include <iostream>
using namespace std;

int main()
{
    int array[3] = { 9, 7, 5 };
    cout << sizeof(array) << endl; // 12
    
    int* ptr = array;
    cout << sizeof(ptr) << endl; // 4

    return 0;
}
  • 고정배열인 array는 4 * 3 = 12를 출력하지만, 포인터인 ptr크기는 4가 나옵니다. 

함수에 배열로 인수를 전달하기

#include <iostream>
using namespace std;

void printSize(int* array)
{
    cout << sizeof(array) << endl; 
}

int main()
{
    int array[] = { 1, 1, 2, 3, 5, 8, 13, 21 };
    cout << sizeof(array) << endl; // 32

    printSize(array); // 4
    return 0;
}
#include <iostream>
using namespace std;

void printSize(int array[])
{
    cout << sizeof(array) << endl; 
}

int main()
{
    int array[] = { 1, 1, 2, 3, 5, 8, 13, 21 };
    cout << sizeof(array) << endl; // 32

    printSize(array); // 4
    return 0;
}
  • 위와 아래 코드의 결과는 같습니다.
  • 아래에서 인수로 전달한 int array[]를 포인터 구문이라고 합니다.
#include <iostream>
using namespace std;

void changeArray(int* ptr)
{
    *ptr = 5;
}

int main()
{
    int array[] = { 1, 1, 2, 3, 5, 8, 13, 21 };
    cout << array[0] << endl; // 1

    changeArray(array);

    cout << array[0] << endl; // 5
    return 0;
}
  • 전달될 때 배열이 포인터로 전달되기 때문에 함수에서 값을 변경하면 실제 배열을 변경하는것과 같습니다.

배열과 메모리

#include <iostream>
using namespace std;

int main()
{
    int array[] = { 9, 7, 5, 3, 1 };

    cout << &array[0] << endl; // 1000번지
    cout << &array[1] << endl; // 1004번지
    cout << &array[2] << endl; // 1008번지
    cout << &array[3] << endl; // 1012번지

    return 0;
}
  • 순차적으로 메모리에 배치됩니다. 
#include <iostream>
using namespace std;

int main()
{
    int array[] = { 9, 7, 5, 3, 1 };

    cout << &array[1] << endl; // 1004번지
    cout << array+1 << endl; // 1004번지
    
    cout << array[1] << endl; // 7
    cout << *(array+1) << endl; // 7    

    return 0;
}
  • array[n] 은 *(array + n)과 같습니다.

이중 포인터

int val = 5;

int* ptr = &val;
cout << *ptr; // 5

int** pPtr = &ptr;
cout << **pPtr; // 5

int** ptrPtr = &&val; // 오류가 난다.
int** ptrPtr = nullptr;
  • 이중 포인터를 직접 값으로 설정할 수 없습니다.
  • null로는 설정할 수 있습니다. 
int** array = new int*[10]; // 10개의 포인터 배열. int*를 10개 가진다. 1차원배열임 

int** arr = new int[10][10]; // 오류가난다.

int (*arr)[8] = new int[10][8]; //

auto arr = new int[10][9];
  • 동적으로 생성하는 코드입니다.
int** array = new int*[10]; 
for (int count = 0; count < 10; ++count)
    array[count] = new int[5]; // 각 행마다 열을 생성해줌
for (int count = 0; count < 10; ++count)
    delete[] array[count]; // 각 행마다 돌면서 해제해줘야합니다.
delete[] array;
  • 동적으로 생성하였으면 해제도 해줘야합니다.

-