본문 바로가기

C언어

심화 C 언어 7 (포인터 - 포인터의 포인터, 함수의 파라미터의 포인터)

0-value, nullptr

: 포인터는 nullptr 이라는 값으로 0-value가 지정가능하다

0-value 란 각각의 변수가 가질 수 있는 무(zero) 값, 기본값, 최초값 등을 의미한다

0-value 에는 널문자, 0, false, nullptr 등이 있다

포인터도 역시 0-value를 가질 수 있으며 포인터에서의 0-value는 nullptr 이다

 

포인터에는 어떤 주소값이 들어가는데 이 주소값이 처음에는 초기화되지 않은 주소값을 가리키게 됨

이를 우리는 쓰레기 주소값이라고 함 

포인터 변수를 만들게되면 이 변수의 값이 무슨 값이 들어있는지 프로그래머는 예측을 할 수가 없다

그래서 변수를 만들고 나서 0 value로 초기화를 해 줘야함

#include <cstdio>
#include <cstdlib>


int main() {
    
    int* pointer = nullptr;

    // 보통 변수 = 0; 으로 초기화를 해주는데 포인터에 대해서는 C++에서는 nullptr 을 활용해서 확인 해 주면 된다
    // 변수에도 0-value가 있다, 보통 변수를 선언하고 0-value로 초기화해준다
    
    pointer = (int*)malloc(sizeof(int));

    // 한 개의 변수 크기만을 heap 영역에다가 초기화를 하게 되면
       이 malloc에 의해서 int 포인터변수의 주소값이 젤 앞 pointer으로 전달이 되기 때문에
       이제부터 이 포인터의 주소값은 nullptr이 아니게 됨
       따라서 포인터는 실제 주소를 가리키고 있다고 출력됨

    
    *pointer = 10// 실제 주소의 값에 넣기
    
    if (pointer == 0){
        printf("제로 value를 갖고 있습니다");
    }
    else {
        printf("포인터는 실제 주소를 가리키고 있습니다\n ");
        printf("%d", *pointer);
    }
    free(pointer);
    // 포인터의 값을 없애고 나면 포인터 안에 있는 주소값은 실제 값이 사라졌기 때문에
       heap 영역에서 아예 사라져 실제값이 아닌 이상한 메모리 값이 출력됨

    pointer = nullptr;
    // free를 하고 난 다음에는 이 포인터에 가리키고 있는 주소값이 잘 못 되지 않도록 nullptr로 만들어주는 절차가 중요

    return 0;
}

 0-value, 배열 초기화

배열의 모든 값을 0-value로 초기화를 하기 위해서는 다음의 표현이 가능하다

Type v[10] = { 0, };

 

혹은 포인터 타입의 배열일 경우

Type*v[10] = { nullptr, };

 

#include <cstdio>
#include <cstdlib>

struct Marine {
    int hp = 40;
    int atk = 6;
};


int main() {
    
    Marine* marine[40] = {nullptr,};
    // 구조체 포인터의 배열, Marine이라는 포인터를 40개 갖고 싶다,
        즉, Marine이라는 유닛을 갖고 있는 이 구조체의 포인터를 40개를 갖고싶다

    
    /* printf("%d", marine[0]);
      marine[0]은 주소값이 들어가 있고
      Marine[0]의 주소값이 Marine 이라는 어떤 그런 heap 영역이나 stack 영역에 어떤 다른 실체를 갖고있다,
      그래서 nullptr,로 초기화를 해주었다 */
    
    for (int i = 0; i < 40; i++){
        marine[i] = (Marine*)malloc(sizeof(Marine));
        // 포인터의 배열임, marine이 40개가 있는 것

        marine[i]->hp = 40;
        marine[i]->atk = 7;
        
    }
    for (int i = 0; i < 40; i++){
        printf("marine[%d].atk = %d\n", i, marine[i] ->atk);
        // marine i번째 index에 있는 이 포인터타입에 실제값을 따라가서 atk를 출력을 해라
        
    }
    
    return 0;
}

포인터의 포인터

: 포인터의 배열

 

이중포인터에서

  • 기본형 데이터 타입일 경우 : 2차원 배열일 가능성이 높음
  • 구조체 타입일 경우 : 포인터의 배열일 가능성이 높음

 

  • int** v;      // int의 2차원 동적배열일 가능성이 높음
  • Type** v; // Type*의 1차원 동적배열일 가능성이 높음, 포인터의 배열인가 생각

 

Marine** marine; 은 동적할당된 일차원 배열 (즉, 포인터의 배열인가) 생각하는게 편한게

분명 Marine* marine[20]; 이런식으로 배열을 포인터의 배열을 쓰기 위해서 만들었는데 

포인터의 배열을 동적으로 20으로 고정이 아니라

뭐 30, 15 이런 식으로 프로그램이 실행 될때마다 동적으로 만들고 싶기 때문에

Marine** marine; 으로 표현했을 것이다라고 생각하면 된다

 

기본형 데이터 타입이 int** 이런식으로 이렇게 두 개의 포인터를 활용해서 만들었다면

2차원 배열이라고 생각하면 된다 int** m; 이런식으로

 

int*** 이면 포인터의 3차원 배열, Marine*** marine; 이면 포인터의 2차원 배열이라고 생각

#include <cstdio>
#include <cstdlib>


struct Marine {
    int hp = 40;
    int atk = 6;
};


int main() {
    
    Marine** marine;
    
    marine = (Marine**)malloc(sizeof(Marine*) * 20);
    // 모든 포인터는 4byte, 4byte의 size에 곱하기 20개, 즉 Marine* marine[20]; 이렇게 되는것,
        Marine** marine; 을 쓸 거를 굳이 더블포인터를 써서 이중포인터를 만든 거, 의미는 거의 같다고 보면 됨

    
    for (int i = 0; i < 20; i++){
        marine[i] = (Marine*)malloc(sizeof(Marine) * 4);
        // 동적할당된 클래스를 1차원 배열을 만들 거니까

        for (int k = 0; k < 4; k++){
            marine[i][k].atk = 9;
        }
    }
    for (int i = 0; i < 20; i++){
        for (int k = 0; k < 4; k++){
            printf("marine[%d][%d]->atk = %d\n", i, k, marine[i][k].atk);
        }
    }
    return 0;
}


/* 결국 malloc 이라는 것어떤 그런 실제값을 heap 영역에다가 만들어 준다는 것이고
    이 크기를 얼마나 만드느냐 이거는 결국 곱하기에 의해서 만들어짐,
    이 곱하기가 빠지게 되면 한 개만 만드는 것

    한 개만 만들면 주소값을 따라가서 -> 표시로 그 주소값에 있는 값을 가져올 수 있지만
    우리가 또 이차원배열을 만들어서 실제값을 위 예제 처럼 저장해서 요거를 heap 영역에서 활용할 수도 있다
*/

함수의 파라미터의 포인터

파라미터의 포인터는 단순히 변수의 주소값을 받기 위함일 수 있지만

배열변수를 받기 위함일 수 도 있다.

 

구분하는 방법은 보통 배열일 경우 배열의 개수를 알려주는 변수를 따로 파라미터로 받게 된다.

 

  • int foo( int* v, int size ); // 배열일 가능성이 높음
  • int goo( int* v );               // 한개의 값만 필요로 할 가능성이 높음
#include <cstdio>
#include <cstdlib>


void foo(int* a, int count){
 // 파라미터의 포인터는 함수, int*포인터의 값을 의미 하는 것일수도 있지만 배열을 집어넣어라 라는 의미일 수도,
    배열일 때는 int count까지 받게 되는 경우가 많다,
    foo 라는 함수가 이 int 배열을 출력하는 함수다

    
    for (int i = 0; i < count; i++){
        printf("%d\n", a[i]);
    }
}

int main() {
    int myArray[5] = {4, 6, 10, 20, 100};
    foo(myArray, 5);
    
    return 0;
}

// 파라미터로서의 포인터그 자체가 포인터의 값을 집어넣을 수도 있지만 함수의 동작에 따라서 이건 배열의 이름을 집어넣어라는 걸 수도 있다
// 분명이 이게 어떤 배열이다라고 가졍했을 때는 오른쪽에 꼭 int count로 숫자를 받는다,
// 숫자를 받아서 그 숫자만큼 뭔가를 해야 되기 때문에 포인터를 받았으면 그 뒤에 배열의 크기를 받는지 확인하고 그 배열 크기를 받는다면 이거는 아마도 배열을 이용해서 출력을 한다던지 덧셈을 한다던지 그런 일을 하는 코드가 됨

const char*

: 하드코딩된 문자열의 타입

const char*는 하드코딩된 문자열(" ... ")을 넣기 위해 이콜 (=) 기호를 사용한다

const char*에는 strcpy를 사용할 수 없다

 

이 const char*의 경우 하드 코딩된 문자열을 넣기 위해 사용되고

이런 하드코딩된 문자열을 '파라미터로 받기 위해' 역시 정말 많이 사용됨

파라미터로 받을 수 있다는 의미는 이콜(=) 기호를 사용할 수 있다는 것

#include <cstdio>
#include <cstdlib>


void foo(const char* str){
    printf("%c", str[1]);
    // 이거도 일종의 배열이라 str[1] 출력시 e만 출력될 것임

}


int main() {
    foo("Hello World");
    // 이 const char 포인터타입은 앞으로 이런 문자 literal을 받는데 많이 사용, 하드코딩된 "로 엮어진 문자열
    
    return 0;
}


// 왜 이게 const char* 포인터인가?
// 프로그램이 시작되면서 끝날 때까지 절대 변하지 않는 어떤 문자열이다, exe 파일에 그냥 박혀있다
// 그렇기 때문에 이건 상수고 그렇기 때문에 const를 붙여야 된다

 

파라미터의 const char*

하드코딩된 문자열을 넣을 수 있지만,

문자열의 배열도 파라미터로 집어넣을 수 있다

 

char*, char 배열 등등의 변수이름은

모두 const char*의 파라미터 형식으로 받을 수 있다

 

const char* 포인터가 char 포인터가 될 수는 없지만 char 포인터는 const char 포인터가 되는데 전혀 무리가 없다

#define _CRT_SECURE_NO_WARNINGS
#include <cstdio>
#include <cstdlib>
#include <cstring>


void foo(const char* str){
    printf("%c", str[1]);
}


int main() {
    char* str = (char*)malloc(sizeof(char) * 12);
    strcpy(str, "Hello World");
    foo(str);
    
    return 0;
}
// char 포인터가 const* char 포인터가 되는데엔 아무 문제 없다, 반대는 안됨

그냥 const* char 포인터는

이런 식으로 우리가 " 로 묶어서 하드코딩해서 들어가는 문자열을 집어넣는데 활용한다

라는 정도로만 이해하면됨

const* char 포인터는

대부분 파라미터로 많이 쓰게 되고

아니면 이제 변수로 쓰게 될 때는 배열이나 나중에 배우게 될 std string 같은 걸 많이 쓰게 된다  

 

void* 

어떠한 포인터도 될 수 있는 것이 void* 

모든 포인터의 기본형

 

다른 포인터 타입에서 void*으로 변환될 땐 자동 형변환

void*에서 다른 포인터 타입으로 변환될 땐 형변환 명시

 

모든 포인터가 void* 포인터로 형변환이 될 수 있다, void* 포인터는 다른 포인터로 될 수도 있다

그래서 malloc 이라는 함수가 void 포인터를 return 함

void* 포인터를 리턴하기 때문에 이 값을 결국 size 크기만큼 byte를 할당해서 void 포인터로 return을 해 주고

그 void 포인터를 char 포인터로 형변환을 해서 집어넣게 됨

 

모든 포인터의 기본형 포인터이기 때문에

void* 타입에서 다른 포인터 타입이 될 때는 형변환을 프로그래머가 명시해야 함

때문에 malloc( ... ) 함수를 사용하고 포인터에 주소값을 넣을 때 malloc( ... ) 함수 앞에 형변환을 해줌

(malloc 함수의 리턴타입이 void*임)