12-1-사.메모리 관리 함수

다음 함수들은 메모리 관리 함수들이다. 문자열과 메모리는 연속된 공간이라는 점에서 공통점이 있기 때문에 메모리 관리 함수들의 동작도 문자열 관리 함수들과 비슷하다. 동작이 비슷하다 보니 함수 이름과 원형도 거의 같다. 일단 원형을 보도록 하자.

 

void *memcpy(void *dest, const void *src, size_t count);

int memcmp(const void *buf1,const void *buf2,size_t count);

void *memchr(const void *buf,int c,size_t count);

void *memset(void *dest,int c,size_t count);

void *memmove(void *dest,const void *src,size_t count);

 

strcpy에 대응되는 memcpy, strcmp에 대응되는 memcmp 등의 함수들이 있는데 기본적인 동작 방식은 대응되는 문자열 함수와 같다고 생각하면 된다. 다만 몇 가지 점만 다른데 메모리 관리 함수들과 문자열 관리 함수들의 차이점은 다음과 같다.

 

인수와 리턴값의 타입이 다르다. 문자열 관리 함수들은 항상 문자열을 대상으로 하므로 취하는 인수나 리턴값이 대부분 char *형이지만 메모리 조작 함수들은 임의의 값을 대상으로 하기 때문에 인수와 리턴값이 모두 void *형이다. 이 함수들은 시작 번지만 알려주면 바이트 단위로 작업을 하므로 메모리에 저장된 값의 타입을 몰라도 상관없으며 임의의 타입에 대해서도 잘 동작한다.

문자열은 시작 번지만 알려 주면 널 종료 문자를 끝으로 인식하기 때문에 길이를 별도로 알려줄 필요가 없다. 하지만 메모리 관리 함수는 길이를 알려 주지 않으면 어디까지가 작업 대상인지를 알지 못한다. 그래서 모든 함수의 끝에 작업 대상 메모리의 길이를 지정하는 count라는 인수가 있다. 메모리끼리 복사하는 memcpy 함수는 strcpy 함수보다는 strncpy 함수와 유사하다고 볼 수 있다.

 

이 두 가지 차이점만 알면 문자열 함수와 비슷한 방식으로 메모리 관리 함수를 사용할 수 있다. 몇 가지 사용예를 보도록 하자. 다음 코드는 ar 배열을 완전히 초기화한다.

 

int ar[10];

memset(ar,0,sizeof(ar));

 

배열을 선언하면서 초기화하지 않았거나 또는 실행중에 이미 값이 저장된 배열을 모두 특정 값으로 채우고자 할 때 memset 함수를 사용한다. 이 코드에 의해 ar배열은 그 크기만큼을 모두 0으로 가득 채우며 ar 배열의 모든 요소는 0으로 재초기화된다. strset 함수가 문자열을 특정 문자로 가득 채우듯이 memset 함수는 지정한 번지부터 지정한 길이까지를 특정값으로 가득 채워준다.

메모리끼리 복사할 때는 memcpy 함수를 사용한다. 복사할 원본과 대상, 그리고 복사할 길이를 인수로 전달하면 두 메모리의 내용을 완전히 똑같이 만들어 준다. 다음 코드는 이 함수를 사용하여 배열끼리 복사한다.

 

int ar[5]={10,8,0,75,28};

int ar2[5];

memcpy(ar2,ar,sizeof(ar));

 

배열은 서로 대입할 수 없으며 배열의 사본을 만들려면 배열 요소끼리 개별적으로 대입해야 하는데 memcpy 함수를 사용하면 배열끼리도 한 번에 복사할 수 있다. 두 배열의 시작 번지와 복사할 길이를 sizeof 연산자로 알려 주기만 하면 된다.

memcpy는 대응되는 바이트끼리 기계적으로 복사하기 때문에 복사할 대상의 논리적인 구조 따위는 전혀 개의치 않는다. 그래서 배열뿐만 아니라 2차원 배열이나 구조체 배열 따위도 memcpy를 사용하면 얼마든지 복사할 수 있다. 또한 널 종료 문자같은 것도 인식하지 않기 때문에 중간에 0을 만나도 지정한 길이까지는 무조건 복사한다.

메모리 관리 함수는 문자열 관리 함수에 비해 훨씬 더 일반적이다. 문자열 관리 함수는 오로지 char *형만 다룰 수 있지만 메모리 관리 함수는 바이트를 직접 다루기 때문에 모든 타입에 대해 사용할 수 있다. strcpy(dest,src)호출은 memcpy(dest,src,strlen(src)+1)과 완전히 동일하다는 것을 이해할 수 있겠는가?

메모리 관리 함수중에 가장 재미있고도 실용적인 함수는 memmove 함수이다. 이 함수는 메모리의 내용을 지정한 길이만큼 다른 곳으로 옮긴다. 이 함수를 사용하면 배열 중간을 뒤쪽으로 밀어서 빈 공간을 만든 후 그 공간에 다른 내용을 삽입해 넣을 수 있다. 다음 예제는 문자열 중간에 단어를 삽입해 넣는다.

 

: InsertString

#include <Turboc.h>

#include <string.h>

 

void main(void)

{

     char str[32]="You are beautiful";

     char str2[]="very ";

 

     memmove(str+13,str+8,10);

     memcpy(str+8,str2,strlen(str2));

     puts(str);

}

 

str[8]이하에 있는 길이 10(널 문자 포함)의 "beautiful"을 str[13]으로 옮기면 중간에 5바이트가 비게 된다. 이 빈 자리에 "very "를 복사해 넣었다. 출력 결과는 You are very beautiful이다. 이 예제가 정상적으로 동작하려면 str이 중간에 삽입되는 문자열까지 고려해서 충분한 길이를 가져야 한다.

이름에 move가 포함되어 있기는 하지만 그렇다고 해서 원본이 삭제되는 것은 아니며 복사된다. memcpy와 memmove는 메모리 내용을 복사한다는 점에서 사실 동일한 함수이다. 그러나  memcpy는 무조건 순방향으로만 복사하는데 비해 memmove는 겹쳐진 메모리에 대해서 원본이 파괴되지 않도록 복사 방향을 지능적으로 선택한다는 점이 다르다.

 

이상으로 문자열 관리 함수와 메모리 관리 함수들에 대해 알아보았다. 여기서 알아본 함수들은 C/C++ 언어의 표준 함수들이며 이 외에도 각 운영체제별로 유사한 함수 집합을 제공하기도 한다. 예를 들어 윈도우즈는 문자열 관리를 위해 lstrcpy, lstrcat, lstrlen 함수를 제공하는데 함수명 앞에 l자 하나만 더 붙어 있을 뿐 표준 함수와 이름이 동일하고 동작도 비슷하다. 하지만 이 함수들은 유니코드를 지원한다는 점이 다르다. 차후 실제 프로그래밍을 할 때는 표준 함수보다는 이런 함수들을 더 많이 사용하게 될텐데 여기서 문자열 함수의 기본 동작만 이해해 두면 이런 함수들은 금방 공부할 수 있을 것이다.

 

 RotateScroll

일정한 수평 범위에서 문자열을 오른쪽으로 스크롤하되 범위의 끝에 이르면 앞쪽으로 회전하면서 스크롤한다. 범위와 스크롤될 문자열은 변경하기 쉽도록 작성하되 범위에서 끝 좌표는 제외하는 것이 논리상 합당하다. 예를 들어 10~30사이를 스크롤한다면 실제 문자열이 찍히는 마지막 좌표는 29여야 한다. 부드러운 스크롤을 위해 clrscr 함수는 호출하지 말아야 하며 꼭 필요한 부분만 삭제하도록 한다. 다 작성했으면 왼쪽으로도 스크롤시켜 보도록 하자.