본문 바로가기

프로그래머, 보안 관련 지식/자료구조

자료구조 시작전 알기 (2) CPU-Memory Architeture, Variables ,Addresses, Pointers

지난번에는 CPU Memory에 대해 알아 보았다. 이번 포스팅에서는 변수에 대해 이야기 해보자.

 

먼저 예시로 다음과 같은 간단한 C 코드를 돌려본다고 생각해보자.

 
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
 
int main()
{
    int a;
    char b;
    a = 10// 이진수로 나타내면 0000 0000 0000 0000 0000 0000 0000 1010
    b = 'k'// 이진수로 나타내면 0110 1011
    printf("%d %c\n" , a, b);
    return 0;
}

실행 결과 : 10 k

 

코드의 흐름을 간단하게 보면

a라는 변수를 만들고 int(정수형)를 담은 것

b라는 변수를 만들고 char(문자형)를 담은 것

a 는 숫자 10을 넣고,

b 는 글자 k 를 넣는다. 

그리고 printf함수를 통해서 a의 값과 b의 값을 출력한다.

 

이런식의 간단한 코드에서도 발생하는 것이 변수의 선언이다. 변수는 대체 무엇일까? 변수에 대해 정확히 정리해보자. 변수는 다음과 같은 속성을 가지고 있다.

 

변수의 속성들

  • 이름 : 사람을 위한 속성, 사람을 이름으로 생각하는게 편하기 때문
  • 주소 : CPU와 메모리가 사용하기 위한 속성, 기계는 주소로 생각하는게 편하기 때문
  • Type : (종류 ex) int, char)
    • 크기(in bytes or bits) : 몇바이트의 크기를 가지는가?
    • 해석(bit들의 의미를 어떻게 정할 것인가?) : 문자형인가 정수형인가

** 해석은 CPU가 하는 경우도 있지만 프로그램들이 정하는 경우가 대부분

 

그러나 우리가 프로그램을 짤때 변수를 선언하는 과정을 보면 

 

 

아래와 같이 선언하는데 이 상태에서는 변수 a 의 이름 type 과 거기 들어가있는 값을 알 수 있지만

주소는 기본적으로 나와있지 않다. 그렇다면 왜 보이지도 않는 주소를 우리가 이용할 필요가 있을까?

변수의 이름은 사람이 필요해서 쓰는것이고, 주소는 컴퓨터가 필요해서 쓴다. 그런데 프로그램을 짤 때 주소까지 알아야 할까?

우리가 주소를 이용해야 하는 이유는 다음과 같다.

 

  • 모든 변수를 다 만들수가 없다.

예를 들어 outlook 같은 프로그램을 보자.

수많은 변수들을 가지고 있을 텐데 프로그램을 짤 때 다 만들수가 없을 것이다. 

 

  • 메모리를 얼마나 쓸지 알수가 없다.

 

따라서 이를 해결하기 위해 메모리 '할당'이 필요하다.

 

예를 들어 int 를 담을 변수가 하나 필요할때 

 

c++에서

 

new int; ( 또는 malloc(sizeof int); )

 

라고 선언하면 int라는 타입의 변수가 생길것이다.

 

저번 포스팅의 그림을 보자

 

 

메모리의 빈공간이 많이 보일 것이다. 그렇다면 a,b,c 가 차지하고 있는 메모리 이외의 부분은 모두 쓰이는 영역일까?

메모리 중에 일부영역은 아무 변수도 쓰지 않는다 라고 약속을 한 공간이 있다.

 

그 공간을 보통 힙(heap) 이라고 부른다.

 

new int 라고 선언하면 안쓰는 4바이트를 찾아서 안쓰는 자리에다가 쓰이는 곳이라고 표시를 한다.

그리고 나서 앞으로 int 변수라고 사용해라 라고 결정해 준다.

 

그렇다면 이 새로 생긴 변수의 이름이 없지 않겠는가? 이러한 변수를 다루기 위해서는 주소가 필요할 수 밖에 없다.

 

 

예를들어 new int: 800번지에 앞으로 int를 저장할 수 있다 라고 알려주기 위해 주소를 리턴해주게 된다.

그러면 그 800번지를 어디다가 적어놓고 써야하는데 마찬가지로 800번지를 다루어야 하는데 이름이 없으니 주소로 다루게 된다.

그래서 이런식으로 표현하게 된다.

 

int *p;

 

p = new int;

 

p는 800이라는 값을 저장하는 변수

*p는 800번지를 저장한 주소

 

정리해보면 주소를 사용해야 될일이 있다는것이고 메모리 할당을 하게 되면 이름은 없는데 주소만 있는 변수가 있고

그 주소를 이용해서 그변수를 다룰수 밖에 없는데 그 주소를 저장할수 있는 변수가 필요하게 된다. 그것이 포인터이다.

 

포인터에 대해 정리하면,

Pointer

  • type 이 주소인 변수 : 저장하는 값을 주소로 해석하기로 약속한 변수
  • 결과 : 그 주소에 갔을 때 어떤 type의 변수가 있는지를 추가로 표시해야 함.

800번지에 int 변수가 저장이 되었다고 가정하면 p에는 단순히 800이라는 숫자만 들어있을 것이다.

 

주소라고 이미 약속은 했지만 여전히 모르는것이 800번지에 뭐가 있는지 모른다는 것이다.

 

int인지 char인지 float인지 알수 없다는 것이다. 그래서 주소 *p 변수앞에 int를  써주는 것이다.

 

그러면 int *p 라는 뜻을 하나 하나 해석해보자

 

p가 포인터 변수라는 뜻이고 예를 들어 800이 들어 있다.  *은 p의 있는 값을 꺼내서 꺼낸 값과 같은 주소로 간다라는 뜻이 된다.

*p는 800번지의 변수를 말하는 것이다.

그렇게 800번지 (목적지에) 가면 int 형 변수가 있다는 것이다.

 

그리고 주소를 다루기 위해 * 뿐만 아니라 다른 기호도 사용하는데 아래의 코드를 보자.

 

p = &a;

 

이게 무엇을 뜻하는 것일까?

&는 주소의 변수를 꺼낸다는 의미이다. 따라서 p 에 a의 주소값이 담겨 있다는 뜻이다.

 

그렇다면 다음과 같은 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
 
int main()
{
    int a;
    char b;
    
    int *p;
    int **q;
    
    p = &a;
    *= 30// a = 30;
    return 0;
}

11번째 줄의 *p =30 의 뜻은 결과적으로는 a = 30 과 같은 결과를 나태낸다.

 

왜 그렇게 될까? 

 

p=&a 로 인해 p에는 a의 주소값이 담기게 된다. *p 는 p에 담겨 있는 값의 번지(주소)로 향하라는 의미이다.

따라서 *p =30 은 p에 담겨 있는 주소에는 30이라는 값이 있다는 뜻이다.

앞에서 p에 담겨있는 주소값은 a의 주소값과 같다고 했다.

따라서 a 에는 30이 담기게 된다.

 

 

이번에는 한단계 더 나아가서 좀 더 어려운것을 살펴보자

 

9번째 줄의 int**q 는 뭘까? *이 하나가 더 붙게 되었다. 천천히 해석을 해보도록 하자

일단 *q는 무엇을 의미하는 것이였나?

q에 들어있는 값은 주소값이고 그 주소로 가보면 어떤 변수가 있다는 뜻이다. 그 어떤 변수를 K라고 해보자

**q 는 그 K번지로 향하면 또 다른 변수가 있다는 뜻이다. 

그 변수가 int(정수형) 이라는 의미에서 int가 붙게 된다.

 

처음 들었을 때 무슨 의미인지 한 번 생각해 볼 필요가 있었다. 다시 그림을 그려서 생각을 해보자

int **q의 도식화, 임의의 값을 넣어서 생각해보자

 

그림으로 그려보니 훨씬 낫다.

이렇게 *도 두번 사용이 가능하다고 알게 되면 한가지 의문이 생길것이다.

q= & &a 이런식으로도 사용이 가능할까? (&&은 아예 다른뜻의 기호가 되어버리기 때문에 한칸 띄어서 표현하였다.)

a의 주소를 꺼내서 그 주소 값의 주소를 q에 담는다 이런식으로 말이다.

 

사용 가능 할것처럼 보이지만 이런식의 사용은 불가능 하다.

&a 를 하게 되면 a의 주소값을 꺼내오게 되는데 &a 자체는 그냥 어떤 숫자일 것이다. 그냥 어떤 값에 주소는 우리가 알수 없다. 따라서 &를 두번적으로 사용하는 것은 불가능하다.

 

 

이번 포스팅에서는 변수와 포인터에 대해 알아보았다.

다음 포스팅에서 포인터에 대해 조금 더 심화적으로 알아보겠다.