동적 할당(C)

|

 

프로그램에서 사용되는 메모리는 정적 메모리와 동적 메모리가 있다. 스택,코드,데이터는 정적 메모리에 저장되고, 힙은 동적 메모리에 저장된다.코드영역(실행 코드,함수),스택(지역,매개 변수),데이터 영역(전역,정적 변수)는 요구되는 메모리의 크기가 컴파일 때 결정된다. 따라서 이 크기는 런타임에 변경되는 것이 불가능하다. 하지만 동적 메모리에서는 가능하다.

다시 한번 프로세스(실행중인 프로그램)의 메모리 공간을 정의해보자면

코드 영역 - 프로그램 실행 코드 또는 함수들이 저장

스택 영역 - 함수 호출에 의핸 매개변수와 지역 변수 등 블록 내부에 정의된 변수. 잠깐 사용되고 메모리에서 소멸되는 데이터가 저장된다.

데이터 영역 - 전역 변수와 정적 변수들이 저장됨. 프로그램이 종료될 때까지 유지되어야 하는 데이터가 저장된다.

힙 영역 - 프로그램이 실행되는 동안에 프로그래머가 동적으로 메모리를 할당할 수 있는 영역.

이 영역들은 각기 주소가 다르고, 각자의 영역을 가지고 있다.

그렇다면 왜 동적 메모리가 필요한가?
예를 들어, 배열은 메모리를 할당하게 되면 배열이 선언된 함수나 프로그램이 종료될 때까지 그 영역을 해제할 수 없다. 즉 고정된 메모리를 할당받는다. 따라서, 메모리의 낭비나 부족이 발생할 수 있고, 배열 선언 시 크기 값을 무조건 상수로 설정하여야 한다.

이렇게 메모리 크기를 미리 예측할 수 없는 경우에 동적 메모리 할당이 사용된다. 힙 영역에 메모리 할당을 하게 되면 필요한 시점에 필요한 크기만큼 메로리를 할당할 수 있다. 동적 메모리 할당은 C에서 포인터를 기반으로 동작한다. 함수를 통해 더 자세히 알아보자.


메모리 할당 함수들 ( stdlib.h )

- void* malloc(size_t size)

- void* calloc(size_t num,size_t size)

- void* realloc(void* p,size_t size)

메모리 해제 함수

- void free(void* p)


보통 메모리 할당에는 malloc 함수가 자주 쓰인다. 이 함수는 성공 시 메모리의 시작 주소를 반환하고, 실패 시 NULL을 반환한다.
인자로 size_t 형식을 받는데,이는 메모리에 얼만큼 동적 할당할 지 바이트 수를 의미한다. 함수의 리턴 형태는  void* , 즉 보이드 포인터다.
사용자가 어떤 형식의 자료형을 할당할 지 알 수 없으므로, 함수는 void* 형식으로 리턴하고 사용자가 이를 형변환 해야 한다.
예를 들어

int *p =NULL;
p = (int*)malloc(4);

이 구문은,4byte만큼의 메모리를 동적 할당해 int형 포인터 p에  그 주소를 저장하겠다는 뜻이다. 자료형은 int이므로 malloc이 리턴하는 주소를 int* 형으로 형변환 해주어야 한다.

할당이 있다면 해제도 있어야 한다. 그렇지 않으면 메모리의 낭비가 발생한다. 해제는 free함수로 한다.
free(p);
이렇게 써주면 p에 동적할당했던 메모리가 해제된다.

 

#include <stdio.h>
#include <stdlib.h>

int main()
{
 int i;
 puts("input the size: ");
 scanf("%d", &i);
 puts("---------------------\n");
 int *p = (int*)malloc(sizeof(int)* i);

 for (int j = 0; j < i; j++)
 {
  p[j] = j;
  printf("%d\n", p[j]);
 }
 free(p);

 printf("%d\n", p[0]); // 쓰레기 값
p = NULL;
 return 0;
}

 

사용자로부터 원소의 개수를 입력받고,그 개수 * int의 크기 만큼(즉 배열의 크기)를 동적할당하는 예제다. 메모리에 int형의 메모리 블록을 생성하고, 그 "주소"를 int *p에 담는 것이므로, 여기에 동적할 크기는 (int*)*i가 아닌 (int)*i가 된다. 혼동하지 말자. 
이렇게 동적할당을 사용하면 프로그래머가 메모리의 크기를 유동적으로 지정할 수 있다.(참고로 동적할당은 sizeof로 배열 크기를 측정할 수 없다. 해당 함수를 실행하면 포인터의 크기인 4가 나오게 된다.)
free 함수를 통해 동적 할당을 해제하면 포인터가 가리키던 대상은 쓰레기 값이 나오게 된다. 혹시 모를 오용을 방지하기 위해 사용을 마친 포인터는 NULL로 초기화 해주는 것이 좋다.



calloc함수는 malloc 함수와 같은 기능을 하지만, 사용법이 약간 다르다.calloc함수의 인자는 2개인데, size_t num과 size_t size다. calloc 함수는 num과 size를 곱한 만큼을 동적할당하게 된다. 즉 int형 4칸이 필요하다면 malloc은 malloc(sizeof(int)*4) 였다면 calloc은 calloc(4,sizeof(int)) 다. 또 다른 차이는, malloc 함수는 메모리 할당된 영역을 초기화 하지 않아 쓰레기 값이 나오는 반면 calloc함수는 전부 0으로 초기화해준다. 편리하긴 하지만 따로 의도한 경우가 아니라면 시간 낭비를 유발할 수 있다.


malloc 함수와 calloc함수는 동적 메모리를 할당한 후 메모리의 크기를 변경할 수 없다는 단점이 있다. 이러한 문제를 realloc 함수가 해결해준다. realloc함수는 동적 메모리로 할당되어 있는 영역 p에서 size만큼을 다시 재할당 해주고(이 때 주소가 바뀔 수 있다), 그 재할당된 메모리의 시작 주소를 반환한다.



#include <stdio.h>
#include <stdlib.h>

int main()
{
 int *p = (int*)malloc(sizeof(int)* 2); // 8바이트 할당
 p[0] = 1;
 p[1] = 2;

 p = (int*)realloc(p, sizeof(int)* 4); // 16바이트로 확장
 p[2] = 30;
 p[3] = 40;

 for (int i = 0; i < 4; i++)
  printf("%d\n", p[i]);

 printf("----------------------\n");
 p = (int*)realloc(p, sizeof(int)* 2); // 8바이트로 다시 재할당
 
 for (int i = 0; i < 4; i++)
  printf("%d\n", p[i]);

 printf("----------------------\n");


 p[2] = 50;
 
 for (int i = 0; i < 4; i++)
  printf("%d\n", p[i]);

 return 0;
}

 

 

예제는 8바이트를 동적할당하고,  16바이트로 확장한 후 , 8바이트로 다시 재할당하고 있다. 16바이트까지 할당하면 p[3]까지 원소가 들어있다. 이때 다시 8바이트로 재할당하면 p[2]에 쓰레기 값이(원래는 30이 저장되어 있던) 저장되어 있는 것을 볼 수 있다. 즉 메모리를 줄이게 되면 메모리에 들어있던 데이터들이 보존되지 않는다. 할당을 해제했지만 p[2]에 다시 접근해 값을 저장하고 출력까지 할 수 있다. 이는 C에서 배열의 경계를 체크하지 않기 때문에 가능한 일이다.



 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char** arr(int num)
{
 char buffer[256];
 char **p = (char**)malloc(sizeof(char*)* num);
 for (int i = 0; i < num; i++)
 {
  puts("type the phrase : ");
  gets(buffer);
  p[i] = (char*)malloc(strlen(buffer) + 1);
  strcpy(p[i], buffer);
 }
 return p;
}

int main()
{
 int i;
 puts("How many ? : ");
 scanf("%d", &i);
 fflush(stdin);
 char **ptr = arr(i);
 puts("--------------------------------\n");
 for (int j = 0; j < i; j++)
 {
  puts(ptr[j]);
  free(ptr[j]);
 }
 free(ptr);
 return 0;
}

 

위 예제는 사용자가 입력한 숫자 만큼 배열을 생성하고,거기에 사용자가 입력한 문자열을 넣는 예제다. 여기서 살짝 복잡한 이차원(혹은 이중)포인터를 통한 동적 할당을 하고 있다.
함수 arr은 리턴 방식이 char**로, char형 2차원 포인터다. 이는 2차원 포인터의 원소에 다시 동적 메모리 할당을 통해 문자열을 저장하고,각각 1차원 포인터에 접근하기 위해 2차원 포인터의 시작 주소를 리턴하기 위함이다.
**p에 인자로 들어온 문자열의 개수 만큼 곱하기 char* 형으로 동적 할당을 한다.
그리고 반복문을 통해 문자열을 입력받고, 이를 버퍼에 넣은 후 2차원 포인터 각각의 원소(2차원 포인터는 1차원 포인터를 가리킨다. 즉 2차원 포인터 배열의 원소"값"은 1차원 포인터의 주소가 되는 것이다.)에 1차원 문자 포인터로 버퍼가 담고 있는 문자열의 크기만큼을 동적할당하고, strcpy 함수로 문자 포인터가 가리키고 있는 메모리에 문자열을 복사한다.
이 때,**p는 static으로 하지 않아도 상관없다. arr 함수가 종료되면 p는 소멸하지만, p에 동적 할당된 메모리는 사라지지 않으므로(별도로 free를 하지 않는 이상) 해당 주소를 return해  새 포인터인 **ptr이 받아 사용할 수 있다.

 

 


 

'ComputerEngineering > C/C++' 카테고리의 다른 글

전처리기  (0) 2016.08.25
가변 인자  (0) 2016.08.18
동적 할당(C)  (0) 2016.08.17
memory.h  (0) 2016.08.13
time.h (c++의 경우 ctime)  (0) 2016.08.10
데이터 변환 표준 함수  (0) 2016.08.10
trackback 0 And comment 0

memory.h

|

이 헤더에 있는 함수들은 메모리 조작을 위한 함수들이다.

- void* memset(void * dest,int value,size_t cnt);

이 함수는 dest로 시작하는 메모리 주소부터 cnt만큼의 바이트를 value값으로 채운다.이 때 value 값은 unsigned char 형으로 형변환 된다.
리턴 값은 dest가 리턴된다.

#include  <stdio.h>
#include <memory.h>
using namespace std;
int main()
{
 char str[] = "Hello World !";
 memset(str, '-', 5);
 puts(str);
 return 0;
}

 

- void* memcpy (void * dest,const void * src,size_t num);

src가 가리키는 곳부터 num 만큼의 바이트를 (dest나 src의 타입에 상관없이) dest에 복사한다.오버플로우를 방지하기 위해 des와 src가 가리키는 배열의 크기는 반드시 num 이상이어야 한다.이 때 null 문자는 검사하지 않고 정확히 num만큼 복사하기 때문에 유의해야 한다. des와 src인자에 들어온 배열은 void*형으로 형변환 되어서 전달된다.리턴 값은 dest다.

#include  <stdio.h>
#include <string.h>
#include <memory.h>
using namespace std;
int main()
{
 char str1[] = "Welcome to Overwatch";
 char str2[40];
 char str3[40];

 memcpy(str2, str1, strlen(str1) + 1);
 memcpy(str3, "copy successful", 16);

 puts(str2);
 puts(str3);

 memcpy(str3, str2, sizeof(str2));
 puts(str3);
 return 0;
}

 

 

- void* memmove ( void * dest,const void * src,size_t num);

이 함수는 src가 가리키는 곳부터 num만큼을 dest가 가리키는 곳으로 옮긴다. 이 때 버퍼를 이용하므로 둘이 겹쳐도 문제가 발생하지 않는다.
memcpy와 마찬가지로 널 문자를 확인하지 않고 num 바이트 만큼 복사를 수행한다.dest와 src의 메모리가 겹치는 경우에는 이 함수를 이용하는 것이 더 좋다.dest를 리턴한다.

 



#include  <stdio.h>
#include <string.h>
#include <memory.h>
using namespace std;
int main()
{
 char str[] = "I'm very sleepy.....";
 memmove(str + 9, str + 4, 11);
 puts(str);
 return 0;
}

 

- int memcmp (const void* ptr1,const void* ptr2,size_t num);

두 메모리 블록을 num만큼 비교한다. 둘이 정확히 같다면 0을 리턴하고, 다를 경우 둘이 가리키는 메모리 블록에서 다른 바이트를 unsigned char로 해석했을 때 ptr1이 크면 양수를, 작다면 음수를 리턴한다.

#include  <stdio.h>
#include <string.h>
#include <memory.h>
using namespace std;
int main()
{
 char str1[256];
 char str2[256];
 int n;
 size_t len1, len2;

 puts("Enter the sentence");
 gets(str1);

 puts("Enter the another sentece");
 gets(str2);

 len1 = strlen(str1);
 len2 = strlen(str2);

 n = memcmp(str1, str2, len1 > len2 ? len1 : len2); // 둘 중 크기가 큰 쪽으로 비교할 바이트 수를 정함.

 if (n > 0)
  puts("str1 is greater than str2");
 else if (n < 0)
  puts("str2 is greater than str2");
 else
  puts("two sentences is equal");


 return 0;
}

 

- const void* memchr(const void* ptr,int value,size_t num); , void* memchr(void* ptr,int value,size_t num);
 

이 함수는 ptr이 가리키는 메모리의 처음 num 바이트 중에서, 처음으로 value와 일치하는 값의 주소를 리턴한다.
C++에서는 두 함수가 오버로딩 되어 있으나, C에서는 후자의 형태만 있다.

#include  <stdio.h>
#include <string.h>
#include <memory.h>
using namespace std;
int main()
{
 char *ptr;
 char str[] = "Find the Q";
 ptr = (char*)memchr(str, 'Q', strlen(str));

 if (ptr == NULL)
  puts("Can't find");
 else
  puts("Found");
 return 0;
}

 

 

 

'ComputerEngineering > C/C++' 카테고리의 다른 글

가변 인자  (0) 2016.08.18
동적 할당(C)  (0) 2016.08.17
memory.h  (0) 2016.08.13
time.h (c++의 경우 ctime)  (0) 2016.08.10
데이터 변환 표준 함수  (0) 2016.08.10
문자열 함수  (0) 2016.08.05
trackback 0 And comment 0

time.h (c++의 경우 ctime)

|

#define MAX 10000

#include  <stdio.h>
#include <string.h>
#include <time.h>
using namespace std;
int main()
{
 register int i;
 // int i;
 clock_t startTime, endTime, result;
 
 startTime = clock();
 for (i = 0; i <= MAX; i++)
  printf("%d \n", i);
 endTime = clock();

 result = endTime - startTime;

 printf("레지스터 변수 속도 = %lf초\n", (double)result / 1000);


 return 0;


}

 

- clock_t clock(void)

위 예제는 레지스터 변수와 일반 변수의 처리 속도 차이를 보기 위한 예제다.
여기서 속도를 재기 위해 clock 함수를 사용했는데, 이 함수는 cpu의 클럭 값을 반환하는 함수다. 즉 프로그램이 실행하고부터 해당 함수가 실행됐을 때 의 클럭을 반환하는 것이다.이 함수의 리턴 값은 clock_t로써, long형을 typedef한 형태다. 이 함수에서 1clock은 0.001초다. 다시 말해 1초에 1000 clock이 발생하는 것이다.(time.h에 #define CLOCKS_PER_SEC  1000 로 정의되어 있다.)  반복문이 시작하기 전에 startTime에 시작 하기 전 클럭을 입력하고, 반복문이 끝난 후의 클럭값을 재서 둘을 빼면 반복문이 실행될 동안 발생한 클럭값이 나온다. 이 클럭 값은 long형이기 때문에 double로 캐스팅 하고, 1당 0.001초이므로 초로 환산하기 위해 /1000을 해준다.  이렇게 clock 함수는 어떤 연산의 속도를 재는데 사용될 수 있다.


- time_t time(time_t* timer)

1970년 1월 1일부터 경과된 시간을 초 단위로 반환한다. time_t는 1970년 1월 1일부터 몇 초가 지났는지를 담는 자료형이라고 보면 된다.인자 time에 time_t 형 변수를 넣으면 timer쪽에도 값이 전달되고, 값이 리턴되기도 한다.

- struct tm* localtime(const time_t* t)

time_t값을 이용해 tm 구조체를 초기화한다. tm구조체는 날짜, 시간에 관한 정보를 담고 있는 구조체다.

#include <time.h> // C++ 에서는 <ctime>
 struct tm
  {
     int tm_sec; // 현재 초 - 윤초 때문에 60 또는 61이 될 수 있음. (0~61)
     int tm_min; // 현재 분(0~59)
     int tm_hour; // 현재 시간(0~23)
     int tm_mday; // 현재 일(1~31)
     int tm_mon; // 현재 달 (0~11)
     int tm_year; // 현재 년(1900년 이후로부터 얼마나)
     int tm_wday; // 현재 요일 (0~6)
     int tm_yday; // 1월 1일부터 몇 일이 지났는지
     int tm_isdst; // 서머타임 여부(0~365)
 };

리턴한 구조체는 gmtime(기능은 같지만 UTC 형식 시간) 함수와 공유해서 쓰므로,이들 함수를 사용하면 내용이 덮어씌워지게 되므로 주의해야 한다.

- time_t mktime(struct tm* timeptr);

tm 구조체를 time_t 형식으로 변환한다.tm 구조체의 현재 시각과 날짜만 참조해 time_t 형식으로 리턴한다.이 때, tm 구조체에 있는 시각과 날짜를 참조해 tm_wday와 tm_yday를 다시 설정한다. 이를 통해서 원하는 날짜가 무슨 요일이고 몇 번째 날인지 추적할 수도 있다.

 

- char * asctime (const struct tm * timeptr)

tm 구조체를 문자열로 바꾼다. timeptr의 구조를 읽어 Www Mmm dd hh:mm:ss yyyy(요일,월,일,시간,연도) 형태의 문자열을 만든다.
이 문자열에는 \0과 \n이 자동으로 포함되어 있다.이 문자열은 ctime과 asctime 함수가 같이 사용하고 있으므로, 보존을 원한다면 직접 복사해야 한다.

- char* ctime(const time_t* timer)

위와 다르게 time_t값을 문자열로 바꾼다.

- size_t strftime ( char * ptr, size_t maxsize, const char * format, const struct tm * timeptr );

tm 구조체를 사용자가 원하는 포맷으로 출력한다.timeptr을 해석해 ptr에 문자열이 복사되고, maxsize는 그 문자열의 최대길이, format은 printf나 scanf의 그것처럼 형식을 지정한다. 문자열의 길이가 maxsize보다 작으면 복사된 문자열의 수가 리턴되고(널 문자 제외) 크다면 0이 리턴된다.


- double difftime(time_t time2,time_t time1);
time2 - time1 으로 두 시간의 차이를 구한다.


#define MAX 10000

#include  <stdio.h>
#include <string.h>
#include <time.h>
#include <Windows.h>
using namespace std;
int main()
{
 time_t sec;
 printf("1970년 1월 1일 부터 %d 초가 지났습니다 \n", time(&sec));
 
 time_t start, end;
 start = time(&start);

 struct tm* ptr = localtime(&start);
 printf("현재 %d년 %d월 %d일 \n", (ptr->tm_year) + 1900, (ptr->tm_mon)+1, ptr->tm_mday);

 char *present = asctime(ptr);
 puts(present);

 present = ctime(&start);
 puts(present);

 char arr[80];
 strftime(arr, 80, "time : %I:%M%p", ptr);

 int year = 89;
 int month = 0;
 int day = 22;
 
 Sleep(1000);
 int garbage;
 printf("Enter the num : ");
 scanf("%d", &garbage);
 end = time(&end);
 
 double diff = difftime(end, start);
 printf("프로그램 시작 후 %.2lf 초 경과\n\n", diff);


 ptr->tm_year = year;
 ptr->tm_mon = month;
 ptr->tm_mday = day;

 end = mktime(ptr);
 char *birth = ctime(&end);
 puts(birth);

 char * weekday[] = { "Sunday", "Monday",
  "Tuesday", "Wednesday",
  "Thursday", "Friday", "Saturday" };
 printf("내 생일은 %s 입니다", weekday[ptr->tm_wday]);

 
}

 

 

'ComputerEngineering > C/C++' 카테고리의 다른 글

동적 할당(C)  (0) 2016.08.17
memory.h  (0) 2016.08.13
time.h (c++의 경우 ctime)  (0) 2016.08.10
데이터 변환 표준 함수  (0) 2016.08.10
문자열 함수  (0) 2016.08.05
공용체  (0) 2016.08.02
trackback 0 And comment 0
prev | 1 | 2 | 3 | 4 | 5 | 6 | 7 | ··· | 42 | next