C에서 동적 메모리 할당에 관한 질문 10개

정글일지 22

C에서 동적 메모리 할당에 관한 10개의 질문

커리어의 시작에서 많은 개발자들은 동적 메모리 할당을 사용하는 것을 두려워 한다. 이 기사에서, 나는 동적 메모리 할당에 관한 10개의 면접 질문을 설명할 것이다.

만약 만약 당신이 C의 동적 메모리 할당에 관한 면접 질문이나 C에서 메모리 할당과 관련된 함정 질문들을 찾고 있다면, 잘 찾아왔다. C 관련 면접에서 면접관에게 들을 수 있는 10개의 C 동적 메모리 할당에 관한 문제와 답을 만들었다. 그래서 나는 C의 동적 메모리 할당에 관한 면접 질문들이 당신에게 도움이 되길 바란다. 행운을 빈다.

질문 1 : malloc과 calloc의 차이는 무엇인가요?

malloc과 calloc은 메모리 관리 함수이다. 그들은 동적으로 메모리를 할당하기 위해 사용된다. 기본적으로, calloc과 malloc은 calloc이 메모리를 0으로 초기화 시키고 할당한다는 것 외에는 실질적인 차이가 없다. C 언어에서, calloc 함수는 모든 할당된 공간 비트를 0으로 초기화 시키지만 malloc은 할당된 메모리를 초기화 시키지 않는다. 이러한 두 함수는 또한 다루는 변수의 갯수에서도 차이를 보이는데, malloc은 1개 calloc은 2개이다.

질문 2 : malloc() 과 calloc() 중 무엇을 쓰는게 나을까요?

calloc 함수는 할당된 메모리를 0으로 초기화하지만 malloc은 그렇지 않다. 그래서 malloc으로 할당된 메모리는 쓰레기 값(garbage data)을 갖는다. 다시 말해, calloc은 malloc과 memeset의 조합과 동일하다고 볼 수 있다.

1
2
3
4
ptr = calloc(nmember, size); //is essentially equivalent to

ptr = malloc(nmember * size);
memset(ptr, 0, (nmember *size));

만약 당신이 할당된 메모리를 0으로 초기화시키길 원치 않는다면, calloc보단 malloc을 쓰는게 낫다.

질문 3 : 할당된 메모리의 일부의 크기를 어떻게 정할 수 있나요?

C 언어에서, 우리는 고정된 배열의 크기를 sizeof operator를 사용하여 계산할 수 있지만 동적 할당 메모리의 크기를 계산하는 operator는 없다. 그래서 몇가지 기술을 써서 할당된 배열의 크기를 알아낼 수 있다. 코드의 각 부분에서 할당된 메모리의 크기를 알아내는 주요 두가지 방법은 다음과 같다.

  • 할당된 메모리의 크기를 저장하는 전역 변수 만들기
  • 할당된 메모리의 길이를 가져가기

어떻게 배열의 길이를 가져갈 수 있는지를 설명하는 예시 코드를 보자. 당신이 크기가 n 인 정수(int) 배열을 만든다고 가정하자. 배열의 길이를 가져가기 위해, 당신은 메모리를 n+1만큼 할당해야 한다.

int *piAraay = malloc ( sizeof(int)*(n+1));

만약 메모리가 성공적으로 할당되면, 인덱스를 0으로 하는 위치에 n(배열의 크기)를 지정하라

1
2
3
piArray[0] = n;
	or
*piArray = n;

원래(original) 포인터의 복사본을 만들 시간이지만 왼쪽 출발에서 한 지점 옆이다.

int * pTmpArray = piArray +1;

이제 프로그램에서 당신이 동적 할당 메모리의 크기를 요구할때 마다, 당신은 임시 포인터(copy pointer)를 얻는다.

int ArraySize = pTmpArray[-1];

할당된 메모리를 사용한후에 비할당화 하는 것을 잊지 말자.

free (piArray);

질문 4 : realloc()의 목적은 무엇인가요?

realloc 함수는 메모리의 할당된 블록의 크기를 재조정하기 위해 사용된다. 두 개의 변수를 취하는데 하나는 이전에 할당된 메모리를 가리키는 포인터이고 두번째는 새롭게 요구된 크기이다. realloc 함수는 우선 이전 객체를 비할당화하고 새롭게 지정된 크기를 다시 할당한다. 만약 새로운 크기가 이전 것보다 작다면, 새롭게 할당된 메모리의 내용물은 이전과 같겠지만, 새롭게 만들어진 객체가 이전 크기를 몇 바이트 넘어선다면 객체의 값은 결정되지 않을 것이다(indeterminate).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    char *pcBuffer = NULL;

    /* Initial memory allocation */
    pcBuffer = malloc(8);

    strcpy(pcBuffer, "aticle");

    printf("pcBuffer = %s\n", pcBuffer);
    /* Reallocating memory */
    pcBuffer = realloc(pcBuffer, 15);
    strcat(pcBuffer, "world");
    printf("pcBuffer = %s\n", pcBuffer);
    free(pcBuffer);
    return 0;
}

출력 값

1
2
pcBuffer = aticle
pcBuffer = aticleworld

오직 동적 할당 메모리에서만 사용되어야 하지만 만약 포인터가 null 포인터라면, realloc은 malloc 함수처럼 기능한다.

질문 5 : 동적 메모리 할당과 정적 메모리 할당의 차이는 무엇인가요?

C 표준에 따르면, 4개의 저장 기간이 있는데 static, thread(C11), automatic, 그리고 allocated(할당)이다.

The static memory allocation :

정적 메모리는 객체가 외부 혹은 내부의 연결을 가지거나 static storage-class라고 선언된 것을 의미한다. 프로그램이 시작하기 전에 오직 한번 초기화되고, 일생기간은 프로그램의 실행기간이다. 전역 변수와 정적 변수(glo bal and static variable)는 정적 메모리 할당의 예시이다.

The dynamic memory allocation :

C 언어에서, malloc, calloc, realloc 등 동적으로 메모리를 할당하는데 사용되는 많은 함수가 있다. 동적으로 할당된 메모리의 문제 중 하나는 컴파일러 스스로 파괴할 수 없고 사용자가 비할당화할 책임이 있다는 것이다.

우리가 메모리 관리 함수를 사용하여 메모리를 할당할 때, 함수들은 할당된 메모리 블록을 가리키는 포인터를 반환하고 반환된 포인터는 메모리 블록의 시작 주소를 가리킨다. 만약 이용 가능한 공간이 없다면 이 함수는 null 포인터를 반환한다.

질문 6 : malloc(0) 의 반환 값은 무엇인가요?

만약 요구된 공간의 크기가 0이라면, 이 행동은 정의된 실행이 될 것이다. malloc의 반환 값은 null 포인터이거나 사이즈가 0이 아닌듯한 행동을 보여주는 것이다. 그래서 당신은 C 프로그램에서 절대 malloc(0)을 사용해선 안된다.

C 프로그램 예시를 보자.

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
#include<stdio.h>
#include<stdlib.h>

int main (void)
{
    int *piBuffer = NULL;

    //allocating memory using
    //the malloc with size 0.
    piBuffer = malloc(0);

    //make sure piBuffer is valid or not
    if (piBuffer == NULL)
    {
        //allocation failed, exit from the program
        fprintf(stderr, "Out of memory!\n");
        exit(1);
    }

    *piBuffer = 10;

    printf("%d\n", *piBuffer);

    free(piBuffer);
    return 0;
}

출력값 : Implementation-dependent

질문 7 : C에서 메모리 누수(memory leak)이란 무엇일까요?

메모리 누수는 흔하고 위험한 문제이다. 자원 유출의 일종이다. C 언어에서, 메모리 누수는 당신이 메모리 관리 함수를 사용하여 한 블록의 메모리를 할당하고 비할당화하는 것을 잊었을 때 발생한다.

1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>
#include<stdlib.h>

int main ()
{
    char * pBuffer = malloc(sizeof(char) * 20);

    /* Do some work */

    return 0; /*Not freeing the allocated memory*/
}

한번 메모리를 할당하면 할당된 메모리는 비할당화 되기 전까지 다른 프로그램이나 프로세스에 할당되지 않는다.

질문 8 : 동적 메모리 단편화는 무엇인가요?

메모리 관리 함수는 할당된 메모리가 객체와 나란히 조정되었다는 보장을 준다. 기본 나열(fundamental alignment)은 나열 구체화(alignment specification)없는 실행에 의한 가장 큰 나열과 작거나 같다.(The fundamental alignment is less than or equal to the largest alignment that’s supported by the implementation without an alignment specification.)

동적 메모리 할당의 주요한 문제 중 하나는 단편화이다. 기본적으로, 단편화는 사용자가 메모리를 효율적으로 사용하지 않을 때 발생했다. 외부 혹은 내부 단편화라는 두가지 종류가 있다.

external fragmentation

외부 단편화는 자유로운 작은 메모리 블록(small memory hole)때문인데 이것은 자유롭게 사용 가능한 메모리지만 프로그램이 사용할 수 없는 것이다. 자유로운 메모리 블록을 효율적으로 사용하는 할당 알고리즘은 다양하다.

프로그램이 3개의 연속된 메모리 블록들을 가졌고 사용자가 가운데 블록을 비할당화하였다고 가정하자. 이 경우, 단일 메모리 블록보다 요구하는 메모리 블록이 크다면, 당신은 메모리를 쓸 수 없다. (1부터 10까지의 칸이 있는데 1~3,4~5,6~8 세 블록을 프로그램이 쓰고 있었다. 4~5를 비할당화 하였다. 그럼 이용 가능한 메모리는 4~5,9~10 총 2겠지만 실제 2를 크기로 하는 블록은 사용할 수 없다. 각각 1씩 단편화 되어있기 때문이다.)

internal fragmentation

내부 단편화는 할당된 메모리를 하나로 모으거나 bookkeeping(infrastructure)에서 할당된 메모리의 낭비이다. (The internal fragmentation is the wastage of memory that is allocated for rounding up the allocated memory and in bookkeeping (infrastructure).) bookkeeping 메모리는 할당된 메모리의 정보를 유지하는데 사용된다.

우리가 malloc 함수를 호출할때 마다 실행과 시스템에 따라 bookkeeping을 위해 여분의 바이트들을 따로 챙겨둔다. 이 여분의 바이트들은 malloc의 각 호출을 위해 남겨지고 내부 단편화의 원인이 된다.

내부 단편화를 이해하기 위해 예시 프로그램을 보자.

아래 코드에서, 개발자는 시스템이 8*100(800) 바이트의 메모리를 할당했을 것이라고 생각할지도 모르지만, bookkeeping으로 인해(bookkeeping 바이트가 8이라면) 시스템은 800의 여분 바이트를 더 할당할 것이다. 어떻게 내부 단편화가 힙 메모리를 낭비하는지 볼 수 있다. 이건 단지 실제 동작이 실행-의존적이라는 것을 이해하기 위해서다.

1
2
3
4
5
6
7
8
9
10
11
12
13
char *acBuffer[100];

int main()
{
	int iLoop = 0;

	while(iLoop < 100)
	{
		acBuffer[iLoop ] = malloc(8);

		++iLoop
	}
}

질문 8 : free는 어떻게 작동할까요?

우리가 malloc, calloc, realloc 함수를 호출할 때, 이 함수들은 bookkeeping을 위해 여분의 바이트를 챙겨놓는다. 우리가 free 함수를 호출하고 할당된 메모리를 가리키는 포인터를 통과(pass)할 때 마다 free 함수는 bookkeeping 정보를 얻고 할당된 메모리를 release한다. 사용자나 프로그램이 할당된 주소를 가리키는 포인터의 값을 바꾸면, free 함수의 호출은 정의되지 않은 결과를 낸다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <stdlib.h>

int main()
{
    char *pcBuffer = NULL;
    //Allocate the memory
    pcBuffer = malloc(sizeof(char) * 16);

    //make sure piBuffer is valid or not
    if (pcBuffer == NULL)
    {
        // allocation failed, exit from the program
        fprintf(stderr, "Out of memory!\n");
        exit(1);
    }
    //Increment the pointer
    pcBuffer++;
    //Call free function to release the allocated memory
	free(pcBuffer);

    return 0;
}

Output : undefined Result

질문 9 : dagling 포인터는 무엇인가요?

일반적으로, dangling 포인터는 참조된 객체가 포인터의 값을 바꾸지 않고 삭제되거나 비할당화될떄 발생한다. 포인터는 여전히 메모리를 가리키지만 그 메모리는 이용 가능하지 않기 때문에 문제가 발생한다. 사용자가 daggling 포인터를 비참조화(dereference)하려고 할때 정의되지 않은 동작을 하거나 분할 실패(segmentation fault)의 원인이 될 수 있다.

쉽게 말해, dangling 포인터는 적절한 타입의 타당한 객체를 가리키지 않는 포인터이고 정의되지 않은 동작의 원인이 될 수 있다고 말할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<stdio.h>
#include<stdlib.h>

int main()
{
    int *piData = NULL;

    //creating integer of size 10.
    piData = malloc(sizeof(int)* 10);
    //make sure piBuffer is valid or not
    if (piData == NULL)
    {
        //allocation failed, exit from the program
        fprintf(stderr, "Out of memory!\n");
        exit(1);
    }
    free(piData); //free the allocated memory

    *piData = 10; //piData is dangling pointer

    printf("%d\n", *piData);
    return 0;
}

Output : undefined