ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 메모리의 구조와 포인터
    C언어 2020. 3. 1. 21:56

    컴퓨터는 내부적으로 연산 즉 CPU가 동작하는 과정에서 정보를 가져오고 연산된 정보를 다시 저장하는 공간이 메모리라는 것은 이제 알 것이다. 메모리에 접근을 할 때 메모리의 위치정보를 식별할 수 있는 주소라는 개념이 있으며 이 주소라는 것을 통해 메모리에 저장된 값을 읽거나, 쓰거나, 옮기는 등 의 작업이 이루어진다.

    포인터는 이러한 메모리의 주소를 저장하는 특별한 변수이다. 포인터의 선언은 다음과 같다.

    위에서 설명하였듯 포인터는 메모리의 주소를 담는 변수이다. 따라서 포인터는 주소로 채워지게 된다.

    '&' 연산자를 통해 ndata 의 주소를 반환하여 int * (int 포인터) 자료형인 pndata라는 변수에 주소 값이 넘겨지는 것을 볼 수 있다. 위의  0x 0018 FF10의 주소에 0x 00 18 FF 1C라는 주소가 들어가 있으며 해당 주소를 따라가 보면 ndata의 주소이며 안에 값 역시 120이 들어가 있는 것을 확인할 수 있다.

    포인터의 선언과 사용

    포인터의 내용을 선언부와 사용할 때 를 나누어서 설명하겠다.

    1. 포인터의 선언

    포인터의 선언이 의미하는 내용은 위에 설명하였지만 자세히 설명하면 다음과 같다.

    선언부는 int * 와 같으며 이는 " 내가 사용하는 변수의 값은 무조건 주소로 인식할 것이다. 해당 주소의 메모리는 int 형식으로 해석할 것이다. " 와 같다. 따라서 포인터를 선언한다는 것은 뒤에 오는 모든 정보를 주소로 인식한다는 것과 같으며 주소로 변환한 뒤 해당 주소의 메모리에 접근했을 때 int 형식으로 해석한다고 볼 수 있다. 실제로 위의 ndata의 주소가 저장된 것을 볼 수 있다.

    위의 자료는 포인터의 개념에 대해서 매우 잘 설명해 준다.

    최상단의 pndata = 20 이라는 부분을 뜯어보면 다음과 같다. int *pndata를 선언하여 pndata 가 인식하는 모든 값은 주소로 인식한다. 따라서 상수 20이라는 값이 pndata로 할당됨에 따라 상수 20을 주소로 해석한다. 0x 00 00 00 14와 같이 변형되어 pndata 즉 포인터에 들어가 있다. 

    두 번째 pndata = ndata 부분을 뜯어보면 다음과 같다. 포인터가 인식하는 값은 주소로 보기 때문에 ndata 안에 있는 상수 120이라는 값이 주소로 변환되어 0x 00 00 00 80이라는 주소로 pndata 안의 값으로 저장된 것을 볼 수 있다.

    세 번째 ((int *) 20)는 상수 20이라는 값을 int에 대한 포인터로 강제 형 변환을 시킨 경우이다. 따라서 상수 20의 자료형은 포인터가 된다. 따라서 20이라는 상수가 강제로 주소로 변환되어 이렇게 변환된 주소의 메모리는 int 형으로 해석한다는 의미와 같다. 

     

    포인터에 대한 이해를 돕기위한 자료이다.

    위의 설명을 이해하였다면 충분히 이해가 갈 것이다. 포인터가 선언 됨에 따라 어떤 과정이 이루어지는지 이제 알 수 있을 것이다. 그렇다면 포인터의 사용에 대해서 다뤄 보자

     

    2. 포인터의 사용

    위의 자료는 int 포인터 변수 pndata에 접근한 모습이다. 자료 우측에 형식이 int * 즉 메모리의 주소 정보라고 나타나는 것을 볼 수 있다. 이는 위에서 설명한 대로 포인터에 주소 (0x 00 18 FF 1C) 가 들어 가 있으며 또한 +1을 하는 순간 해당 주소에서 int 자료형 1개의 크기만큼 이동한 것을 볼 수 있다. (0x 00 18 FF 20) 이와 같은 접근을 통해 포인터 변수는 역시 모든 정보를 주소로 인식하며 연산 또한 주소의 개념으로 연산하여 인식한다는 것을 확인할 수 있다. 

    위의 자료는 포인터를 사용할 수 있는 방법에 대해서 나타내 준다. '*' 연산자를 통해 해당 포인터에 접근을 하게 되면 포인터에 주소가 아닌 값에 접근 할 수 있다.포인터에 있는 주소 값에 해당하는 메모리를 찾아가 해당 메모리의 값을 가져온다. 이때 값은 ( int * pndata ) 즉 포인터를 선언할 때 기술 한 자료형에 맞게 해석한다. 따라서 메모리를 int 형식으로 해석하여 값을 가져온다.

     

    정보의 간접 지정과 직접 지정

    위의 자료는 직접 지정과 간접 지정의 차이를 확인이 나타내 준다.

    간접지정

    간접 지정의 경우 ndata라는 변수의 값을 +1 시키기 위해서 int 포인터 pndata를 선언하여 해당 변수에 ndata 변수의 위치 정보 즉 주소를 할당하였다. 다음으로 *pndata를 통해 포인터에 할당된 주소의 메모리로 접근하여 값을 1 증가시킨 것이다. 이때 값의 해석은 int 자료형이다.

    1. 변수 선언 및 초기화 (ndata)

    2. 포인터 ( int *pndata ) 선언 및 초기화 ( &ndata )

    3. 포인터를 ( *pndata ) 통해 &ndata의 메모리에 접근하여 int로 해석하며 메모리 안의 값을 +1

     

    직접 지정   

    직접 지정의 경우 0x 0018FF1C와 같이 직접 주소로 접근하여 해당 주소를 강제 형 변환 ((int *) 0x 00 18 FF 1C)의 값이 주소 상수로 인식되며 해당 주소 상수의 메모리에 접근할 때 값은 int로 해석한다. 따라서 *((int *) 0x 00 18 FF 1C)를 통해 0x 00 18 FF 1C 주소의 메모리에 접근하여 안에 있는 값을 int 자료형에 맞게 1 증가시키게 된다. 

    1. 0x0018 FF1C라는 상수를 int * 로 강제 형 변환 ((int) 0x0018FF1C) => 따라서 0x0018FF1C 상수를 메모리 주소로 인식하며 해당 메모리는 int 형식으로 해석

    2. *((int *) 0x0018FF1C) => '*' 연산자를 통해 ((int) 0x001FF1C)에

     

    두 개의 접근 방법 모두 결과적으로는 모두 0x0018FF1C이라는 메모리의 주소에 해당하는 값이 변경되었지만 접근 방법에서 차이가 있는 것을 인식해야 포인터를 온전히 이해하고 사용할 수 있다.

     

    요약

    요약하자면 다음과 같다 

     '*' 즉 포인터 자료형을 선언한다는 것은 변수 또는 상수의 값을 주소로 변환하여 인식한다는 의미와 같으며 '*' 연산자를 사용하여 해당 포인터에 접근을 한다는 것은 포인터에 있는 주소에 접근하여 값을 가져온다는 의미이다.  

    ((* int) 20) -> 상수 20을 주소로 변환 결과는 0x 00000014이다 또한 해당 주소의 메모리는 int로 해석

    따라서 ((* int) 20)+1 을 시행햘 경우 0x 00000014이 주소에서 int 의 크기만큼 1칸 떨어진 주소가 확인 됨 


    *((* int)20) -> 0x 00000014로 변환된 주소의 메모리에 있는 값을 확인하겠다는 의미이다.

    포인터는 쉽게 생각해서 '*'가 자료형과 함께 명시되는 선언 부라고 볼 수 있으며 선언부의 의미는 변수를 생성한다는 개념과 같다. 왜냐하면 뒤에 나오는 변수 또는 상수의 값을 주소로 변환해서 해당 주소에 해당하는 메모리를 int 자료형으로 해석하기 때문에 변수라고 볼 수 있음 또한 '*'으로 포인터 변수에 접근을 하면 해당 포인터 변수가 가리키는 주소의 메모리에 있는 값을 가져온다.

     

     

    상대주소와 기준주소로 문자(배)열의 크기 구하기

    상대주소 : 포인터에 있는 주소

    기준주소 : 배열의 주소

    처음에는 상대주소 즉 포인터를 선언할 때 넣어준 주소에 계속 1씩 증가시키면 문자를 입력 받는다고 했을 때 char 1개의 크기 즉 1byte 만큼의 메모리가 이동 할 것이다. 이렇게 이동 한다는 것은 문자 (배)열의 각 인덱스가 1씩 증가 했다고 볼 수 있고 다른 의미로는 기존에 있던 주소에서 1칸 떨어진 메모리의 주소로 이동한다고 볼 수 도 있을 것이다. 

    위와 같은 형태로 while 반복문을 사용하여 pnch 즉 포인터에 있는 주소 값을 1씩 증가시킴에 따라 ch 배열의 기준주소에 해당하는 메모리 부터 상대적으로 떨어진 주소의 메모리까지 순회하며 각 요소에 접근하게 될 것이다. 이때 만약 메모리에 있는 값이 NULL 문자와 같다면 while 반복문을 빠져나오게 될 것이고 pnch 포인터에 있는 주소의 정보는 NULL문자가 있는 메모리의 주소가 될 것이다.

    따라서 'H' 'E' 'L' 'L' 'O' '\0' 까지 기준 주소로 부터 5칸 멀어진 pnch + 5 까지 수행 되고 반복문 탈출의 과정이 이루어 날 것이다. 한번 확인해 보자

    ch 배열의 값이 0018FF14 주소의 메모리에 들어가 있는 것을 볼 수 있다. 또한 0018FF08 주소인 메모리 즉 pnch 포인터 변수에 0018FF14 라는 메모리의 위치정보가 담긴 것을 볼 수 있다. 이 상태에서 주소의 값에 +1을 해보도록 하겠다.

     예상대로 포인터에 담긴 주소의 값이 1증가한 것을 확인 할수 있다. 따라서 *pnch 즉 포인터가 가르키는 주소의 메모리에 접근하여 값을 char으로 해석하였을 때 해당 값이 'H' 'E' 'L' 'L' 'O' 중 기존의 주소로 부터 1칸 멀어진 'E' 라는 값이 확인 된다. 이렇게 하나씩 주소 값을 증가 시켰을 때 해당 주소의 메모리의 값이 '\0' 과 같을 때 반복문이 탈출 됨으로 pnch 포인터에 주소값은 NULL문자가 있는 주소가 될 것이다.

    여기서 ch[10] 이 가르키는 기준주소 즉 배열의 시작 요소의 주소와 pnch 포인터가 가르키는 상대주소 (NULL) 의 값을 각각 구한뒤 상대주소에서 기준주소를 빼게 된다면 해당 배열의 크기를 구할 수 있을 것이다.

    이를 통해 문자열의 본질은 배열이며, 상대주소에서 기준주소를 빼서 인덱스를 계산하는 방법, 배열의 인덱스가 0에서 시작할 수 밖에 없는 이유를 ( 배열에 접근할때 상대적 위치로 접근하기 때문 ex ) 0칸 떨어진 요소 ,1칸 떨어진 요소 .... ) 확인할수있다.

    'C언어' 카테고리의 다른 글

    메모리 동적 할당 및 해제  (0) 2020.03.04
    가상메모리  (0) 2020.03.03
    함수 기본 이론  (0) 2020.02.28
    배열의 주소와 이름의 관계  (0) 2020.02.26
    프로그램의 성능향상 ( 쇼트서킷 )  (0) 2020.02.24

    댓글

Designed by Tistory.