수색…


소개

동적으로 할당 된 메모리를 관리하기 위해 표준 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 , reallocaligned_alloc ) 메모리의 총 크기 (바이트). aligned_alloc 경우, 크기는 정렬의 정수배 여야합니다.
크기 ( calloc ) 각 요소의 크기
nelements 원소의 수
태평양 표준시 이전에 malloc , calloc , realloc 또는 aligned_alloc 의해 반환 된 할당 메모리에 대한 포인터
조정 할당 된 메모리 정렬

비고

C11

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 charunsigned 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

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

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+1argv 인수에 대해 C 표준에서 규정하는 최소 길이입니다.

공식적으로 VLA 지원은 C11에서는 선택 사항이지만 C11을 구현하고 컴파일러가없는 컴파일러는 알지 못합니다. 꼭 __STDC_NO_VLA__ 경우 __STDC_NO_VLA__ 매크로를 사용하여 테스트 할 수 있습니다.

realloc (ptr, 0)은 free (ptr)와 동일하지 않습니다.

realloc 은 다른 포인터에서 malloc + memcpy + free 와 개념적으로 동일 합니다.

요청 된 공간의 크기가 0이면 realloc 의 동작은 구현에 따라 정의됩니다. 이것은 값 0size 매개 변수를받는 모든 메모리 할당 함수에서 비슷합니다. 이러한 함수는 사실 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() 를 사용하지 마십시오.
C99

현대 대안.

void foo(int size) {
    char data[size];
    /*
      function body;
    */
    // data is automatically freed
}

이것은 alloca() 가 동작하는 곳에서 작동하며, alloca() 가 (예를 들어 루프 내부에서 alloca() 작동하지 않는 곳에서 작동합니다. C99 구현 또는 __STDC_NO_VLA__ 정의하지 않는 C11 구현을 가정합니다.



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow