Unit 35. 메모리 사용하기

포인터에 원하는 만큼 메모리 공간을 할당받아 사용할 수 있다.

메모리는 malloc -> 사용 -> free의 패턴으로 사용한다.

35.1 메모리 할당하기

메모리를 사용하려면 malloc 함수로 사용할 메모리 공간을 확보해야 한다. 할당할 메모리 크기는 바이트 단위로 지정한다. malloc함수는 stdlib.h 헤더파일에 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    int num1 = 20;
    int *numPtr1;
 
    numPtr1 = &num1;
 
    int *numPtr2;
    numPtr2 = malloc(sizeof(int));
 
    printf("%p\n", numPtr1);
    printf("%p\n", numPtr2);
    
    free(numPtr2);
    return 0;
}
cs

메모리를 할당할 때는 malloc 함수를 사용하여 할당할 메모리 공간의 크기를 넣어주는데, 위 코드에서는 sizeof 연산자를 이용하여 int크기 만큼의 즉 4바이트 크기의 메모리를 할당했다.

이처럼 원하는 시점에 원하는 메모리를 할당할 수 있어 동적 메모리 할당이라 부른다.

출력값은 같은 메모리 주소를 출력하는것 같지만 메모리 주소에는 내부적인 차이가 있다. 변수는 스택에 생성되며 malloc함수는 힙부분의 메모리를 사용한다.

스택에 생성된 변수는 사용한뒤 따로 처리를 하지 않아도 되지만 malloc함수를 사용하여 힙에 할당한 메모리는 반드시 해제를 해야 한다. free 함수를 이용하여 메모리를 해제할 수 있다.

메모리를 해제하지 않으면 시스템의 메모리가 부족해져 운영체제가 프로그램을 강제로 종료시키거나 메모리 할당에 실패할 수 있다. 메모리를 해제하지 않아 메모리 사용량이 계속 증가하는 현상을 메모리 누수(memory leak)라 한다.

35.2 메모리에 값 저장하기

다음은 할당한 메모리에 값을 저장하는 코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    int *numPtr;
    numPtr = malloc(sizeof(int));
    *numPtr = 10;
 
    printf("%d\n"*numPtr);
    free(numPtr);
    return 0;
}
cs

malloc 함수로 할당한 메모리에 값을 저장할 때는 포인터를 역참조한 뒤 값을 저장하면 된다. 값을 출력할 때도 역참조를 사용하면 된다. 메모리를 할당하고 사용한 뒤에는 반드시 free함수로 해제를 해 줘야 한다.

35.3 메모리 내용을 한꺼번에 설정하기

memset 함수를 사용하면 메모리의 내용을 원하는 크기 만큼 특정 값으로 설정할 수 있다. 설정하는 크기는 바이트 단위이며 이 함수는 string.h 헤더파일에 선언되어 있다. memset 함수는 다음과 같이 사용한다.

memset(포인터, 설정할값, 크기);

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int main()
{
    long long *numPtr = malloc(sizeof(long long));
    memset(numPtr, 0x278);
 
    printf("0x%llx"*numPtr);
    free(numPtr);
    return 0;
}
cs

long long 자료형의 크기인 8바이트 만큼 동적 메모리를 할당하고, memset 함수로 numPtr이 가리키는 메모리에 16진수 27이 8개 들어가도록 한다.

memset함수는 다음과 같이 설정할 값을 모두 0으로 지정하여 메모리의 내용을 모두 0으로 만들 때 주로 사용한다.

memset(numPtr, 0, 8); 

35.4 널 포인터 사용하기

1
2
3
4
5
6
7
8
9
#include <stdio.h>
 
int main()
{
    int *numPtr1 = NULL;  
 
    printf("%p\n", numPtr1); 
    return 0;
}
cs

위 코드와 같이 NULL이 들어있는 포인터를 널 포인터라고 하며 아무것도 가리키지 않는 상태이다. 역참조도 할 수 없다.

실무에서는 다음과 같이 포인터가 NULL인지 확인하고, NULL이면 메모리를 할당하는 방식을 주로 사용한다.

if (ptr == NULL)      
{
    ptr = malloc(1024);  
}

35.5 퀴즈

정답은 d이다.

정답은 c이다.

정답은 b이다.

정답은 a이다.

정답은 d이다.

정답은 널 포인터 이다.

35.6 연습문제 : 메모리 할당하기

정답은 다음과 같다.

1. malloc(sizeof(int));

2. malloc(sizeof(long long));

각각 4바이트와 8바이트 만큼의 메모리를 할당해 줘야 하기 때문이다.

35.7 심사문제 : 두 정수의 합 구하기

정답은 다음 코드와 같다.

1
2
int *numPtr1 = malloc(sizeof(int));
int *numPtr2 = malloc(sizeof(int));
cs

Unit 36. 배열 사용하기

배열은 같은 자료형의 변수를 일렬로 늘어놓은 형태이다.

반복문과 결합하면 연속적이고 반복되는 값을 손쉽게 처리할 수 있다.

36.1 배열을 선언하고 요소에 접근하기

배열은 변수 이름 뒤에 [](대괄호)를 붙인 뒤 크기를 설정한다. 배열을 선언하며 값을 초기화 할 때는 {}(중괄호)를 사용한다.

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
 
int main()
{
    int numArr[10= { 112233445566778899110 };    
 
    printf("%d\n", numArr[0]);    
    printf("%d\n", numArr[5]);    
    printf("%d\n", numArr[9]);    
    return 0;
}
cs

크기가 10인 int형 배열을 선언하며 값을 할당하였다. 값을 할당할 때 {} 안의 값 개수는 배열의 크기보다 작아도 되만 크면 안된다. 또한 {}를 사용하여 값을 할당하는 것은 배열을 선언할 때만 사용할 수 있으며 이미 선언된 배열에서는 사용할 수 없다.

배열 값이 저장된 각각의 공간을 요소라고 하며 요소에 접근할 때는 배열 뒤에 [](대괄호)를 사용하고, 대괄호 안에 각 요소의 인덱스를 지정한다. 배열의 인덱스는 항상 0부터 시작한다.

배열을 선언하며 값을 초기화하면 다음과 같이 배열의 크기를 생략할 수 있다. 초기화를 하지 않고 생략하면 컴파일 에러가 발생한다.

int numArr1[] = { 11, 22, 33, 44, 55, 66, 77, 88, 99, 110 }; 

36.2 배열을 0으로 초기화하기

다음과 같이 한번에 배열의 모든 요소를 0으로 초기화 할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
 
int main()
{
    int numArr[10= {0, };    
 
    printf("%d\n", numArr[0]);    
    printf("%d\n", numArr[5]);    
    printf("%d\n", numArr[9]);    
    return 0;
}
cs

배열의 모든 요소를 한 번에 0으로 초기화하여 0을 일일이 나열하지 않아도 된다.

36.3 배열의 요소에 값 할당하기

배열은 []로 요소에 접근한 뒤 값을 할당할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
 
int main()
{
    int numArr[3];
 
    numArr[0= 1;
    numArr[1= 22;
    numArr[2= 8;
 
    printf("%d\n", numArr[0]); 
    printf("%d\n", numArr[1]); 
    printf("%d\n", numArr[2]);  
    return 0;
}
cs

인덱스는 0부터 시작하며 []에 인덱스를 지정한 뒤 값을 할당하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
 
int main()
{
    int numArr[3];
 
    numArr[0= 1;
    numArr[1= 22;
    numArr[2= 8;
 
    printf("%d\n", numArr[-1]); 
    printf("%d\n", numArr[5]); 
    printf("%d\n", numArr[10]);  
    return 0;
}
cs

위 코드와 같이 배열의 요소에 접근할 때 인덱스로 음수를 지정하거나, 배열의 크기를 벗어난 인덱스를 지정해도 컴파일에러는 발생하지 않지만, 쓰레기값을 출력한다. 배열의 범위를 벗어난 인덱스에 접근하면 배열이 아닌 다른 메모리 공간에 접근하여 엉뚱한 값을 출력하게 된다.

36.4 배열의 크기 구하기

반복문을 이용해서 배열의 요소를 모두 출력할 때 선언된 크기로 사용하면 나중에 배열의 크기를 늘려야 해서 코드를 수정해야 할 경우 반복문의 조건식도 수정해야 모든 배열이 출력될 수 있다. 이러한 불편함을 해결하려면 sizeof연산자를 사용하여 배열의 전체 요소의 개수를 구해 배열의 크기가 수정되면 반복문의 조건식에서도 알아서 계산되도록 할 수 있다.

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
 
int main()
{
    int numArr[10= { 112233445566778899110 };    
 
    printf("%d\n"sizeof(numArr));                  
    printf("%d\n"sizeof(numArr) / sizeof(int));                                                     
    return 0;
}
cs

sizeof연산자로 numArr의 크기를 구하면 4바이트인 int형 요소가 10개 있으므로 40이 나온다. 요소의 개수를 구하려면 배열의 크기에서 요소의 크기(sizeof(int))로 나눠주면 된다.

36.5 반복문으로 배열의 요소를 모두 출력하기

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
 
int main()
{
    int numArr[10= { 112233445566778899110 };    
 
    for (int i = 0; i < sizeof(numArr) / sizeof(int); i++)   
    {
        printf("%d\n", numArr[i]);   
    }
    return 0;
}
cs

반복문의 조건식에서 sizeof 연산자를 사용하여 배열의 요소 개수만큼 반복하였다.

변수 i는 변화식을 통해 1씩 증가하므로 인덱스에 i를 넣어서 모든 배열의 요소를 순서대로 출력할 수 있다.

다음과 같이 배열의 요소를 역순으로 출력할 수 도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
 
int main()
{
    int numArr[10= { 112233445566778899110 };    
 
    for (int i = sizeof(numArr) / sizeof(int- 1; i >= 0; i--)  
    {
        printf("%d\n", numArr[i]);   
    }
    return 0;
}
cs

배열을 역순으로 출력할 때는 요소의 개수를 조건식에 바로 넣으면 인덱스는 0부터 시작하기 때문에 배열의 마지막 인덱스는 배열의 개수보다 1 작아서 반복문의 조건식에서도 요소의 개수에서 1 뺀 값에서 시작해야 한다. 조건식에서 0보다 크거나 같을 때 까지 반복하여 역순으로 출력할 수 있다.

36.6 배열의 요소 합계 구하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
 
int main()
{
    int numArr[10= { 112233445566778899110 };
    int sum = 0;    
 
    for (int i = 0; i >= sizeof(numArr) / sizeof(int); i++)  
    {
        sum += numArr[i];
    }
    printf("%d\n", sum);
    return 0;
}
cs

위 코드와 같이 합을 저장할 변수를 만들고 배열의 요소만큼 반복하면서 배열의 각 요소의 값을 만든 변수에 누적시킨다. 저장할 변수를 만들때는 0으로 초기화 하지 않으면 쓰레기값이 들어간다.

36.7 배열의 요소에 저장된 값을 두 배로 만들기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
 
int main()
{
    int numArr[10= { 112233445566778899110 }; 
 
    for (int i = 0; i < sizeof(numArr) / sizeof(int); i++
    {
        numArr[i] *= 2;
    }
 
    for (int i = 0; i < sizeof(numArr) / sizeof(int); i++
    {
        printf("%d\n", numArr[i]);
    }
    
    return 0;
}
cs

배열의 요소에 저장된 값을 두 배로 만드려면 배열의 요소에 접근하여 2로 곱한 뒤 다시 요소에 저장하면 된다. *= 연산자를 사용하고, 반복문으로 모든 요소에 접근하여 값을 두배로 변경하고 출력했다.

36.8 배열을 초인터에 넣기

배열은 첫 번째 요소의 주솟값만 담고 있다. 배열은 주솟값이기 때문에 포인터에 넣을 수 있다. 다음과 같이 포인터에 배열을 넣고 포인터에서 인덱스로 요소에 접근할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
 
int main()
{
    int numArr[10= { 112233445566778899110 };
    int *numPtr = numArr;
 
    printf("%d\n"*numPtr);
    printf("%d\n"*numArr);
    printf("%d\n", numPtr[5]);
    printf("%d\n"sizeof(numArr));
    printf("%d\n"sizeof(numPtr));
    return 0;
}
cs

위 코드처럼 배열을 포인터에 바로 할당할 수 있다. 자료형이 같아야 하며 1차원 배열이면 *이 한개인 단일 포인터여야 한다. 배열을 포인터에 할당한뒤 포인터를 역참조하면 배열의 첫 번째 요소가 나온다. 배열도 주솟값을 가진 포인터이기 때문에 역참조 할 수 있다.

배열의 주소가 들어있는 포인터는 인덱스를 통해 요소에 접근할 수 있다.

sizeof 연산자를 사용하면 배열은 전체 공간의 크기가 출력되고, 포인터는 배열의 주소가 들어있는 포인터의 크기만 나온다. 운영체제가 32비트면 4, 64비트면 8이 나온다.

36.9 배열을 활용하여 10진수를 2진수로 변환하기

10진수를 2진수로 변환하는 방법은 다음과 같이 10진수를 0이 될 때 까지 2로 계속 나눈 뒤 나머지를 역순으로 읽으면 2진수가 된다.

이 식을 코드로 변환하면 다음과 같다.

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
#include <stdio.h>
 
int main()
{
    int dec = 13;         
    int bin[20= {0, };
    int pos = 0;
 
    while(1)
    {
        bin[pos] = dec % 2;
        dec = dec / 2;
        pos++;
 
        if(dec == 0)
            break;
    }
 
    for(int i = pos-1; i >= 0; i--)
    {
        printf("%d", bin[i]);    
    }
 
    return 0;
}
cs

변환할 10진수 13을 변수 dec에 저장하고, 2진수를 저장할 배열 bin을 선언한다.

배열에 나머지를 구해서 저장하고 10진수를 2로 나눈 것을 dec가 0이 될 때까지 무한 반복한다.

가장 아래의 나머지부터 읽어야 하므로 배열을 출력할 때는 요소를 역순으로 출력해야 한다.

36.10 퀴즈

정답은 c이다.

정답은 d이다.

정답은 e 이다.

정답은 a, e이다. 배열의 범위는 0~99인데 범위를 벗어난 인덱스를 접근했다.

36.11 연습문제 : 점수 평균 구하기

정답은 다음 코드와 같다.

1. sum += scores[i];

2. average = sum / ( sizeof(scores) / sizeof(float) );

점수 합계를 구할 때는 배열의 모든 요소를 더해주면 되고 평균을 구할 때는 합계에서 요소의 개수로 나누면 된다.

36.12 연습문제 : 2진수를 10진수로 변환하기

정답은 다음 코드와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
int pos = 0;
 
for(int i = sizeof(binary) / sizeof(int) - 1; i >= 0; i--)
 
{
 
    if(binary[i] == 1)
 
        decimal += 1 << pos;
 
    pos++;
 
}
cs

2진수를 10진수로 변환하는 방법은 2진수의 각 자리수 1이면 비트 연산자로 해당 자리에 1을 위치시키면 된다.

36.13 심사문제 : 2진수를 10진수로 변환하기

정답은 다음 코드와 같다.

1
2
3
4
5
6
smallestNumber = numArr[0];
for(int i = 0; i <= 4; i++)
{
    if(smallestNumber > numArr[i])
        smallestNumber = numArr[i];
}
cs

+ Recent posts