C언어 복습

C언어 복습 16 (포인터)

Captic 2019. 7. 13. 11:40

참고: 정덕 - <컴맹을 위한 C언어>

♨ 개인적 해석이 다분한 글임으로, 인지하지 못한 오류가 있을 수 있습니다 

 

비트는 1, 0이 들어갈 수 있는 정보저장의 최소단위

메모리는 비트들이 1차원적으로 붙어있는 형태

메모리는 1바이트(8비트)마다 주소가 매겨져 있다

-> 포인터는 "메모리주소"를 의미 / 메모리주소를 저장할 수 있는 변수가 포인터 변수

 

▣ 변수의 주소를 사용하는 방법

 - 주소연산자(&)를 변수 이름 앞에 붙여 메모리상 어느 위치에 있는지 알 수 있다

 

  -> 어디서 많이 본 듯 하다면 scanf에서 사용자에게 입력을 받을 시 사용하던 그 친구이다

      scanf는 선언된 변수의 주소를 참조하여 입력값을 넣는 것이였다

 

 - 변수의 메모리주소(포인터)를 출력한 결과가 숫자+알파벳인 이유 ->16진수 형식

    2진수를 3개 혹은 4개씩 끊어서 8진수(3개)/ 16진수(4개)로 출력

     예) 10진수 65 = 2진수 01000001 이를 4개씩 끊으면 0100 / 0001 16진수로 표현하면 41이다

 

 포인터가 필요한 이유

 - 1. 함수의 인자로 배열을 넘기고 싶을 때

    만약 A라는 함수 내에서 B라는 함수를 호출할 때 B함수에 필요한 인자들이 B함수의 영역 앞으로 복사

      (B함수가 인자로 넘겨진 값을 사용할 수 있게 하기 위해)

     예) 외부함수 B - int Num(인자)를  외부함수 A에서 호출/사용

 

    이때 배열을 인자로 넘길 수 있게 되면 함수를 한 번 호출할 때 마다 배열에 있는 방대한 데이터가 복사

    -> 프로그램 실행 속도 저하

    -> 배열은 인자로 넘길 수 없게 되어 있다

 

   BUT! 인자로 배열의 메모리주소(포인터)를 넘겨주면 배열 자체가 아닌 해당 주소를 사용하여

          배열에 있는 데이터 사용 가능

 

 - 2. 다른 함수의 영역 안에 있는 변수의 값을 바꾸고 싶을 때

이 값의 결과가 a = b, b = a (a, b를 입력하면 b, a가 출력)일 것 같지만 그렇지 않다

 

처음 입력값과 다음 입력값의 순서를 교체했다고 생각했지만 입력한 순서대로 출력되었다

 

 왜이러지???

  -> swap함수에 '인자'를 넘겨 호출할 경우 인자가 swap함수 영역 앞으로 복사

      swap함수가 바꾼 것은 swap함수의 영역 앞에서 복사된 값일 뿐

      인자로 넘겨진 실제 변수(num1, num2)에 있는 값은 바뀌지 않는다

      이때 변수의 주소(포인터)를 인자로 받으면

      swap함수에서 해당 변수의 주소로 찾아가 변수의 값을 직접 수정하는 것이 가능

 

  ※ Call By Value // Call By Reference

    함수 호출 시 함수 인자에 값을 넘기는 2가지 방법

    앞선 상황에서 swap함수가 했던 것은 num1, num2 변수의 '값'에 초점을 두고 그 값을 복사해 사용하는

     Call by Value

 

    만약 num1, num2라는 변수에 초점을 두고, 각각의 포인터를 사용하여 변수를 '참조'하는

     Call By Reference

 

 ※ 게임, 동영상 프로그램 등은 메모리를 크게 확보해서 다량의 데이터 처리

   but 프로그램 실행시 확보되는 영역에는 제한 -> 힙 영역 필요

     *힙 영역: 메모리 안에서 마음대로 확보해서 사용할 수 있는 영역

  => 힙 영역에 확보된 데이터를 읽고 쓰기 위해서 메모리주소(포인터)를 이용해야 한다

 

 

 포인터 변수의 선언과 초기화

 - 포인터 변수를 선언할 떄 필요한 정보

    1. 포인터 변수의 이름

    2. 포인터 변수가 가리킬 데이터의 자료형

실제 주소는 이렇지 않지만 설명을 위해

위에서 int는 25000~25003번지까지 4바이트 메로리를 차지

그 시작주소인 25000이 int 변수의 주소

 -> 25000이라는 숫자를 저장하고 있는 포인터 변수는 이 int변수를 '가리킨다'고 표현      // 진짜?

 

 - 포인터 변수의 선언 방법

   포인터 변수는 포인터가 가리킬 자료형 뒤에 애스터리스크" * "기호(별표)를 붙여 선언

 

           자료형* 변수이름;

          예) char* str;

 

 - 포인터 변수의 초기화 방법

    포인터 변수를 초기화하기 위해서 주소연산자(&)를 이용, 변수의 주소를 포인터 변수에 대입

 

          ①  double number=971.002;

          ②  double* pnumber=&number;

 

  위 코드에서 pnumber는 ①number을 가리키는 'double형 포인터' 변수(=double형 변수 number의 시작주소)

 

  포인터가 아무것도 가리키지 않는다는 표시

   -> int* pointer=NULL;

  NULL은 아무것도 없다는 의미

 

 - 포인터 변수의 크기

    무엇을 가리키든 포인터 변수의 크기는 항상 동일

     -> 32비트 환경에서는 4바이트(32비트), 64비트 환경에서는 8바이트(64비트)

 

 ▣ 포인터의 가감산

 

 만약 위 코드의 결과(int형 변수 a의 주소값)가 100이라면

  printf("%p", pointer+1) = 104이다

  (-> int값이 4이기에 100~103이 int의 크기이고, 이에 +1을 하니까 104가 출력)

 

아래의 코드는 위의 코드와 동일한 결과가 출력된다

 

    int a;
    printf("%p", &a);

 

  a의 주소를 담고 있는 'pointer'와 '&a'가 가리키는 것이 동일하기 때문

 

위 코드에서 처럼 3[arr]의 값이 arr[3]의 값이 동일하게 나온다?!

  - 책(컴맹을 위한 C언어)에 의하면,

     배열 생김새가 A[b]일때

      A와 b를 더해서 나온 메모리주소에 있는 데이터를 참조하는 것(A+b의 메모리주소 참조)이기에 결과가 동일하다

     예외상황 3가지(이건 책에 잘 나와있다;)를 제외하면,

     '배열이름'(arr) 을 참조하려고 시도할 때 배열의 첫번째 요소(1.0)의 포인터가 참조 (= arr은 arr[]의 시작주소 포인터)

 

     ※ 이와 같이 배열이름을 가지고 배열을 포인터처럼, 포인터를 배열처럼 사용하는 것 처럼 보일 때가 있다

        포인터를 선언하고 거기에 배열이름을 넣으면 (바로 위 코드에서라면, double* arrptr을 선언하고 arrptr = arr;)

        오류가 없을 뿐더러, 포인터 변수와 인덱스로 배열의 값을 참조할 수 있기 때문이다

 

       이는 배열변수(이름)가 포인터 상수이기 때문이다.

       즉 배열변수 = 값을 이미 갖고 있고, 새로 할당받지 못하는 포인터 라서 포인터로서 사용할 수도 있는 것이다

 

▣ 역참조

 - 데이터를 직접 참조하지 않고 포인터를 이용해 참조하는 것 (≒간접참조)

 - 역참조연산자 : " * "(애스터리스크 기호)

   예)

 -> double형 포인터 변수 pnum에는 double형 변수인 num의 주소가 들어가 있다

      pnum 앞에 역참조연산자를 붙여 데이터를 쓰거나 읽으면

      pnum이 가리키는 num의 데이터가 읽히거나 써진다

 

 - swap함수 만들기(두 변수에 있는 값을 스왑하는 방법)

   1. 하나의 변수를 새로 선언한다

   2. 변수 A에 있는 데이터를 새변수로 옮긴다

   3. 변수 B에 있는 데이터를 변수 A로 옮긴다

 

   주의사항: 변수의 값을 직접 넘겨 호출하면 호출된 함수가 변수의 값을 저장 할 수 없다

                변수의 주소를 넘겨주면 역참조로 수정이 가능

                swap함수를 호출 할 때 int가 아닌 int형 포인터를 넘겨주어야 한다

▣ 동적 배열 만들어보기

   강제 형 변환 복습 강제 형 변환: 자료형을 강제로 바꾸는 것 

      -> 바꾸려는 데이터 앞에 " (자료형) " 을 붙여서 바꾼다

      예) int num=123; float realnum= (float)num;

 

 - 동적?

   동적할당이란? : 프로그램 실행 중에 프로그램에서 확보하지 않았던 메모리를 확보해주는 것

   

 - malloc함수를 이용한 메모리 확보

   ①역 할: malloc 함수를 이용하면 힙 영역을 원하는 만큼 확보

             (힙 영역: 자유롭게 확보해서 사용할 수 있는 메모리 영역);

   ②헤더 파일: stdlib.h

   ③인 자: 확보할 바이트 수, 자료형은 size_t(양의 정수)

   ④반 환 값: 메모리 확보에 실패시 -> NULL 반환

                                       성공시 -> 메모리의 첫 주소(포인터)를 void*형으로 반환

 

어느샌가 포인터 변수를 배열로서 사용하고 있다 그렇다 둘은 아내의 유혹마냥 * 점하나 차이일 뿐이였다

 - free함수를 이용한 메모리 해제

  메모리 확보 후 해제하지 않을 시 프로그램 종료시까지 메모리차지

  -> 반드시 해제 :" free(포인터 변수) "로 해제 가능

 

▣ 2차원 포인터

  포인터가 포인터를 가리키는 것

  2차원 포인터 -> 1차원 포인터의 주소

                           1차원 포인터    -> 데이터의 주소

 

▣ 2차원 포인터가 필요한 이유

  2차원 포인터는 함수 내에서 1차원 포인터의 값을 수정할 때 용이

   예) 동적 메모리 할당을 알아서 해주는 함수를 만들고 싶어서 아래와 같이

        void ddalloc(int*a, size_t n)

        {

          a = malloc(sizeof(int) * n);

        }

     이렇게 만들면 결과는 실패

      <-?: a, b, tep으로 a, b 값 맞바꿀때 처럼, dalloc함수 안에 호출된 인자 (int형 포인터) a는 복사된 것

           (Call by Value/실질적 수정X)

        ∴ 2차원 포인터를 활용해서 1차원 포인터를 혼내주면 값을 참조 호출할수 있다

 

▣ 2차원 포인터의 선언과 초기화

 - 1차원 포인터 선언 : 자료형* 변수이름;

 => 2차원 포인터 선언: 자료형** 변수이름;

    예) int a =10;

         int* aa= &a;

         int**aaa=&aa;

      비워두고 싶을때는(선언만 할때) int*** aaa=NULL;

 

▣ 2차원 포인터로 역참조 하기

 (위에서 실패 했던 동적 메모리 할당을 알아서 해주는 함수 만들기)

외부함수에서 int형 포인터의 포인터(int** a)를 역참조하여 동적할당 하고 있다( *a = malloc)

2차원 포인터에 역참조연산자(*)를 붙이면 1차원 포인터를 참조

 

 

+ 역참조연산자를 하나 더 붙이면 원시자료형 참조 가능

예)

 

*이 아무리 많아도 결국은 원본을 가져오게 되어있다

 

▣ void 포인터

 - void 포인터? : 포인터는 포인터지만 어떤 자료형의 데이터를 가리킬지 정해지지 않은 것

 - void 포인터로 역참조는 불가(자료형이 정해지지 않았기에) -> 강제 형변환 필요