자 드디어 대망의 포인터입니다. 많은 분들이 C언어를 공부하다가 포인터부분에서 좌절하는 걸로 알고 있습니다. 포인터의 내용이 조금 난해하고 광범위하다보니 그런것 같은데 사실 차근차근 하나씩 알아가다보면 전혀 어려운 내용이 아닙니다. 그럼 포인터의 기초부터 하나씩 이야기 해 보겠습니다.
1. 데이터의 저장위치
자~ 우리가 입력하거나 프로그램이 실행되면서 생성된 데이터는 메모리의 특정공간에 저장됩니다. 데이터의 최소 단위는 bit(비트)이지만 우리가 지금 이야기할 데이터의 단위는 좀 더 의미있는 데이터인 정수나 실수 같은 데이터를 이야기 할 것이므로 byte(바이트)단위로 이야기를 하겠습니다. 정수(int)는 4byte, 실수(float)는 8byte죠. 그래서 메모리상의 단위사이즈를 1byte라고 가정해 보겠습니다. 정수(int)는 4byte니까 우리가 프로그램을 만들 때 정수형 변수를 선언하게 되면 메모리 상에서 4개의 공간을 사용하게 되는 것입니다. 이 공간에는 우리가 현실에서 살고 있는 집처럼 각각의 주소가 부여되어 있습니다. 우체국 집배원 아저씨가 편지를 전달해 주거나 우리가 편지를 보낼때도 우리 집 주소를 쓰죠? 그것처럼 메모리 상에서도 데이터가 입력되거나 출력되려면 데이터를 가지고 오거나 가져다 놓을 주소가 필요하기 때문에 메모리상의 1byte단위공간에는 주소가 부여되어 있습니다. 이해가 되시나요? 그림으로 나타내자면 아래와 같습니다.
먼저 위와 같은 프로그램을 하나 작성했다고 예를 들겠습니다. 위 프로그램에서 우리는 정수(int)타입의 변수 a를 선언했고, 그 변수 a에 숫자 100을 집어넣었으며, printf함수를 이용하여 변수 a에 들어있는 숫자 100을 출력하였습니다. 컴퓨터상에서 이러한 행위는 아래와 같이 실행됩니다.
갈색의 라운드 네모박스 하나하나가 1byte의 공간을 의미하는 메모리 공간일 때 정수(int)형으로 선언된 변수 a는 1byte의 공간 4개를 차지하게 되고, 그 메모리 공간에 숫자 100을 입력하여 저장해 두는 것입니다. printf함수를 이용하여 출력을 명령하면 printf함수는 변수 a가 차지하고 있는 메모리공간인 0x1112부터 0x1115까지의 메모리공간을 읽어들여서 해당 공간에 저장되어 있는 숫자 100을 출력하게 되는 것입니다. 이때!! 변수 a의 데이터를 입력하거나 출력하기 위해서는 변수 a에 할당된 메모리공간의 시작주소를 컴퓨터가 알고 있는데 이 시작주소를 저장하고 접근 할 수 있는 변수타입이 바로 포인터입니다. 이렇듯 변수를 선언하고 데이터를 주고 받으려면 메모리의 어느 공간에 해당 변수가 위치하고 데이터를 저장하고 있는지 알아야 하기에 메모리의 모든 공간에는 0x1112나 0x1113같이 주소가 지정되어 있고 우리는 포인터를 통해 이 주소에 직접적으로 접근이 가능한 것입니다. 참고로 0x1112나 0x1113은 제가 임의로 부여한 주소입니다.
2. 포인터란?
앞서 이야기 했던것 처럼 포인터란 변수의 한 종류로써 특정데이터가 저장되어 있는 메모리의 시작주소를 저장합니다. 여기까지 완벽하게 이해하셨으면 포인터에 대해서 반 이상 이해하신겁니다. 앞으로의 내용으로 좀 더 완벽하게 이해하게 되실 거니 찬찬히 알아가 보도록 하겠습니다.
자 변수는 정수형, 실수형, 문자형등 여러가지가 있습니다. 그때마다 필요한 메모리 공간의 갯수도 다르지요. 그렇기 때문에 각각의 변수타입에 맞는 포인터 타입도 따로 있습니다. 거창하게 이야기 했지만 어려운것은 하나도 없습니다.
위와 같이 정수형 변수를 선언할 때 int a라고 한다면 정수형 포인터를 선언할 때 int* a 형식으로 선언하시면 됩니다. 변수의 데이터 타입과 변수이름 사이에 *(별표)이 하나 추가된 것이죠. 그렇다면 저렇게 선언하면 int* a에는 변수 a의 주소가 입력되어 있는 것인가요? 라는 의문이 생기실 것 같은데 그렇지는 않습니다. 위와 같이 선언하면 변수 a에 아직 아무 값을 대입하지 않은 것처럼 포인터 a에도 아직 아무 값을 대입하지 않았으니까요. 변수가 초기화(값입력)를 하지 않으면 프로그램 실행이 정상적으로 되지 않고 오류가 나오듯 포인터도 마찬가지입니다. (포인터도 변수의 한 종류인것을 잊지마세요!!)
그럼 정수(int)형 말고 다른 타입의 데이터를 저장하는 포인터들은 어떻게 생겼을까요?
생각보다 간단하죠? 자 이렇게 포인터의 선언 형식까지 알아보았습니다. 그럼 이제 포인터는 주소값을 저장하는 변수타입이라고 하였으니, 주소값을 저장해 보아야겠죠? 주소값은 어떻게 저장할 수 있을까요? 아래예제를 한번 보시기 바랍니다.
위 프로그램은 정수형 변수 a를 선언하고 a에 대입할 값을 입력한 뒤 a에 대입된 값을 출력하는 아주 간단한 프로그램입니다. 따로 자세히 설명하지 않아도 충분히 이해 하실 듯 합니다. 우리가 여기서 주목해야 하는 부분은 바로 scanf_s함수를 사용한 부분입니다.
scanf_s함수는 프로그램이 사용자의 입력을 받는 함수입니다. 입력을 받은 값을 지목한 변수에게 대입시키게 되어 있죠. 여기서 변수를 지목하는 방법이 "&a"입니다. 즉! 변수 "a"앞에 온 첨자 "&"이 해당 변수가 저장되어 있는 메모리상의 주소를 나타내어 주는 연사자인 것입니다.
그런데 이런 의문을 재기할 수도 있습니다. "&는 AND연산자 아닌가?" 맞습니다. 다만 AND연산자로 사용이 되려면 피연산자가 두개가 와야 합니다. "a & b" 형태처럼요. 위에서 처럼 피연산자가 하나인 경우에 사용하면 단항연산자라고 하여 AND연산자와는 다르게 해당변수의 주소를 불러오는 역할을 합니다. 그럼 예제를 통해서 한번 확인해 볼까요?
3. 포인터 예제
위 예제는 변수 a1의 주소값을 포인터 a2에 대입한 뒤 변수a1의 주소값과 포인터 a2의 값을 출력하여 동일한지 확인해 보는 프로그램입니다. 여기서 중요한 점은 대입을 시킬 때 포인터 "a2"를 "*a2"로 쓰지 않고 그냥 "a2"만 써야한다는 점입니다. 즉 포인터를 선언할 때 "int * a2"에서 "*"(별표)는 포인터의 이름이 아닌 포인터의 형식에 포함된다는 점을 명심해야 합니다. 그럼 바로 실행해 보도록 하겠습니다.
변수 a1의 주소값과 포인터 a2의 값이 동일하다는 것을 확인 할 수 있었습니다. 이제 어느정도 포인터에 대한 감이 잡히시나요? 비슷한 방법으로 여러번 프로그램을 작성해 보며 감을 익히시길 바랍니다. 여담이지만 C언어가 굉장히 Raw level의 언어이지만 아직도 강력한 이유는 이렇게 포인터를 이용하여 메모리에 직접 접근이 가능하다는 점 때문입니다. 그렇기 때문에 포인터는 C언어에 있어서 매우 중요한 부분입니다.
자 그럼 딱 한가지만 더 하고 오늘 포인터의 기초에 대해서는 마무리 하겠습니다. 위에서 변수의 주소를 불러오는 단항연산자가 "&"이었죠? 그럼 반대로 포인터에 저장되어 있는 주소값에 저장되어 있는 데이터를 불러오는 단항연산자도 있지 않을까요? 네. 당연히 있습니다. 바로 "*" 입니다. 간단한 예제를 통해서 바로 알아보도록 하겠습니다.
위 예제는 변수 a1에 7이라는 값을 대입하고 변수 a1의 주소를 포인터 a2에 입력한 다음, 변수 a1의 주소값과 포인터 a2의 값을 출력하고, 마지막으로 포인터 a2에 저장된 주소값에 들어 있는 데이터를 출력하는 프로그램입니다. 첫번째 예제와 동일한 예제에 변수 a1에 7을 대입하는 부분과 포인터에 저장된 주소값에 저장된 데이터를 출력하는 부분을 추가했을 뿐입니다. 바로 실행시켜서 결과를 확인 해 보겠습니다.
오오~ 변수 a1의 주소가 포인터 a2에 잘 대입되었고, 포인터 a2에 저장된 주소값을 추적하여 해당 주소에 들어 있는 데이터를 출력해보았더니 변수 a1에 대입한 7이라는 값이 정확하게 출력되는 것을 확인 할 수 있었습니다. 자 여기서 포인터에 저장된 주소값에 들어있는 데이터를 추적하기 위해 사용한 단항연사자는 "*"입니다. 사용법은 포인터 이름 앞에 "*"을 붙여주면 됩니다. 이걸 그림으로 표현하자면 아래와 같습니다.
이렇게 변수와 포인터, 그리고 단항연산자 "&"과 "*"을 이용해서 데이터와 주소값을 자유로이 핸들링 할 수 있습니다. 굉장히 강력한 기능이죠. 참고로 주소값이 저장되는 포인터의 경우 32bit시스템을 사용하시면 메모리 주소의 길이가 32bit이기 때문에 4byte의 크기를 가집니다. 요즘에는 64bit 시스템을 많이 사용하시죠? 이럴 경우에는 메모리 주소의 길이가 64bit이기 때문에 8byte의 크기를 가지는 점을 꼭 명심해야 합니다.
오늘은 이렇게 C언어의 큰 관문(?)중 하나인 포인터의 기초 대해서 알아보았습니다. 너무 어렵게 생각하지 않으셔도 됩니다. 오늘 배운 내용만 숙지하셔도 포인터에 대해서 반이상 이해하셨다고 생각하셔도 무방합니다. 이 이후의 내용은 전부 오늘 배운 기초를 응용하는 내용들입니다. 다음시간에는 포인터에 대해서 좀 더 깊이있게 이야기를 해보는 시간을 갖도록 하겠습니다. 감사합니다.
제 블로그를 방문해 주셔서 감사합니다.
도움이 되셨다면 공감(좋아요) 부탁드리고,
댓글은 다른분들께도 좋은 정보일 수 있으니
공개로 부탁드립니다.
여러분의 작은 정성이 좋은 포스팅을 생산하는
힘이 된답니다. :)
감사합니다.
'PROGRAMMING > C' 카테고리의 다른 글
C언어 포인터 심화과정 (포인터 연산) (3) | 2022.11.01 |
---|---|
C언어 포인터 심화과정 (상수포인터) (0) | 2022.10.31 |
C언어 배열 응용 (0) | 2022.10.25 |
C언어 배열 기초 (0) | 2022.10.24 |
C언어 상수 constant (0) | 2022.10.21 |
댓글