ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • C언어 포인터, 배열, 포인터 연산, 동적 메모리 할당 (인프런)
    CS/C언어 2020. 1. 8. 20:08

    ※ 인프런 무료강좌 C로 배우는 자료구조(권오흠 교수님)를 보고 개인적인 복습을 위해 정리한 내용입니다.


    포인터 변수

    컴퓨터의 메모리(RAM)는 데이터를 보관하는 장소이다.

    메모리에는 바이트(8 bits) 단위로 주소가 지정되며, 모든 변수는 주소를 가진다.

    예를 들어 정수형 변수 int sum 1004번지~1007번지까지 4바이트의 메모리가 할당되어있다면,

    sum의 주소는 1004번지이다. 

     

    포인터는 이러한 메모리 주소를 값으로 가지는 변수이다. 

    다음과 같이 포인터 변수를 선언할 수 있다. 

    type-name * variable-name;

    variable-name : 선언된 포인터 변수의 이름

    * : variable-name이 포인터 변수임을 표시하는 기호. 

    type-name : 포인터 변수 variable-name에 저장될 주소에 저장될 데이터의 유형을 지정

     

    다음의 연산자들을 포인터 변수와 함께 사용할 수 있다. 

    연산자 & : 변수로부터 그 변수의 주소를 추출하는 연산자 (주소 추출)

    연산자 * : 포인터 변수에 담겨있는 주소에 있는 변수를 가져오는 연산자 (값 추출)

     

    포인터 변수를 선언할 때 변수명 앞에 있는 * 기호는 이 변수가 포인터 변수라는 의미로, * 연산자와는 다르다. 

     

    ※ 치환문

    더보기

    대입연산자를 사용해 치환문을 작성할 때, 좌항은 주소이고 우항은 값이다. 

    우항에 있는 값이나 우항에 있는 변수의 값을 

    좌항에 있는 변수의 '주소'에 저장한다. 

     

    예시 1)

    y = 1;

    변수 y의 '주소'에 1이라는 '값'을 담아라.

     

    예시 2)

    *ip = 0;

    포인터 변수 ip에 담긴 주소에 있는 변수 x의 '주소'에 0이라는 '값'을 담아라.

     

    포인터와 배열

    배열의 이름은 배열의 시작 주소(배열의 첫번째 원소의 주소)를 저장하는 포인터 변수로,

    다른 포인터 변수와 달리 그 값을 변경할 수 없다.

     

    int a[10];

    위와 같이 배열을 선언하면,

    메모리에는 a라는 변수의 공간이 할당되고

    배열의 원소인 a[0], a[1], ..., a[9]의 공간이 연속적으로 할당된다.

    이 때 a(배열의 이름)에는 배열의 시작주소가 저장되고, a[0], a[1], ..., a[9] 에는 배열의 각 원소에 담긴 값이 저장된다.

     

    함수에서 배열을 매개변수로 받을 때,

    int array[]int *array 는 둘 다 배열의 첫번째 원소 주소를 담은 포인터 변수이므로 완전히 같다.

    (int array로 받을 경우 배열이 아니라 정수형 변수를 매개로 받는 것이기 때문에, 그 자리에 배열을 전달하면 에러가 발생한다. 배열의 자료형은 int array[]와 같이 []를 붙여서 자료형이 배열임을 알려줘야 한다. )

     

    C언어에서는 "배열의 이름이 포인터 변수"라는 것이 많이 활용되므로 이것을 이해해야

    C언어를 잘 이해하고 다룰 수 있다.

     

    포인터 arithmetic

    int a[10]; 을 선언했을 때

    *a와 a[0]은 동일한 의미이다(*a == a[0]). 배열의 이름 a가 배열 첫번째 원소의 주소이고, * 연산자는 주소에 저장된 값을 가리키는 연산자이기 때문이다. 

     

    포인터로 더하기 연산(+ n)을 할 때는 n이 더해지는 것이 아니라, n x 자료형의 크기만큼이 더해진다. 

    예를 들어 int *a이고 a가 1000일 때 a++를 하면, 그 결과는 1001이 아니라 1004가 된다. (정수형 변수의 크기는 보통 4바이트)

    이 때, 배열의 원소들은 메모리에서 자료형의 크기만큼 공간을 차지하면서 연속적으로 할당되어 있기 때문에

    첫번째 원소의 주소에 4(1x4바이트)가 더해지면 두번째 원소의 주소가 된다. 

    두번째 원소의 인덱스는 1이기 때문에, *(a+1) == a[1] 이 된다.

    다른 원소들에도 마찬가지로 적용할 수 있기 때문에 *(a+i) == a[i] 이다.

     

    동적 메모리 할당 (Dynamic Memory Allocation)

    변수를 선언하는 대신, 메모리를 직접 요청해 할당받아서 데이터를 저장하는 것.

     

    malloc 함수(비슷한 다른 함수들도 있다)를 호출해 동적 메모리 할당을 요청하면, 

    요청받은 크기의 메모리를 할당하고 그 메모리의 시작 주소를 반환한다.

    주소값을 반환하므로, 포인터 변수에 반환값을 담아서 사용하면 된다. 

    이 때 malloc이 반환하는 주소는 타입이 없는 주소(void pointer, void *) 이다.

    그 공간에 어떤 타입의 값이 저장될지 알 수 없기 때문이다.

     

    만약 정수를 저장하고 싶다면 다음과 같이 int형 포인터로 변환해 할당하면 된다. 

    int *p;
    
    p = (int *)malloc(10*sizeof(int));
    
    if (p==NULL) {
    	/* 동적 메모리 할당이 실패할 경우, 예외처리를 해준다 */
    }

    malloc 함수의 인자로는 할당할 메모리의 크기가 들어가게 되는데,

    코드의 호환성을 위해 직접 값을 입력하기보다는 (예 : malloc(40) ) 위 코드에서처럼 sizeof를 이용하는 것이 좋다. 

     

    malloc으로 할당받은 메모리는 이렇게 배열처럼 사용할 수 있다.

    p[0] = 12;
    
    p[1] = 24;
    
    *(p+2) = 36;

     

    배열 키우기

    동적으로 할당된 배열은 크기가 부족한 경우 더 큰 배열로 교체할 수가 있다.

    원래 배열의 크기를 키운다기보다는

    메모리의 다른 곳에 크기가 더 큰 새로운 배열을 할당받은 다음, 원래 배열에 있던 데이터를 그곳으로 복사해 옮기는 것이라고 볼 수 있다. 

    int *array = (int )malloc(4**sizeof(int));
    
    array[0] = 1;
    
    array[1] = 2;
    
    *(array+2) = 3;
    
    
    int *tmp = (int* )malloc(8*sizeof(int));
    
    int i;
    
    
    for (i=0; i<4; i++)
    
    tmp[i] = array[i];
    
    array = tmp;

     

    동적 메모리 할당을 하는 대신 int array[4]; 처럼 일반적인 방법으로 배열을 선언했을 경우

    앞에서 말한 것처럼 배열의 이름이 배열의 시작주소를 저장하는 포인터 변수가 된다.

    그런데 배열이름 포인터 변수는 다른 포인터 변수와 달리 그 값을 변경할 수가 없다.

    그래서 array = tmp; 부분에서 에러가 발생한다. 

     

    반면에 동적 메모리 할당을 하면 포인터 변수 array의 값을 수정할 수 있기 때문에 배열의 크기를 키우는 것이 가능해진다.

    단, 동적 메모리 할당을 이용해 배열의 크기를 키울 경우 원래 배열이 할당되어있던 메모리가 garbage가 되는데

    C언어는 garbage를 자동으로 처리해주지 않기 때문에 메모리의 효율적인 관리에 조금 문제가 생길 수 있다.

     

    댓글

Designed by Tistory.