Embedded

[C 언어] [Embedded] 메모리 동적 할당에 대해 (1)

Teodore 2023. 1. 17. 22:34
728x90

지난번 Embedded환경에 대한 간략한 설명과 함께 Endian mode와 컴파일러 최적화 오류 등에 대해 알아보았다. 

오늘은 Firmware 개발자라면 누구나 쉽게 사용하고 있는 동적할당에 대해 몇가지 적어보려고 한다. 

 

1. 동적할당이란?

먼저 동적할당이라는 개념부터 간단히 설명해보려 한다. 

프로그래밍을 하다보면 데이터를 임시로 저장하거나 다른 이벤트의 인수로 데이터를 넘겨야 하는 경우가 있다. 

데이터의 크기를 미리 알 수 없는 경우가 대부분이고 여러 조건에 따라 데이터의 크기와 유효범위가 달라질 수 있다. 

아래 코드를 보자 (억지로 예제를 만든 것이니 참고만 하길)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 
#define SIZE 10
 
int func1(void){
    int arr[SIZE];
    for(int i = 0; i < SIZE ; i++)
        arr[i] = i*3;
    add_scheduler(100MS, func2, arr);
    return 0;
}
 
int func2(void* ptr){
    if(ptr == NULLreturn;
    int sum = 0;
    for(int i = 0; i < SIZE; i++)
        sum += *(int*)ptr++;
    printf("func2 result : %d\n", sum);
    return 0;
}
cs

이 코드에서 add_scheduler는 특정 시간 뒤에 전달된 함수포인터에 전달된 인수를 넣어 실행하는 것이다. 
자칫 실수하기 쉬운 코드로서 이 코드에는 치명적인 오류가 있다. 바로 func1에서 생성된 arr이라는 변수는 지역변수로서 func1이 종료되는 즉시 값이 보장되지 않는다. 따라서 func2에서 일어나는 일은 개발자가 예측불가능하다. 

이것을 해결하기 위한 코드는 아래와 같다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 
#define SIZE 10
 
int func1(void){
    int* arr = malloc(SIZE * sizeof(int));
    if(arr == NULLreturn;
    for(int i = 0; i < SIZE ; i++)
        arr[i] = i*3;
    add_scheduler(100MS, func2, arr);
    return 0;
}
 
int func2(void* ptr){
    if(ptr == NULLreturn 1;
    int sum = 0;
    for(int i = 0; i < SIZE; i++)
        sum += *(int*)ptr++;
    printf("func2 result : %d\n", sum);
    free(ptr);
    return 0;
}
cs

이것을 해결하기 위한 코드는 아래와 같다. 

func1이 종료되도 변수가 유지될 수 있도록 arr을 malloc를 통해 동적할당 받아 전달하였다. 

즉 arr은 func1에서 memory의 heap영역에 malloc함수를 통해 공간을 할당받았고, func2에서 free를 통해 메모리 해제되었다. 

 

2. 동적할당 사용시 주의점

이런 파워풀한 기능이라니! 하면서 모든 변수에 대해 malloc으로 대체하면 어떨까 라는 생각을 해볼 수 있다. 

그러나 몇 가지 주의해야 되는 점이 있기 때문에 malloc은 가능한 사용하지 않는 것이 최선이다.

누군가는 이 글에 대해 딴지를 걸수도 있다는 것을 알고 있다. 하지만 내가 경험해보았고 양산품을 많이 만들어 본 결과 malloc은 가급적 지양해야 한다는 것이 나의 결론이었다. 그 이유와 대처법에 대해 알아보자

 

주의점 1. - 메모리 할당 실패 

  - heap 영역은 한정된 공간을 가지고 있다. 개발자가 디바이스가 동작중에 동적할당이 연속적으로 일어나는 모든 케이스를 관리하지 않는다면 heap에 더이상 할당할 메모리가 남지 않아 malloc fail이 발생하게 되고 이는 치명적 오류를 발생시킬 수 있다. 즉 위 코드에서 func1에서 arr이 NULL이 되는 케이스로 NULL이 발생했을 때 대처방법까지 코드로 만들어야 한다. 

 

주의점 2. - 디바이스 속도 저하

  - 동적할당을 사용하면 미리 할당된 메모리를 사용하는 지역변수나 전역변수에 비해 코드의 속도가 떨어지게 된다. 일반적으로는 전혀 문제가 없겠지만, 아주 빠른 통신 분야에서는 누적된 시간오차에 의해 오류가 발생하게 되고, 이는 개발 검증에서 걸러질 수 없기 때문에 시장 출시 이후 제품 품질 오류로 나타날 수 있다. 

 

주의점 3. - 불필요한 메모리 공간 점유

  - 많은 사람들이 간과하고 있는 문제다. heap 공간이 1024byte라고 했을 때 개발자는 1024byte를 할당받을 수 있다고 생각한다. 아래 코드를 보자. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
#define SIZE 16
 
int func1(void){
    int* arr[SIZE];
    int ret = 0;
    for(int i = 0; i < SIZE ; i++){
        arr[i] = malloc(SIZE * sizeof(int));
        if(arr[i] == NULL){
             ret = 1;
            break;
        }
    }
    return ret;
}
cs

func1에서 arr이라는 2차원 배열을 동적 할당을 이용하여 메모리 할당을 받는 예제다. int [16][16] 사이즈로 1024byte의 크기 변수를 만들려는 목적을 가지고 생성한 것인데, 실제 결과는 생성도중 arr[i]가 할당받지 못해 NULL로 표시되고 return 1이 발생하게 된다. 

이 이유는 동적할당 시 관리 테이블(일반적으로 8byte)이 같이 생성되기 때문이다. 

즉 위 코드를 실행시키려면 메모리가 1024 + 8*10 = 1104byte 만큼의 메모리가 필요한 것이다. 

 

이러한 문제점으로 인해 동적할당을 가급적 사용하지 않는 코드 스타일을 적용해야 하는데 이것은 다음 포스팅에 이어 적도록 하겠다.

 

 

728x90

'Embedded' 카테고리의 다른 글

[C 언어] [Embedded] 메모리 동적 할당에 대해 (2)  (0) 2023.01.19
[C 언어] [Embedded] Endian mode  (0) 2023.01.14
[C언어] [컴파일러] [Embedded] 최적화와 오류에 대해  (0) 2023.01.11
방향  (0) 2023.01.07
Embedded  (0) 2023.01.06