*C언어의 포인터란 무엇인가?
자료 구조에서 포인터는 중요한 역할을 한다. 포인터에 대한 이해가 곧 자료 구조를 이해하는 기본 토대라고 봐도 무방하다.
* 포인터는 일반 변수와 비교할 수 있다.
많은 부분 차이 나지만 대표적인 것은 아래와 같다.
변수 | 포인터 |
값을 저장한다. (int, double, char etc) | 메모리의 주소를 저장한다. |
스택 메모리(STACK) / 데이터 영역(전역 변수) | 히프 메모리(HEAP) |
프로그램에서 메모리 관리 | 프로그래머가 메모리 관리 |
정적 데이터 타입 | 동적 데이터 타입 |
* 다음의 예제는 포인터를 얻는다.
'*' 는 포인터 연산자이다. 이 변수가 int 형을 가리키는 포인터 임을 선언한다. int 형이라는 것은 포인터 연산시 정수형 4바이트 단위로 이동하는 의미를 담고 있다. char 형은 1바이트 씩 이동하고, short 형은 2바이틀 이동한다. char 형 포인터로 int 타입을 가리키는 것도 가능하기 때문에 포인터 제어를 잘못하면 금방 메모리의 다른 영역을 침범할 수 있다.
calloc 함수를 사용해서 int 형 4바이트 크기의 메모리를 1개 할당한다. calloc 은 메모리를 0으로 초기화 시킨다. 비슷한 메모리 할당하는 malloc 함수는 메모리를 초기화를 하지 않는다.
컴퓨터 프로그램에서 메모리 오류는 가장 치명적인 오류중의 하나다. stackoverflow(사용가능한 메모리가 부족한 상황)나 포인터 유실(제어불능)등의 메모리 오류는 프로그램의 강제 종료의 원인이 된다. 컴퓨터의 운영체제가 어느정도 수습을 하지만 심하면 운영체제까지도 손상 시킬 수 있다. 특히 상업용 소프트웨어는 시스템의 안정성이 최우선이므로 포인터의 사용에는 큰 책임이 따른다.
물론 큰 책임이 있는 만큼 메모리를 직접 조작할 수 있는 권한이 주어진다.
실시간 감시기능을 키고 C언어 프로그램을 실행시키면 시스템에 대한 공격으로 간주해서 코드를 차단하는 경우가 있다. C프로그램이 권한이 많은 것을 백신 프로그램은 알고 있는 것이다. 이런 경우 실시간 감시기능을 잠시 꺼둬야 코드를 실행할 수 있다.
C언어는 운영체제를 만들기 위해 설계된 언어라서 저수준(low level - 하드웨어) 자원에 직접 접근이 가능하다. 포인터는 가장 중요한 하드웨어인 메모리에 직접 접근할 수 있으니 보안 시스템 입장에서는 주요 감시 대상이다.
사용자의 입장에서 CPU 레지스터에 직접 접근하는 어셈블리어와 C를 함께 사용하면 컴퓨터의 동작에 대한 이해도를 높일 수 있다.
* 포인터의 값은 *a 연산자로 접근한다. 포인터가 가진 주소에 가서 메모리에 저장된 값을 가져온다. 포인터는 메모리의 주소인 것을 염두한다.
a *연산자를 빼면 포인터의 주소다. 메모리는 16진수로 표기한다. 0x 로 시작되는 8개의 숫자는 32비트 주소를 의미한다. 16진수 -> 1자리가 4바이트, 4바이트 8개는 32비트이다. 32비트 이상의 메모리 주소도 사용하지만 아직도 많은 애플리케이션이 32비트 기준으로 개발되있다.
흔히 프로그램 다운로드 받을 때 32비트 64비트 버전이라는 말을 하는데 메모리 주소 방식을 말한다. 아직 64비트가 아닌 컴퓨터 시스템도 있기 때문에 32비트가 호환성이 더 높다. 현재의 OS나 하드웨어의 아키텍쳐가 32비트에 최적화되어 있기 때문에 보통의 프로그래머들은 아직도 32비트 프로그램을 권장하고 있다.
윈도우는 cmd 창에서 systeminfo 명령어를 입력하면 자신의 PC 종류를 알 수 있다.(32비트 64비트)
* 다음은 포인터로 동적 배열을 생성하는 코드이다.
calloc 의 매개변수에 size를 주면 그 숫자만큼 메모리를 할당한다. 5 개. 4바이트 20바이트를 할당 받는다. 정수 5개를 무작위로 생성하여 출력한다. 포인터로 생성한 배열도 일반 배열로 생성한 것처럼 사용할 수 있다. [ ] 괄호안의 숫자를 이용해 배열의 요소에 똑같이 접근이 가능하다. 주소를 보면 B8 BC C0 이는 4바이트 단위라는 것을 알 수있다.
16진수는 8 -> C (12) - > 0 (16) 이다. 즉 20바이트안에 연속해서 4바이트 단위로 5개의 int 가 할당된 것이다. int 는 약 42억 범위의 숫자를 표현할 수 있다. 42억 x 5개 숫자가 저장된다. 정수형이라고 해도 4바이트가 42억까지 밖에 저장이 안된다. 숫자의 한계를 아는 것은 컴퓨터 구조를 이해하는데 도움이 된다.
현재의 컴퓨터는 32비트 int 형 연산이 가장 효율적이라고 한다. 불과 20년쯤 전에는 16비트 정수형을 많이 사용했다. 예를 들어 닌텐도의 16비트 게임기가 사용하는 기본 자료형이 16비트 정수형이다. 386x PC도 16비트였다. 16비트 정수라는게 65536 이라는 작은 수다. 만단위라 커보이지만 컴퓨터 안에서는 for 루프 몇번에 순식간에 소모되는 숫자다.
실제로 루프 테스트를 해보면 int 연산이 가장 빠르고 long (64비트) 으로 넘어가면 눈에 띄게 차이가 난다. 물론 옮기고 연산할 비트의 양이 2배로 늘었으니 당연한 일이긴 한데 포인트는 숫자가 높다고 무조건 좋은 것은 아니다. 32비트와 64비트는 16비트와 32비트 차이보다 훨씬 큰 갭이 있다. (참고 포스트)
부동소수점(float) 같은 경우 더 말할 것도 없이 정수형에 비하면 연산에 부하가 걸린다. 프로그램 작성할 때 int 형을 많이 쓰는 것에도 이유가 있다.
* C언어를 공부하는 것은 자바스크립트 처럼 즉각적으로 보이는 앱을 만드는 것과는 거리가 있다. 반면 컴퓨터 구조와 시스템(OS)에 대한 이해도를 높이고 알고리즘 공부를 하는데 있어서는 C만한 언어가 없다.
그런데 이 C언어에 대한 의견간에 차이가 있을 수 있다. 아무래도 보이지 않는 컴퓨터 하드웨어, 내부의 동작원리에 대하여 설명하다보니 일어나는 일도 많다. 컴퓨터 구조에 대한 자료는 영문 자료가 많으니 영어 실력도 높일 겸 자주 Article 과 pdf 교재를 읽으면 좋다. 한국의 대학에서 쓰이는 교재도 대부분 원서의 번역본이다. 그 원서의 내용을 뒷받침하는 article 들이 온라인에 많이 있으니 참고한다.
마지막으로 메모리 구조인 스택(STACK)과 히프(HEAP) 메모리 비교에 대한 웹문서를 읽는 것을 추천한다. 아래의 문서 세 개 정도를 읽는다면 확실하게 개념이 생길 것이다. 메모리 설명의 예제로 주로 C, C++, 자바를 예로 드는데 이들이 각각 다른 구조가 아니라 큰 틀에서는 비슷하다. 스택과 힙을 어떻게 사용하는가? 그리고 자바의 경우 JVM 이라는 가상머신이 메모리를 관리한다. C와 C++ 과 어떻게 다른지, 비슷한 점은 무엇인지 생각하는 것은 구조 공부에 도움이 된다.
자바는 C와 C++에 영향을 받은 언어다. 이전의 장점은 계승하고 단점은 보완하려는 방식에서 나왔기 때문에 기존의 방식과 완전히 다르지 않다.
포인터 챕터가 C언어를 배우는 과정에 다소 지루한 파트가 될 수 있는데 뜻을 세웠으면 성공하기를 바란다.
*전체 소스코드
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void getPointer();
void getArrayPointer();
int randomInt(int rangeFrom, int rangeTo);
int main(int argc, char const *argv[])
{
srand(time(NULL));
getPointer();
getArrayPointer();
return 0;
}
void getPointer(){
int * a;
a = calloc(1, sizeof(int));
if (a == NULL){
printf("메모리 할당 실패... \n");
} else {
// * a = 99;
printf("포인터의 값 *a = %d\n포인터 주소 a = 0x%p", *a, a);
free(a);
}
}
void getArrayPointer(){
int i;
int *array;
int size;
size = 5;
printf("요소의 개수 : %d\n", size);
array = calloc(size, sizeof(int));
if (array == NULL) puts("메모리 할당 실패");
else
{
for (int i = 0; i < size; i++)
{
array[i] = randomInt(1, 100);
}
}
for (int i = 0; i < size; i++)
{
printf("array[%d] = %d, at address : %p\n", i, array[i], &array[i]);
}
free(array);
}
int randomInt(int rangeFrom, int rangeTo){
int random = rand() % (rangeTo-rangeFrom+1) + rangeFrom;
return random;
}