펌웨어란 하드웨어를 직접 접근하고 제어하는 밀접한 관계를 가지고 동작하는 소프트웨어의 일종이다.
그렇기 때문에 일반적인 어플리케이션 소프트웨어와는 다르게 조금 더 신경 써줘야한다고 생각한다.
수정: 스택은 컴파일러가 정해주고 힙은 런타임(프로그램 실행 중 동적으로 바뀜)
1 .스택 오버플로우 (Stack Overflow) 예방
펌웨어의 크기가 커질수록 또는 최적화해야 할 필요가 있다면, 자신에 시스템에 맞는 스택과 힙의 크기를 결정해야 한다.
- 스택은 메모리 최상단에 위치하여 데이터가 추가될 수록 아래로 커지는 자료구조
- 힙은 반대로 데이터가 추가될 수록 위로 커지는 자료구조
- 스택 여유 공간 확인 (모니터링) : FreeRTOS 테스크를 사용중이라면 각 태스크의 스택 사용량을 확인 할 수 있다. 또는 런타임 시 스택의 사용량을 확인하고 디버깅할 수 있겠다.
#include <Arduino.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
void setup() {
Serial.begin(115200);
// 현재 태스크의 스택 여유 공간 확인
Serial.print("Stack high water mark: ");
Serial.println(uxTaskGetStackHighWaterMark(NULL));
}
void loop() {
// put your main code here, to run repeatedly:
}
- 재귀 함수 사용의 최소화 : C/C++ 에서 재귀 함수는 호출 깊이에 따라 스택 사용량이 크게 증가 될 수 있다. 재귀함수를 피하거나 반복문으로 대채한다. 재귀함수를 사용해야하는 경우, 재귀의 깊이를 제한하거나 각 호출 시 필요한 스택의 크기를 신중하게 계산한다. 설령 사용하더라도 그럴일이 있었서는 안되겠지만 무한 재귀가 발생하는 일이 있어서는 안된다.
- 인라인 함수 사용 : 인라인 함수를 사용하면 함수 호출에 따른 스택 프레임 생성이 불필요해진다. 그라나 코드영역(텍스트)의 크기가 증가할 수 있다. 코드 영역에는 실행가능한 명령어가 저장되는 메모리 영역이다.
/* 인라인 함수 선언 */
#include <iostream>
inline int add(int a, int b) {
return a + b;
}
int main() {
int x = 10;
int y = 20;
int result = add(x, y); // 인라인 함수 호출
std::cout << "Result: " << result << std::endl;
return 0;
}
- 대형 지역 변수 피하기 : 큰 배열이나 구조체를 지역 변수로 선언하는 대신 동적으로 힙에 할당하는 것이 좋다. C언어의 malloc() 함수나 C++의 new 연산자를 사용하여 동적으로 할당하여 프로그래밍 해보자. (뒤에 서술하겠지만 free(), delete를 사용하여 힙 영역에 할당된 메모리를 해제하는 것은 필수사항이다.)
void someFunction() {
// 큰 배열을 스택에 할당하는 대신
// int largeArray[1000];
// 힙에 동적으로 할당
int* largeArray = (int*)malloc(1000 * sizeof(int));
if (largeArray == NULL) {
// 메모리 할당 실패 처리
}
// 작업 수행
free(largeArray); // 메모리 해제 (*필수)
}
- 스택 오버플로우 감지 핸들러 만들기 : 스택 오버플로우를 감지하는 함수(또는 객체)를 만들어 스택 오버플로우가 발생하였을 때 감지 하고 로그출력, 시스템 재부팅 등의 대처를 마련하는 것도 방법이 될수 있다.
2. 메모리 누수 (Memory Leak) 예방
메모리 누수는 C/C++의 malloc()함수나 new 연산자로 부터 동적으로 할당된 힙 메모리가 더 이상 필요하지 않음에도 불구하고 해제되지 않아 시스템의 사용 가능한 메모리가 줄어드는 상황을 말한다. ( 타 메모리 영역과 충돌하는 것만을 의미하지 않는다.) 특히 장시간 실행되는 임베디드 프로그램에서는 메모리 누수 현상에 대해 엄격하게 관리하여야 한다.
- 힙 (동적 할당 메모리) 할당 후 반드시 해제 : 동적으로 할당된 메모리를 사용한 후에는 반드시 해제한다.
int* ptr = (int*)malloc(sizeof(int) * 10);
if (ptr == NULL) {
// 메모리 할당 실패 처리
}
// 메모리 사용
free(ptr); // 메모리 반드시 해제
ptr = NULL; // 포인터 초기화
- (클래스 사용시) RAII 패턴 사용: RAII(Resoure Acquisition Is Initalization) 패턴은 생성자와 소멸자를 사용하여 자원의 할당과 해제를 자동으로 관리하는 디자인이다. 객체의 생명 주기에 따라 자원을 관리하게 되며 메모리 누수 뿐만 아니라 기타 자원 누수를 방지할 수 있다.
#include <iostream> #include <fstream> class FileRAII { public: FileRAII(const std::string& filename) : file(filename) { if (!file.is_open()) { throw std::runtime_error("Unable to open file"); } std::cout << "File opened.\n"; } ~FileRAII() { if (file.is_open()) { file.close(); std::cout << "File closed.\n"; } } void write(const std::string& data) { if (file.is_open()) { file << data << std::endl; } } private: std::ofstream file; }; int main() { try { FileRAII file("example.txt"); file.write("Hello, RAII!"); // FileRAII 객체가 스코프를 벗어나면 파일이 자동으로 닫힙니다. } catch (const std::exception& e) { std::cerr << e.what() << '\n'; } return 0; } /* FileRAII 클래스가 생성될 때 파일을 열고, 객체가 소멸될 때 파일을 자동으로 닫는다. */
#include <iostream>
class MemoryRAII {
public:
MemoryRAII(size_t size) {
data = new int[size];
std::cout << "Memory allocated.\n";
}
~MemoryRAII() {
delete[] data;
std::cout << "Memory deallocated.\n";
}
int* getData() {
return data;
}
private:
int* data;
};
int main() {
{
MemoryRAII mem(100);
int* data = mem.getData();
// 메모리 사용
} // MemoryRAII 객체가 스코프를 벗어나면 메모리가 자동으로 해제됩니다.
return 0;
}
// MemoryRAII 클래스가 생성될 때 메모리를 할당하고, 객체가 소멸될 때 메모리를 자동으로 해제합니다.
'Embedded System > 소프트웨어 (C,C++)' 카테고리의 다른 글
[C/C++] LVGL GUI 라이브러리 (0) | 2024.07.10 |
---|---|
[C/C++] 고정된 너비 정수 (fixed-width integers) 자료형 (0) | 2024.07.09 |
[C++] 참조자 (Reference)/ 참조에 의한 호출(Call-by-reference) (0) | 2024.06.21 |
[C++] 함수 오버로딩 (Function Overloading) (0) | 2024.06.20 |
[F/W] CPU 구조 및 동작 (1) | 2024.06.18 |