C Language
메모리 관리
수색…
소개
동적으로 할당 된 메모리를 관리하기 위해 표준 C 라이브러리는 malloc()
, calloc()
, realloc()
및 free()
함수를 제공합니다. C99 이상에서는 aligned_alloc()
있습니다. 일부 시스템에서는 alloca()
도 제공합니다.
통사론
- void * aligned_alloc (size_t alignment, size_t size); / * C11 이후 만 * /
- void * calloc (size_t nelements, size_t size);
- void free (void * ptr);
- void * malloc (size_t size);
- void * realloc (void * ptr, size_t size);
- void * alloca (size_t size); / * alloca.h에서 가져온 것이지 표준이 아니고 이동성이 없으며 위험합니다. * /
매개 변수
이름 | 기술 |
---|---|
크기 ( malloc , realloc 및 aligned_alloc ) | 메모리의 총 크기 (바이트). aligned_alloc 경우, 크기는 정렬의 정수배 여야합니다. |
크기 ( calloc ) | 각 요소의 크기 |
nelements | 원소의 수 |
태평양 표준시 | 이전에 malloc , calloc , realloc 또는 aligned_alloc 의해 반환 된 할당 메모리에 대한 포인터 |
조정 | 할당 된 메모리 정렬 |
비고
aligned_alloc()
은 C11 이상에서만 정의됩니다.
POSIX에 기반한 시스템과 같은 시스템은 정렬 된 메모리 (예 : posix_memalign()
)를 할당하는 다른 방법을 제공하며 mmap()
같은 다른 메모리 관리 옵션도 제공합니다.
메모리 해제
free () 를 호출하여 동적으로 할당 된 메모리를 해제 할 수 있습니다.
int *p = malloc(10 * sizeof *p); /* allocation of memory */
if (p == NULL)
{
perror("malloc failed");
return -1;
}
free(p); /* release of memory */
/* note that after free(p), even using the *value* of the pointer p
has undefined behavior, until a new value is stored into it. */
/* reusing/re-purposing the pointer itself */
int i = 42;
p = &i; /* This is valid, has defined behaviour */
p
가 가리키는 메모리는 free()
호출 한 후 (libc 구현 또는 기본 OS에 의해) 다시 확보되므로, 해제 된 메모리 블록에 p
를 통해 액세스하면 정의되지 않은 동작이 발생 합니다. 해제 된 메모리 요소를 참조하는 포인터는 일반적으로 포인터 를 사용하지 않고 보안 위험이 있습니다. 또한 C 표준은 매달려있는 포인터 의 값 에 액세스 하는 경우에도 정의되지 않은 동작이 있음을 나타냅니다. 위와 같이 포인터 p
자체를 다시 사용할 수 있습니다.
malloc()
, calloc()
, realloc()
및 aligned_alloc()
함수에서 직접 반환 된 포인터에 대해서만 free()
를 호출 할 수 있습니다. 또는 메모리가 그런 방식으로 할당되었다는 것을 문서가 알려주는 곳에서 함수 같은 strdup ()
주목할만한 예제입니다). 포인터를 해제하면,
- 변수에
&
연산자를 사용하여 얻은 - 할당 된 블록의 중간에,
금지되어 있습니다. 이러한 오류는 일반적으로 컴파일러에서 진단하지 않지만 정의되지 않은 상태로 프로그램 실행을 유도합니다.
이러한 정의되지 않은 동작을 방지하기위한 일반적인 전략은 두 가지가 있습니다.
첫 번째와 바람직한 것은 간단합니다. 예를 들어 p
가 더 이상 필요하지 않을 때 p
가 더 이상 존재하지 않게됩니다.
if (something_is_needed())
{
int *p = malloc(10 * sizeof *p);
if (p == NULL)
{
perror("malloc failed");
return -1;
}
/* do whatever is needed with p */
free(p);
}
포함하는 블록의 끝 (즉, }
앞에 직접 free()
를 호출하면 p
자체가 사라집니다. 컴파일러는 그 이후에 p
를 사용하려고 시도 할 때 컴파일 오류를 발생시킵니다.
두 번째 방법은 포인터가 가리키는 메모리를 해제 한 후 포인터 자체를 무효화하는 것입니다.
free(p);
p = NULL; // you may also use 0 instead of NULL
이 접근법에 대한 논거 :
많은 플랫폼에서 널 포인터를 참조 해제하려고하면 즉시 충돌이 발생합니다 : 분할 오류. 여기서 적어도 해제 된 후에 사용 된 변수를 가리키는 스택 추적을 얻습니다.
포인터를
NULL
설정하지 않으면 포인터가 매달려 있습니다. 포인터가 가리키는 메모리가 조용히 손상되기 때문에 프로그램은 여전히 충돌 할 가능성이 높습니다. 이러한 버그는 초기 문제와 완전히 관련이없는 호출 스택을 초래할 수 있기 때문에 추적하기가 어렵습니다.따라서이 접근법은 fail-fast 개념을 따른다.
null 포인터를 해제하는 것이 안전합니다. C 표준 은
free(NULL)
이 영향을 미치지 않도록 지정합니다.free 함수는 ptr이 가리키는 공간이 할당 해제되도록합니다. 즉, 더 많은 할당을 위해 사용 가능하게됩니다. ptr이 널 (NULL) 포인터이면 조치가 수행되지 않습니다. 그렇지 않으면 인수가
calloc
,malloc
또는realloc
함수에 의해 이전에 반환 된 포인터와 일치하지 않거나free
또는realloc
에 대한 호출로 공간 할당이 취소 된 경우 동작은 정의되지 않습니다.
- 때로는 첫 번째 방법을 사용할 수없는 경우가 있습니다 (예 : 메모리가 한 함수에 할당되어 완전히 다른 함수에서 훨씬 더 나중에 할당 취소됨)
메모리 할당
표준 할당
C 동적 메모리 할당 함수는 <stdlib.h>
헤더에 정의됩니다. 객체에 대한 메모리 공간을 동적으로 할당하려면 다음 코드를 사용할 수 있습니다.
int *p = malloc(10 * sizeof *p);
if (p == NULL)
{
perror("malloc() failed");
return -1;
}
이 10 바이트의 수를 계산 int
다음 많은 바이트 것을 요청, s는 메모리 점유를 malloc
하고 그 결과를 할당합니다 (즉, 단지 사용하여 생성 된 메모리 청크의 시작 주소 malloc
) 포인터라는 이름으로 p
.
사용하는 것이 좋습니다 sizeof
한 결과 이후 요청에 메모리의 양을 계산하는데 sizeof
(문자있는 유형을 제외하고 정의 구현 char
, signed char
및 unsigned char
하는, sizeof
항상 제공하기 위해 정의된다 1
).
malloc
은 요청을 처리 할 수 없기 때문에 널 포인터를 리턴 할 수 있습니다. 나중 포인터가 역 참조를 시도하지 못하게하려면이 점을 검사하는 것이 중요합니다.
malloc()
사용하여 동적으로 할당 된 메모리는 realloc()
사용하여 크기를 조정하거나 더 이상 필요하지 않을 때 free()
사용하여 해제 할 free()
있습니다.
또는, int array[10];
선언하십시오 int array[10];
동일한 양의 메모리를 할당합니다. 그러나 static
키워드가없는 함수 내에서 선언 된 경우에는 선언 된 함수와 함수에서 사용할 수 있습니다 (배열이 스택에 할당되고 공간이 재사용을 위해 해제 될 때 함수가 돌아옵니다). 또는 함수 내부에서 static
으로 정의되거나 함수 외부에서 정의 된 경우 해당 수명은 프로그램의 수명입니다. 함수에서 포인터를 반환 할 수도 있지만 C에서 함수는 배열을 반환 할 수 없습니다.
제로 메모리
malloc
의해 반환 된 메모리는 적절한 값으로 초기화되지 않을 수 있으며 memset
하여 메모리를 0으로 만들거나 적절한 값을 즉시 복사해야합니다. 또는 calloc
은 모든 비트가 0
으로 초기화되는 원하는 크기의 블록을 반환합니다. 이것은 부동 소수점 0 또는 널 포인터 상수의 표현과 동일 할 필요는 없습니다.
int *p = calloc(10, sizeof *p);
if (p == NULL)
{
perror("calloc() failed");
return -1;
}
calloc
에 대한 참고 사항 : 대부분의 (일반적으로 사용되는) 구현은 성능을 위해 calloc()
을 최적화하므로 net 효과가 동일하더라도 malloc()
, memset()
을 호출하는 것 보다 빠릅니다 .
정렬 된 메모리
C11은 주어진 정렬로 공간을 할당하는 새로운 함수 aligned_alloc()
을 도입했습니다. 할당 할 메모리가 malloc()
또는 calloc()
의해 충족 될 수없는 특정 경계에서 정렬되어야하는 경우에 사용할 수 있습니다. malloc()
과 calloc()
함수는 모든 객체 유형에 맞게 정렬 된 메모리를 할당합니다. 즉 정렬은 alignof(max_align_t)
)입니다. 그러나 aligned_alloc()
더 큰 정렬을 요청할 수 있습니다.
/* Allocates 1024 bytes with 256 bytes alignment. */
char *ptr = aligned_alloc(256, 1024);
if (ptr) {
perror("aligned_alloc()");
return -1;
}
free(ptr);
C11 표준은 두 가지 제한을 부과합니다. 1) 요청 된 크기 (두 번째 인수)는 정렬의 첫 번째 인수의 정수 배수 여야하며 2) 정렬 의 값은 구현에서 지원하는 유효한 정렬이어야합니다. 둘 중 하나를 충족시키지 않으면 정의되지 않은 동작이 발생 합니다.
메모리 재 할당
메모리를 할당 한 후 포인터 저장 공간을 확장하거나 축소해야 할 수 있습니다. void *realloc(void *ptr, size_t size)
기능은 기존의 객체가 가리키는 할당을 취소 ptr
에 의해 지정된 크기가 객체에 대한 포인터를 반환 size
. ptr
은 이전에 malloc
, calloc
또는 realloc
(또는 널 포인터)로 할당 된 메모리 블록에 대한 포인터입니다. 원본 메모리의 가능한 최대 내용이 보존됩니다. 새 크기가 더 큰 경우 이전 크기를 초과하는 추가 메모리는 초기화되지 않습니다. 새 크기가 더 짧으면 수축 된 부분의 내용이 손실됩니다. ptr
이 NULL이면, 새로운 블록이 할당되고 그것에 대한 포인터가 함수에 의해 리턴됩니다.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *p = malloc(10 * sizeof *p);
if (NULL == p)
{
perror("malloc() failed");
return EXIT_FAILURE;
}
p[0] = 42;
p[9] = 15;
/* Reallocate array to a larger size, storing the result into a
* temporary pointer in case realloc() fails. */
{
int *temporary = realloc(p, 1000000 * sizeof *temporary);
/* realloc() failed, the original allocation was not free'd yet. */
if (NULL == temporary)
{
perror("realloc() failed");
free(p); /* Clean up. */
return EXIT_FAILURE;
}
p = temporary;
}
/* From here on, array can be used with the new size it was
* realloc'ed to, until it is free'd. */
/* The values of p[0] to p[9] are preserved, so this will print:
42 15
*/
printf("%d %d\n", p[0], p[9]);
free(p);
return EXIT_SUCCESS;
}
재 할당 된 객체는 *p
와 같은 주소를 가질 수도 있고 가지지 않을 수도 있습니다. 따라서 호출이 성공적이면 새 주소가 들어있는 realloc
에서 반환 값을 캡처하는 것이 중요합니다.
realloc
의 반환 값을 원본 p
아닌 temporary
값으로 지정해야합니다. realloc
은 포인터를 덮어 쓰는 오류가 발생할 경우 null을 반환합니다. 이렇게하면 데이터가 손실되고 메모리 누수가 발생합니다.
다양한 크기의 다차원 배열
C99 이후 C는 가변 길이 배열 인 VLA를 사용하여 초기화 시간에만 알려진 경계가있는 모델 배열을 만듭니다. VLA에 대한 포인터를 사용하고 sizeof
표현식에 사용하면 너무 큰 VLA (스택을 손상시킬 수 있음)를 할당하지 않도록주의해야합니다.
double sumAll(size_t n, size_t m, double A[n][m]) {
double ret = 0.0;
for (size_t i = 0; i < n; ++i)
for (size_t j = 0; j < m; ++j)
ret += A[i][j]
return ret;
}
int main(int argc, char *argv[argc+1]) {
size_t n = argc*10;
size_t m = argc*8;
double (*matrix)[m] = malloc(sizeof(double[n][m]));
// initialize matrix somehow
double res = sumAll(n, m, matrix);
printf("result is %g\n", res);
free(matrix);
}
여기서 matrix
타입의 엘리먼트에 대한 포인터 double[m]
, 그리고 sizeof
와 식 double[n][m]
그것을위한 공간이 포함되도록 n
이러한 요소.
이 공간은 모두 연속적으로 할당되므로 free
to single call을 사용하여 할당을 취소 할 수 있습니다.
언어에서의 VLA의 존재는 함수 헤더에있는 배열 및 포인터의 가능한 선언에 영향을 미칩니다. 이제 일반 정수 표현식은 배열 매개 변수의 []
안에 허용됩니다. 두 함수 모두 []
의 식은 매개 변수 목록에서 전에 선언 된 매개 변수를 사용합니다. sumAll
이들은 사용자 코드가 행렬에 대해 기대하는 길이입니다. C의 모든 배열 함수 매개 변수에 대해 가장 안쪽의 차원이 포인터 유형으로 다시 작성되므로이 선언은 선언과 동일합니다.
double sumAll(size_t n, size_t m, double (*A)[m]);
즉, n
은 실제로 함수 인터페이스의 일부는 아니지만 정보는 문서화에 유용 할 수 있으며 범위를 벗어나는 액세스에 대해 경고하기 위해 경계 검사 컴파일러에서 사용할 수도 있습니다.
마찬가지로, main
경우 argc+1
은 argv
인수에 대해 C 표준에서 규정하는 최소 길이입니다.
공식적으로 VLA 지원은 C11에서는 선택 사항이지만 C11을 구현하고 컴파일러가없는 컴파일러는 알지 못합니다. 꼭 __STDC_NO_VLA__
경우 __STDC_NO_VLA__
매크로를 사용하여 테스트 할 수 있습니다.
realloc (ptr, 0)은 free (ptr)와 동일하지 않습니다.
realloc
은 다른 포인터에서 malloc + memcpy + free
와 개념적으로 동일 합니다.
요청 된 공간의 크기가 0이면 realloc
의 동작은 구현에 따라 정의됩니다. 이것은 값 0
의 size
매개 변수를받는 모든 메모리 할당 함수에서 비슷합니다. 이러한 함수는 사실 null이 아닌 포인터를 반환 할 수 있지만 역 참조하면 안됩니다.
따라서 realloc(ptr,0)
은 free(ptr)
와 동일하지 않습니다. 그것은
- "게으른"구현이어야하며 단지
ptr
반환하십시오. -
free(ptr)
, 더미 요소를 할당하고 -
free(ptr)
및 반환0
- 실패한 경우
0
을 반환하고 다른 작업은 수행하지 않습니다.
특히 후자의 두 경우는 응용 프로그램 코드에서 구별 할 수 없습니다.
이것은 realloc(ptr,0)
이 실제로 메모리를 free / deallocate하지 않을 수 있음을 의미하므로 free
대신하여 사용해서는 안됩니다.
사용자 정의 메모리 관리
malloc()
종종 기본 운영 체제 함수를 호출하여 메모리 페이지를 가져옵니다. 그러나 함수에 특별한 것은 없으며 큰 정적 배열을 선언하고 할당함으로써 똑바로 C로 구현할 수 있습니다 (실제로는 8 바이트로 정렬하는 것이 거의 항상 적절합니다).
간단한 방식을 구현하기 위해 포인터가 호출에서 반환되기 바로 전에 메모리 영역에 제어 블록이 저장됩니다. 이것은 free()
가 반환 된 포인터에서 빼고 제어 정보를 읽음으로써 구현 될 수 있음을 의미합니다. 제어 정보는 일반적으로 블록 크기와 빈 테이블에 다시 넣을 수있는 정보, 즉 할당되지 않은 블록의 연결 목록입니다.
사용자가 할당을 요청하면 요청 된 양과 같거나 더 큰 크기의 블록이 발견 될 때까지 사용 가능리스트가 검색되고 필요하면 분할됩니다. 사용자가 많은 양의 할당을 계속해서 예측할 수없는 간격으로 예측할 수없는 간격으로 해제하는 경우 메모리 조각화가 발생할 수 있습니다. 모든 실제 프로그램이 이와 같이 동작하는 것은 아니며 간단한 프로그램은 종종 작은 프로그램에 적합합니다.
/* typical control block */
struct block
{
size_t size; /* size of block */
struct block *next; /* next block in free list */
struct block *prev; /* back pointer to previous block in memory */
void *padding; /* need 16 bytes to make multiple of 8 */
}
static struct block arena[10000]; /* allocate from here */
static struct block *firstfree;
많은 프로그램은 같은 크기의 작은 개체를 많은 수의 할당해야합니다. 이것은 매우 구현하기 쉽습니다. 다음 포인터가있는 블록을 사용하기 만하면됩니다. 따라서 32 바이트 블록이 필요한 경우 :
union block
{
union block * next;
unsigned char payload[32];
}
static union block arena[100];
static union block * head;
void init(void)
{
int i;
for (i = 0; i < 100 - 1; i++)
arena[i].next = &arena[i + 1];
arena[i].next = 0; /* last one, null */
head = &block[0];
}
void *block_alloc()
{
void *answer = head;
if (answer)
head = head->next;
return answer;
}
void block_free(void *ptr)
{
union block *block = ptr;
block->next = head;
head - block;
}
이 구성표는 매우 빠르고 효율적이며 일정한 선명도를 잃지 않고 일반화 할 수 있습니다.
alloca : 스택에 메모리 할당
주의 사항 : alloca
는 완전한 설명을 위해 여기에 언급되었습니다. 이 제품은 완전히 이식성이 없으며 (일반적인 표준으로는 다루지 않음) 인식 할 수없는 많은 위험 요소가 있습니다. 최신 C 코드는 VLA ( Variable Length Arrays)로 대체해야합니다.
#include <alloca.h>
// glibc version of stdlib.h include alloca.h by default
void foo(int size) {
char *data = alloca(size);
/*
function body;
*/
// data is automatically freed
}
호출자의 스택 프레임에 메모리를 할당하면 호출자 함수가 끝나면 반환 된 포인터에 의해 참조 된 공간이 자동으로 해제 됩니다.
이 함수는 자동 메모리 관리에 편리하지만 대량 할당을 요청하면 스택 오버플로가 발생할 수 있으며 alloca
할당 된 메모리에는 free
를 사용할 수 없습니다 (스택 오버플로에 더 많은 문제가 발생할 수 있음).
이러한 이유로 alloca
는 루프 나 재귀 함수 내에서 사용하지 않는 것이 좋습니다.
함수가 반환 될 때 메모리가 free
때문에 포인터를 함수 결과로 반환 할 수 없습니다 ( 동작은 정의되지 않습니다 ).
개요
-
malloc
과 똑같이 호출한다. - 함수 반환시 자동으로 free'd
-
free
,realloc
함수와 호환되지 않음 ( 정의되지 않은 동작 ) - 함수 결과로 포인터를 반환 할 수 없습니다 ( 정의되지 않은 동작 ).
- 할당 크기는 스택 공간에 의해 제한되며, 대부분의 컴퓨터에서
malloc()
사용할 수있는 힙 공간보다 훨씬 작습니다. - 단일 함수에서
alloca()
및 VLA (가변 길이 배열) 사용하지 않음 -
alloca()
는malloc()
외에도 이식성이malloc()
추천
- 새 코드에서
alloca()
를 사용하지 마십시오.
현대 대안.
void foo(int size) {
char data[size];
/*
function body;
*/
// data is automatically freed
}
이것은 alloca()
가 동작하는 곳에서 작동하며, alloca()
가 (예를 들어 루프 내부에서 alloca()
작동하지 않는 곳에서 작동합니다. C99 구현 또는 __STDC_NO_VLA__
정의하지 않는 C11 구현을 가정합니다.