일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- effective python
- yarn
- tensorflow
- HelloWorld
- Java
- scrapy
- 하이브
- 그래프이론
- 알고리즘
- LSTM
- hadoop2
- NumPy
- recursion
- 선형대수
- codingthematrix
- 하둡2
- 파이썬
- RNN
- GRU
- 텐서플로
- C언어
- C
- graph
- hive
- 딥러닝
- python
- 주식분석
- Sort
- collections
- 코딩더매트릭스
- Today
- Total
EXCELSIOR
[C]Chap11.2 - 메모리와 포인터 (2) 본문
지금까지 예제코드에서 봐온 변수들은 메모리를 할당하고 해제하는 과정을 컴파일러가 자동으로 관리해주기 때문에 메모리 관리에 신경을 쓰지 않아도 됐었다. 그래서 이러한 변수들을 자동변수라고 불렀다.
그렇다면, 이제 메모리 동적 할당 및 관리에 대해 알아보자. malloc()
과 free()
함수는 메모리를 동적으로 할당 및 해제하는 함수다.
malloc()
함수를 이용하면 자동변수로 사용할 수 있는 메모리와는 비교할 수도 없을 만큼 큰 메모리를 자유롭게 '동적(dynamic)'으로 다룰 수 있다. 즉, 프로그램 실행 중에 대량의 메모리가 필요한 경우 바로 할당이 가능하다. 대신 이러한 할당에는 반환(또는 해제)을 해주어야 한다.
void *malloc(size_t size);
Description : 할당받은 메모리는 반드시
free()
함수를 이용해 반환해야하며, 메모리를 초기화하려면memset()
함수를 이용해야한다. 기본적으로는 쓰레기값이 들어있다.Parameters : size - 할당받을 메모리의 바이트 단위 크기
Return : 힙(heap) 영역에 할당된 메모리 덩어리 중 첫 번째 바이트 메모리의 주소, 에러 발생 시 NULL 반환
void free(void *memblock)
Description : 동적으로 할당받은 메모리를 운영체제에 반환하는 함수
Parameters : memblock - 반환할 메모리의 주소
Return : 없음
malloc()
함수는 인수로 전달받은 정수만큼의 바이트 단위 메모리를 동적으로 할당하고 주소를 반환한다. 이 주소는 할당받은 메모리 전체에 대한 기준주소이다. 메모리의 사용이 끝난 다음에는 반드시 free()
함수를 이용해 메모리를 운영체제에 반환해야 한다.
다음 예제는 malloc(), free()
함수를 이용해 포인터 변수인 pList
에 메모리를 동적할당한 후 반환하는 코드다.
// ptrmalloc01.c
// malloc() 함수를 사용하기 위한 헤더 포함
int main(void){
int *pList = NULL, i = 0;
// 12바이트(sizeof(int) * 3) 메모리를 동적 할당하고 시작주소를
// pList 포인터 변수에 저장
pList = (int*)malloc(sizeof(int)*3);
// 동적 할당한 대상 메모리를 배열 연산자로 접근한다.
pList[0] = 10; // *(pList + 0) = 10;
pList[1] = 20;
pList[2] = 30;
for(i=0; i<3; ++i)
printf("%d\n", pList[i]);
// 동적 할당한 메모리를 해제한다.
free(pList);
return 0;
}
/*
출력결과
10
20
30
*/
위의 코드를 디버깅모드를 통해 메모리를 조사해보면 아래의 그림처럼 확인할 수 있다.
만약에 위의 코드에서 free(pList);
를 주석으로 처리한 후 실행하면 에러는 발생하지 않지만, 할당 받은 메모리를 반환하지 않아, 메모리 누수(memory leak)가 발생하게 된다.
11.2.1 메모리 초기화 및 사용(배열)
할당된 메모리는 일단 0
으로 초기화하는 것이 바람직하다. 그 이유는 메모리를 할당할때 운영체제에서 할당할 메모리를 빌려오는 것이기 때문에 할당될 메모리안에 어떠한 값이 들어 있는지 모르기 때문이다. memset()
과 calloc()
함수를 이용해 할당받은 메모리를 초기화할 수 있다.
void *memset(void *dest, int c, size_t count)
Description : 동적으로 할당받은 메모리에는 쓰레기 값이 있으므로 일반적으로 0으로 초기화하여 사용
Parameters :
dest - 초기화할 대상 메모리 주소
c - 초기값, 이 값이 0이면 메모리를 0으로 초기화
count - 초기화 대상 메모리의 바이트 단위 크기
Return : 대상 메모리 주소
void *calloc(size_t num, size_t size)
Description :
malloc()
함수와 달리 할당받은 메모리를 0으로 초기화하여 전달한다. 할당받은 메모리는 반드시free()
함수를 이용하여 반환해야한다.Parameters :
num - 요소의 개수
size - 각 요소의 바이트 단위 크기
Return : 힙(heap) 영역에 할당된 메모리 덩어리 중 첫 번째 바이트 메모리의 주소. 할당된 메모리의 크기는
num * size
의 값이다. 에러가 발생하면 NULL 반환
// ptrmeminit01.c
// malloc(), calloc() 함수를 위한 헤더 포함
// memset() 함수를 위한 헤더 포함
int main(void){
int *pList = NULL, *pNewList = NULL;
// A. int형 3개 배열 선언 및 정의(0 초기화)
int aList[3] = { 0 };
// B. int형 3개를 담을 수 있는 크기의 메모리를 동적으로 할당한 후
// 메모리를 모두 0으로 초기화
pList = (int*)malloc(sizeof(int) * 3);
memset(pList, 0, sizeof(int) * 3);
// C. int형 3개를 담을 수 있는 메모리를 0으로 초기화한 후 할당받음
pNewList = (int*)calloc(3, sizeof(int));
for(int i=0; i<3; ++i)
printf("pList[%d]의 값 : %d\n", i, pList[i]);
for (int i = 0; i < 3; ++i)
printf("pNewList[%d]의 값 : %d\n", i, pNewList[i]);
// 동적 할당한 메모리들을 해제
free(pList);
free(pNewList);
return 0;
}
/* 출력결과
pList[0]의 값 : 0
pList[1]의 값 : 0
pList[2]의 값 : 0
pNewList[0]의 값 : 0
pNewList[1]의 값 : 0
pNewList[2]의 값 : 0
*/
위의 코드를 디버깅모드를 통해 메모리를 조사해보면 아래의 그림처럼 확인할 수 있다.
11.2.2 메모리 복사
예를 들어 int num = 10;
처럼 단순 대입연산자는 오른쪽 r-value
값인 10
을 왼쪽 피연산자인 num
즉, l-value
에 복사한다. 이 과정에서 num
의 기존 정보는 유실되고 새로운 정보로 덮어 씌어진다. 이러한 단순 대입 연산에서 피연산자가 변수(l-value
)일 경우, 그 개수가 1개라고 전제하여 그 1개에 대해 대입이 이루어진다.
반면, 배열처럼 여러 인스턴스가 있는 경우에는 단순 대입으로 r-value
를 l-value
로 복사할 수가 없다. 따라서, 배열에 대입 연산을 수행하려면 각 요소의 개수만큼 for
문이나 while
문을 통해 요소별로 일일이 대입 연산을 수행해야한다. 하지만, memcpy()
함수를 사용하여 이러한 귀찮은 작업을 대신할 수 있다.
void *memcpy(void *dest, const void *src, size_t count)
Description : 특정 주소로 시작하는 일정 길이의 메모리에 저장된 값을 대상 메모리에 그대로 복사해준다.
Parameters :
dest - 대상 메모리 주소
src - 복사할 원본 데이터가 저장된 메모리 주소
count - 복사할 메모리의 바이트 단위 크기
Return : 대상 메모리 주소
// ptrmemcpy01.c
// memcpy() 함수를 위한 헤더포함
int main(void){
char szBuffer[12] = { "HelloWorld" };
char szNewBuffer[12] = { 0 };
// 원본에서 4 바이트만 대상 메모리로 복사
memcpy(szNewBuffer, szBuffer, 4);
puts(szNewBuffer);
// 원본에서 6 바이트만 대상 메모리로 복사
memcpy(szNewBuffer, szBuffer, 6);
puts(szNewBuffer);
// 원본 메모리 전체를 대상 메모리로 복사
memcpy(szNewBuffer, szBuffer, sizeof(szBuffer));
puts(szNewBuffer);
return 0;
}
/* 출력결과
Hell
HelloW
HelloWorld
*/
11.2.3 메모리 비교(memcmp(), strcmp()
)
memcmp()
int memcmp(const void *buf1, const void *buf2, size_t count);
Description : 첫 번째 인자로 전달된 주소의 메모리에 저장된 값에서 두 번째 인자로 전달된 주소에 저장된 메모리의 값을 빼서 두 값이 같은지 비교한다. 즉, 주어진 길이만큼 두 메모리를 비교하는 함수이다.
Parameters :
buf1 - 비교 원본 메모리 주소
buf2 - 비교 대상 메모리 주소
count - 비교할 메모리의 바이트 단위 크기
Return :
0 - 두 값이 같음
0 - buf1이 buf2 보다 더 큼
0 - buf2가 buf1보다 더 큼
// ptrmemcmp01.c
int main(void){
char szBuffer[12] = {"TestString"};
char *pszData = "TestString";
// 두 메모리에 저장된 값이 같은 경우
printf("%d\n", memcmp(szBuffer, pszData, 10));
// 왼쪽("teststring")이 더 큰 경우 ASCII 숫자상 t > T이기 때문
printf("%d\n", memcmp("teststring", pszData, 10));
// 오른쪽(pszData)이 더 큰 경우
printf("%d\n", memcmp("DataString", pszData, 10));
return 0;
}
/*출력결과
0
1
-1
*/
memcmp()
함수는 아래의 그림처럼 메모리 하나 하나를 1:1로 비교해 메모리를 비교한다.
strcmp()
먼저, strcmp()
함수를 알아보기 전에 다음 예제를 살펴보자.
// ptrstrcmp01.c
int main(void){
char szBuffer[12] = { "TestString" };
char *pszData = "TestString";
// 다음 코드들은 두 문자열이 같은지 비교하는 것이 아니라
// 문자열이 저장된 메모리의 위치가 같은지 비교하는 것이다.
printf("%d\n", szBuffer == pszData);
printf("%d\n", "TestString" == pszData);
printf("%d\n", "DataString" == "TestString");
return 0;
}
/* 출력결과
0
1
0
*/
위의 코드를 작성한 의도는 두 문자열이 같은지를 비교하기 위해 작성한 것이다. 하지만, 문자열은 배열이므로, 배열의 이름은 주소 다. 따라서 위의 ==
부분은 모두 주소가 같은 주소인지 비교하는 것이다. 따라서, 주소를 비교하는 것이 아닌 문자열 내용을 비교하기 위해서는 strcmp()
함수를 이용하면 된다.
strcmp(const char *string1, const char *string2);
Description : 대소문자를 식별하여 두 문자열이 같은지 비교하는 함수
Parameters :
string1 - 비교할 문자열이 저장된 메모리 주소
string2 - 비교할 문자열이 저장된 메모리 주소
Return :
0 - 두 문자열이 같음
0 - string1이 sting2보다 알파벳 순서상 나중
0 - string2가 나중
위의 ptrstrcmp01.c
코드를 다음과 같이 수정하면 문자열 내용을 비교할 수 있다.
// ptrstrcmp02.c
int main(void)
{
char szBuffer[12] = {"TestString"};
char *pszData = "TestString";
// 다음 코드들은 두 문자열이 같은지 비교하는 것이 아니라
// 문자열이 저장된 메모리의 위치가 같은지 비교하는 것이다.
printf("%d\n", strcmp(szBuffer, pszData));
printf("%d\n", strcmp("TestString", pszData));
printf("%d\n", strcmp("Test", "TestString"));
return 0;
}
/* 출력결과
0
0
-1
*/
11.2.4 문자열 검색
strstr()
함수는 두 문자열의 주소를 인자로 받아 검색하여 결과를 반환해 주는 함수이다.
char strstr(const char *string, const char *strCharSet)
Description : 임의의 대상 문자열에서 특정 문자열을 검색하는 함수
Parameters :
string - 검색 대상이 될 문자열이 저장된 메모리 주소
strCharSet - 검색할 문자열이 저장된 메모리 주소
Return :
문자열을 찾으면 해당 문자열이 저장된 메모리 주소 반환
찾지 못하면
NULL
반환
// ptrsearch01.c
int main(void){
char szBuffer[32] = { "I am a boy." };
// 배열의 주소를 출력한다.
printf("%p\n", szBuffer);
// 대상 문자열에서 문자열을 검색하고 찾은 위치(주소)를 출력한다.
printf("%p\n", strstr(szBuffer, "am"));
printf("%p\n", strstr(szBuffer, "boy"));
// 문자열이 검색된 위치에서 기준이 되는 주소를 빼면
// 인덱스를 계산할 수 있다.
printf("Index: %d\n", strstr(szBuffer, "am") - szBuffer);
printf("Index: %d\n", strstr(szBuffer, "boy") - szBuffer);
return 0;
}
/* 출력결과
0061FF10
0061FF12
0061FF17
Index: 2
Index: 7
*/
위의 코드를 szBuffer
배열의 그림으로 나타내면 아래와 같다.
11.2.5 배열 연산자 풀어쓰기
1차원 배열을 포인터 관점으로 보면, 기준주소에서 일정 인덱스만큼 떨어진 상대주소를 배열 요소의 변수로 지정하는 연산이라 할 수 있다. 즉 , *(기준주소 + 인덱스)
와 기준주소[인덱스]
는 같은 의미이다.
// ptrnarray02.c
int main(void){
char szBuffer[32] = { "You are a girl." };
// 배열의 첫 번째(0번) 요소의 값을 %c 형식으로 출력한다.
printf("%c\n", szBuffer[0]);
// 0번 요소에 대한 주소인 배열의 이름(주소)에 대해 간접지정 연산을
// 수행하고 그 안에 담긴 정보를 출력한다.
printf("%c\n", *szBuffer);
// 0을 더하더라도 주소는 달라지지 않는다.
printf("%c\n", *(szBuffer + 0));
// 배열 연산자는 '기준주소 + 인덱스' 연산결과로 얻은 주소를
// 간접지정한 것과 같다.
printf("%c\n", szBuffer[5]);
printf("%c\n", *(szBuffer + 5));
// 주소연산(&)은 간접지정 연산과 상반된다.szBuffer
// 그러므로 아래 세줄의 코드는 모두 같다.
printf("%s\n", &szBuffer[4]);
printf("%s\n", &*(szBuffer + 4));
printf("%s\n", szBuffer + 4);
printf("%p\n", szBuffer + 4);
return 0;
}
/* 출력결과
Y
Y
Y
r
r
are a girl.
are a girl.
are a girl.
0061FF14
*/
위의 코드에서 %s
는 배열의 주소에 대응하는 형식문자이다. printf()
함수는 %s
와 대응된 인자를 메모리의 주소로 판단하고 그 주소에서 0
이 나올 때 까지 한 글자씩 읽어와 문자열로 출력한다. 즉, %s
는 배열의 이름과 대응된다.
11.2.6 realloc(), sprintf()
함수
realloc()
함수는 이미 할당된 메모리를 이름처럼 재할당하는 함수이다.
void *realloc(void *memblock, size_t size);
Description : 이미 할당된 메모리 영역에서 크기를 조정할 수 있다면, 반환된 주소는 첫 번째 인자로 전달된 주소와 같다. 불가능할 경우 기존의 메모리를 해제하고 새로운 영역에 다시 할당한 후, 새로 할당된 메모리의 주소를 반환한다.
Parameters :
memblock - 기존에 동적 할당된 메모리 주소. 만약 이 주소가
NULL
이면malloc()
함수와 동일하게 동작size - 다시 할당받을 메모리의 바이트 단위 크기
Return : 다시 할당된 메모리 덩어리 중 첫 번째 바이트의 메모리 주소. 실패할 경우
NULL
반환
sprintf()
함수는 printf()
함수와 비슷하지만, 문자열이 콘솔에 출력되는 것이 아니라 특정 주소의 메모리에 출력된다. strcpy()
함수와 유사한 기능을 한다.
int sprintf(char *buffer, const char *format [, argument] ...)
Description : 형식 문자열에 맞춰 특정 메모리에 문자열을 저장하는 함수
Parameters :
buffer - 출력 문자열이 저장될 메모리 주소
format - 형식 문자열이 저장된 메모리 주소
[, argument] - 형식 문자열에 대응하는 가변 인자들
Return : 출력된 문자열의 개수
strcpy(), sprintf()
함수는 보안결함이 있어, Windows 에서는 strcpy_s(), sprintf_s()
함수를 사용하는 것이 좋으며 Linux, UNIX
에서는 strncpy(), snprintf()
를 사용하는 것이 좋다.
다음 예제는 12 바이트의 메모리를 동적 할당한 후 메모리에 문자열을 출력(sprintf()
)하고, 더 긴 문자열 출력을 위해 32바이트로 늘려(realloc()
) 다른 문자열을 출력하는 코드이다.
// ptrrealloc01.c
// _msize() 함수를 위한 헤더 포함
int main(void){
char *pszBuffer = NULL, *pszNewBuffer = NULL;
// 12바이트 메모리를 동적 할당한 후
pszBuffer = (char*)malloc(12);
// NULL 문자를 포함해 영문 11자를 저장한다.
sprintf(pszBuffer, "%s", "TestString");
// 동적할 메모리의 주소, 크기, 저장된 문자열 등을 출력한다.
printf("[%p] %d %s\n", pszBuffer, _msize(pszBuffer), pszBuffer);
// 12바이트의 메모리를 32바이트로 '확장'을 시도한다.
pszNewBuffer = (char*)realloc(pszBuffer, 32);
if (pszNewBuffer == NULL)
free(pszBuffer);
// 문자열을 덮어쓰고 주소, 메모리 크기, 저장된 내용을 확인한다.
sprintf(pszNewBuffer, "%s", "TestStringData");
printf("[%p] %d %s\n", pszNewBuffer, _msize(pszNewBuffer), pszNewBuffer);
free(pszNewBuffer);
return 0;
}
/* 출력결과
[02721C70] 12 TestString
[02721C70] 32 TestStringData
*/
위의 출력결과에서 malloc()
함수를 이용해 12바이트 메모리를 할당한 메모리 주소와 realloc()
를 이용해 32바이트로 메모리를 확장한 메모리 주소는 0x02721C70
으로 같음을 알 수 있다.
'C&C++ > C - Programming' 카테고리의 다른 글
[C]Chap11.3 - 메모리와 변수(3) (0) | 2018.06.18 |
---|---|
[C]Chap11.1 - 메모리와 포인터 (1) (1) | 2018.05.30 |
[C]Chap10 - 함수에 대한 기본 이론 (0) | 2018.05.23 |
[C] Chap09 - 배열을 활용한 프로그래밍 기법 (0) | 2018.05.21 |
[C] Chap08 - 배열 Array (0) | 2018.05.16 |