본문 바로가기

C언어

심화 C 언어 6 (포인터 3 - 메모리 동적할당 / 배열)

 포인터와 배열

: 배열 변수명에는 '배열' 이라는 의미보다 '포인터' 라는 의미를 갖고 있다

 

배열 변수명의 의미

: 정확히 말하자면 다르지만 배열 변수명은 '포인터' 라고 볼 수 있다.

배열 인덱스를 집어넣어 값을 가져오는 [] 대괄호해당 주소값에서 몇번째 뒤쪽의 값을 가져오느냐 라는 의미를 갖고있다

배열도 근본은 포인터랑 같다

 

#include "prc.hpp"


int main(){
    int myArray[100]; // myArray 부분은 주소를 갖고있다

    int* pMyArray = myArray;
    // pMyArray 라는 배열 변수에 주소를 집어넣어 줄 수가 있다
    
    pMyArray[0] = 100;
    pMyArray[1] = 200;
    pMyArray[2] = 300;
    
    printf("%d\n", myArray[0]);  // pMyArray[0] 을 출력해도 같은 값을 출력
    return 0;
}

 

포인터 값을 가져오는 것의 의미

 (*pv) 의 형태로 가져온다는 것은 해당 주소값의 값을 가져온다는 것

 

 pv[0] 의 형태로 가져온다는 것은 해당 주소값에서 0번째 위치의 값을 가져온다는 것

 

즉 결론적으로 (*pv) 라는 표현과 pv[0]이라는 표현은 동일한 표현임

 

대괄호를 입력하고 숫자를 입력하는 것으로 해당 포인터를 '배열과 같은 방법'으로 활용할 수 있다

*변수명과 같은 방법으로 값을 가져올 수 있지만 변수명[0] 과 같은 방법으로도 값을 가져올 수 있다

#include "prc.hpp"


int main(){
    int v1;
    int* pv1 = &v1;
    
    printf("%d\n", pv1[0]); // 주소값을 따라가서 그 주소값의 0번째 index에 있는 값을 가져오라는 의미
    printf("%d\n", *pv1); // 위의 코드랑 같은 출력값
    
    return 0;
}

 

문자열 포인터

: 문자열을 포인터로 사용할 때에는 const char*를 활용한다

 

단순히 char*를 사용할 때는 동적할당 문자열을 사용할 때 사용하며

const char*를 사용하는 경우는 우리가 실제로 "" 큰따옴표로 묶인 문자열을 이콜(=) 기호로 할당할 때 사용

 

const는 상수 라는 뜻

상수는 프로그램 안에 직접적으로 박혀 있는 값을 의미

프로그램이 끝날 때 까지 값 수정 불가능

(그래서 free( ... )를 사용하게 된다)

#include "prc.hpp"


int main(){
    char input[100];
    const char* myString = "HelloWorld";
     // HelloWorld 라는 문자열은 프로그램상 직접적으로 박혀있다, 값 수정 불가능 strcpy 사용 안됨

    
    printf("%s", myString);
    return 0;
}

 

메모리 동적할당

: 배열이나 데이터를 컴퓨터의 메모리중 힙(Heap) 영역에 자리시키는 것

 

Heap 영역은 함수의 실행주기와 무관하게

프로그램 종료시까지 살아남은 메모리 공간이다

 

malloc ( ... )

: 메모리를 동적할당할 수 있는 함수

cstdlib 헤더파일에 선언되어 있음

 

Type v = (Type*)malloc(sizeof(Type));

와 같은 방법으로 Type의 크기만큼 1개의 공간을 Heap 영역에 할당한다

 

메모리 동적 할당은 cstdlib 헤더파일에 존재하는 malloc( ... ) 함수를 이용해서 할당되게 됨

이 동적 할당하는 함수인 malloc 함수를 이용하게 되면 컴퓨터의 메모리 Heap 이라고 하는 영역에 메모리가 할당이 되며,

이 Heap 영역의 메모리의 주소값malloc 이라는 함수가 반환해 줌

 

주소값이 반환되었으니까 포인터 데이터 타입이 반환이 되고

이 메모리를 얼마나 할당하느냐 (데이터 타입의 크기만큼만 할당하느냐)

혹은 데이터 타입의 크기보다 훨씬 크게 만드느냐에 따라서

단일 메모리 할당이 가능하거나, 배열 또한 메모리 동적 할당을 할 수 있게 됨

 

#include "prc.hpp"


struct Marine{
    int hp;
    int mp;
    char name[20];
};
Marine* foo(){ // Marine이라는 구조체 포인터를 return 하는 함수인 foo 만들기
    Marine* m = (Marine*)malloc(sizeof(Marine));
    (*m).hp = 100;
    (*m).mp = 50;

    strcpy((*m).name, "Jim Maynor");

    return m;
    // Marine의 m 구조체변수의 주소값을 return 해줌

    // foo라는 함수는 Marine 이라는 구조체 변수를 하나 만들고, 구조체 변수에 체력을 100이 들어가고 마력이 50이 들어감
}


int main(){
    
    Marine* m = foo(); // 주소값 m을 받아와서 Marine의 값의 구조체 포인터 변수에다가 주소값 집어넣어주기
    printf("%s", (*m).name);

    free(m);

    return 0;
}


// 함수가 실행되고 나면 그 함수의 변수는 모두 다 파괴가 되지만 메모리 동적할당을 하게 되면 동적할당된 메모리는 파괴되지 않고 heap 영역에 남아 있다. 여기서 파괴되는 것은 주소값이 파괴가 되는 것이지 실제로 동적할당된 메모리 그 자체에 실제 값은 파괴가 되지 않기 때문

// 함수의 life cycle, 함수가 시작되고 종료되고 함수가 살아 있고 죽음의 관계 없이 무결한 변수 데이터를 만들고 싶을 때 주로 사용

// 그리고 (Marine*)malloc(sizeof(Marine)); 이런식으로 동적할당한 것은 char name[20]; 이렇게 정적으로 할당했던 것과는 다르게
가변적 크기의 배열 변수를 만들 수 있게 해줌

 

-> 표현

: 동적 할당된 구조체의 포인터 변수의 

'멤버 변수'에 접근하기 위하여 사용할 수 있는 표현

동적 할당된 구조체의 멤버에 접근 가능

(*변수).멤버변수 ->멤버 와 같은 방법으로 멤버에 접근

 

Hero*h = (Hero*)malloc(sizeof(Hero));

h->agility = 48;

 

위 예제 코드의 (*m). 을 m-> 로 바꾸면 됨

 

free ( ... )

: cstdlib 에 포함된 함수

 

동적할당된 변수, 배열 등등은

함수의 라이프사이클에 관계없이 계속 살아있는데

이것은 자동으로 메모리가 반환되지 않는다는 것

즉, malloc 으로 할당된 메모리 공간은 프로그래머가 free 함수를 사용하기 전까지

지속적으로 메모리 영역을 차지하고 있다

 

이것은 free() 라는 함수를 이용해 해제하지 않으면 메모리 누수의 원인이 된다

 

동적 메모리 할당 - 배열

Type*v = (Type*)malloc(sizeof(Type) * size);

: 이는 근본적으로 배열이 포인터와 같은 원리이기 때문에

malloc으로 할당될 메모리의 양만 더 크게 잡아주면 동적으로 '배열' 을 선언할 수 있다.

즉, 동적 메모리 할당 함수인 malloc 함수로 가변 길이의 배열을 만들 수 있다.

 

size 에는 자신이 원하는 배열의 크기가 지정되면 된다

#include "prc.hpp"


int main(){
    // 동적배열할당을 활용을 해서 사용자로부터 입력을 받아
        입력을 받은 숫자만큼의 배열을 할당하고
        할당된 배열의 임의의 값을 집어넣은 다음에 출력 해 보기

    
    int* myArray;
    
    int input;
    fseek(stdin, 0, SEEK_END);
    printf("숫자를 입력하시면 배열이 만들어집니다 : ");
    scanf("%d", &input);
    
    myArray = (int*)malloc(sizeof(int) * input);

    // malloc 이란 함수동적으로 뭔가 메모리를 잡아주는 것 (배열일수도 배열이 아닐수도),
       return항상 포인터 타입이 나오기 때문에 이걸 (int*) 으로 캐스팅을 해 주는 것,
       캐스팅을 해주고 이것을 myArray주소값을 myArray에 집어넣어 주는 것,       
       실제 메모리는 heap 영역에 생기게 되고 그 메모리의 주소값을 return 해서 myArray에 집어넣어주게 되는 것,

      그러면 젤 윗 코드 int* myArray에 주소값이 들어가고 우리는 그 주소값을 이용해 배열처럼 활용하게 되는 것,
      sizeof몇바이트냐 이고 그 뒤 (int) 4byte다 라는 것을 의미,
      sizeof(4byte)4byte 크기의 배열이 input의 개수만큼 메모리가 할당이 됨,
      우리가 100이라는 숫자를 입력했을때 100*4byte 만큼 메모리가 할당이 됨
      그것을 우리가 대괄호[]를 활용해서 사용할 수 있게 됨 이것이 메모리 동적할당

    
    for (int i = 0; i < input; i++){
        myArray[i] = i*10;
    }
    for (int i = 0; i < input; i++){
        printf("myArray[%d] = %d\n", i, myArray[i]);
    }
    
    return 0;
}

과제형 연습 프로그래밍

사용자로부터 숫자를 입력받아

배열의 동적할당을 활용해 입력받은 숫자 만큼의 크기를 갖는 배열을 만들고

해당 배열에 2, 4, 6, 8, ... 로 증가하는 수열을 저장한 뒤 배열을 출력하세요

#include "prc.hpp"


int main(){
    
    int input;
    fseek(stdin, 0, SEEK_END);
    printf("숫자를 입력해주세요 : ");
    scanf("%d", &input);
    
    int* myArray;
    
    myArray = (int*)malloc(sizeof(int) * input);
    // malloc이라는 것은 void 포인터로 리턴을 하게됨, malloc이라는 함수를 실행을 하면 꼭 (int*)로 캐스팅 해줘야함
    // int 크기만큼 이렇게 sizeof(int) 하게 되면 4byte가 나옴
        4 *(곱하기) 입력한 사이즈(input) 만큼 48byte를 myArray에 만들어서 최초 주소값을 myArray에 할당해주게 되는 것

    
    for (int i = 0; i < input; i++){
        myArray[i] = i * 2 + 2;
    }
    
    for (int i = 0; i < input; i++){
        printf("%d\n", myArray[i]);
    }
    
    free(myArray);
    
    return 0;
}