Arduino 세계에서 사용 malloc()
하고 free()
꽤 드문 것 같습니다. 순수한 AVR C에서 훨씬 더 자주 사용되지만 여전히주의해야합니다.
Arduino malloc()
와 free()
함께 사용하는 것은 정말 나쁜 생각 입니까?
Arduino 세계에서 사용 malloc()
하고 free()
꽤 드문 것 같습니다. 순수한 AVR C에서 훨씬 더 자주 사용되지만 여전히주의해야합니다.
Arduino malloc()
와 free()
함께 사용하는 것은 정말 나쁜 생각 입니까?
답변:
임베디드 시스템에 대한 나의 일반적인 규칙 malloc()
은 프로그램 시작시 (예 : in) 큰 버퍼에만 한 번만 적용하는 것입니다 setup()
. 메모리를 할당 및 할당 해제 할 때 문제가 발생합니다. 장기 실행 세션에서, 총 여유 메모리가 요청에 충분한 양을 초과하더라도 충분한 여유 공간이 부족하여 메모리가 조각화되고 결국 할당이 실패합니다.
(역사적 관점, 관심이 없다면 건너 뛰십시오) : 로더 구현에 따라 런타임 할당 대 컴파일 타임 할당 (초기화 전역)의 유일한 이점은 16 진수 파일의 크기입니다. 휘발성 메모리가 모두있는 기성품 컴퓨터를 사용하여 내장형 시스템을 구축 할 때 프로그램은 종종 네트워크 나 계측 컴퓨터에서 내장형 시스템으로 업로드되었으며 업로드 시간이 문제가되기도했습니다. 이미지에서 0으로 가득 찬 버퍼를 제거하면 시간이 상당히 단축 될 수 있습니다.)
임베디드 시스템에서 동적 메모리 할당이 필요한 경우 일반적으로 malloc()
또는 큰 풀을 정적으로 할당하여 고정 크기 버퍼 (또는 각각 작은 풀과 큰 버퍼 각각에 하나의 풀)로 나누고 내 자신의 할당 / 해당 풀에서 할당을 해제합니다. 그런 다음 고정 버퍼 크기까지의 메모리 양에 대한 모든 요청에는 해당 버퍼 중 하나가 적용됩니다. 호출 함수는 요청보다 큰지 여부를 알 필요가 없으며 분할 및 재결합 블록을 피함으로써 조각화를 해결합니다. 물론 프로그램에 할당 / 할당 해제 버그가있는 경우 메모리 누수가 여전히 발생할 수 있습니다.
일반적으로 Arduino 스케치를 작성할 때 동적 할당 ( C ++ 인스턴스 와 함께 malloc
또는 new
C ++ 인스턴스) 을 피하고 사람들은 전역 또는 static
변수 또는 로컬 (스택) 변수를 사용합니다.
동적 할당을 사용하면 몇 가지 문제가 발생할 수 있습니다.
malloc
/ free
호출 후 )내가 직면 한 대부분의 상황에서 동적 할당이 필요하지 않았거나 다음 코드 샘플과 같이 매크로를 사용하여 피할 수 있습니다.
MySketch.ino
#define BUFFER_SIZE 32
#include "Dummy.h"
Dummy.h
class Dummy
{
byte buffer[BUFFER_SIZE];
...
};
없이 #define BUFFER_SIZE
우리가 원하는 경우, Dummy
비 고정하도록 클래스를 buffer
크기를 다음과 같이 우리는 동적 할당을 사용하는 것이다 :
class Dummy
{
const byte* buffer;
public:
Dummy(int size):buffer(new byte[size])
{
}
~Dummy()
{
delete [] bufer;
}
};
이 경우 첫 번째 샘플보다 더 많은 옵션이 있지만 (예 : 각각 Dummy
다른 buffer
크기의 다른 객체 사용 ) 힙 조각화 문제가있을 수 있습니다.
인스턴스를 삭제할 buffer
때 동적으로 할당 된 메모리를 확보하기 위해 소멸자를 사용 Dummy
합니다.
malloc()
avr-libc에서 사용하는 알고리즘을 살펴 보았으며 힙 조각화 관점에서 안전한 몇 가지 사용 패턴이있는 것 같습니다.
이것으로 나는 프로그램의 시작 부분에 필요한 모든 것을 할당하고 결코 해제하지 않는다는 것을 의미합니다. 물론이 경우 정적 버퍼를 사용할 수도 있습니다 ...
의미 : 다른 것을 할당하기 전에 버퍼를 비 웁니다. 합리적인 예는 다음과 같습니다.
void foo()
{
size_t size = figure_out_needs();
char * buffer = malloc(size);
if (!buffer) fail();
do_whatever_with(buffer);
free(buffer);
}
내부 do_whatever_with()
에 malloc이 없거나 해당 함수가 할당 된 것을 해제하면 조각화로부터 안전합니다.
이것은 이전 두 경우의 일반화입니다. 스택처럼 힙을 사용하면 (마지막 있음) 스택처럼 작동하며 조각화되지 않습니다. 이 경우 마지막으로 할당 된 버퍼의 크기를로 조정하는 것이 안전합니다 realloc()
.
이렇게하면 조각화가 방지되지 않지만 힙이 사용 된 최대 크기 보다 커지지 않는다는 점에서 안전 합니다. 모든 버퍼의 크기가 같은 경우 버퍼 중 하나를 비울 때마다 이후 할당에 슬롯을 사용할 수 있습니다.
malloc
/ free
또는 new
/ 를 통한 동적 할당 사용 delete
은 본질적으로 나쁘지 않습니다. 사실, 문자열 처리와 같은 것 (예 : String
객체 를 통한 것)의 경우 종종 도움이됩니다. 많은 스케치가 여러 개의 작은 문자열 조각을 사용하기 때문에 결국 더 큰 문자열로 결합되기 때문입니다. 동적 할당을 사용하면 각 메모리에 필요한만큼의 메모리 만 사용할 수 있습니다. 대조적으로, 각각 고정 크기 정적 버퍼를 사용하면 컨텍스트에 전적으로 의존하지만 많은 공간을 낭비하게 될 수 있습니다 (메모리가 훨씬 빨리 소모 됨).
이 모든 것을 말하면서 메모리 사용을 예측할 수있게하는 것이 매우 중요합니다. 스케치가 런타임 환경 (예 : 입력)에 따라 임의의 양의 메모리를 사용하도록 허용하면 조만간 문제가 발생할 수 있습니다. 경우에 따라 사용량이 더 이상 증가하지 않는다는 것을 알고 있는 경우에는 완벽하게 안전 할 수 있습니다 . 프로그래밍 과정에서 스케치가 변경 될 수 있습니다. 나중에 변경 될 때 초기에 만들어진 가정은 잊혀 질 수 있으며, 예기치 않은 문제가 발생합니다.
견고성을 위해 일반적으로 가능한 경우 고정 크기 버퍼로 작업하고 처음부터 이러한 한계에 대해 명시 적으로 작동하도록 스케치를 디자인하는 것이 좋습니다. 이는 향후 스케치 변경이나 예기치 않은 런타임 환경이 메모리 문제를 일으키지 않아야 함을 의미합니다.
나는 당신이 그것을 사용해서는 안되거나 일반적으로 불필요하다고 생각하는 사람들에 동의하지 않습니다. 나는 당신이 그것의 내용을 알지 못하면 위험 할 수 있다고 생각하지만 유용합니다. 구조 또는 버퍼의 크기를 알지 못하고 알지 않아도되는 경우가 있습니다 (컴파일 타임 또는 런타임시). 특히 라이브러리에 관해서는 세계에 보냅니다. 응용 프로그램이 하나의 알려진 구조 만 처리하는 경우 컴파일 타임에 해당 크기로 구워야한다는 데 동의합니다.
예 : 임의의 길이의 데이터 페이로드 (struct, uint16_t 배열 등)를 취할 수있는 직렬 패킷 클래스 (라이브러리)가 있습니다. 해당 클래스의 송신 측에서는 Packet.send () 메소드에 송신하려는 주소와 송신하려는 HardwareSerial 포트를 알려 주기만하면됩니다. 그러나 수신 측에는 수신 페이로드를 유지하기 위해 동적으로 할당 된 수신 버퍼가 필요합니다. 예를 들어 페이로드는 응용 프로그램의 상태에 따라 주어진 순간에 다른 구조가 될 수 있기 때문입니다. 단일 구조를 앞뒤로 보내고 있다면 버퍼를 컴파일 타임에 필요한 크기로 만들면됩니다. 그러나 시간이 지남에 따라 패킷 길이가 다를 수있는 경우 malloc () 및 free ()는 그리 나쁘지 않습니다.
며칠 동안 다음 코드로 테스트를 실행하여 계속 반복되고 메모리 조각화의 증거를 찾지 못했습니다. 동적으로 할당 된 메모리를 해제 한 후 여유 공간은 이전 값으로 돌아갑니다.
// found at learn.adafruit.com/memories-of-an-arduino/measuring-free-memory
int freeRam () {
extern int __heap_start, *__brkval;
int v;
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}
uint8_t *_tester;
while(1) {
uint8_t len = random(1, 1000);
Serial.println("-------------------------------------");
Serial.println("len is " + String(len, DEC));
Serial.println("RAM: " + String(freeRam(), DEC));
Serial.println("_tester = " + String((uint16_t)_tester, DEC));
Serial.println("alloating _tester memory");
_tester = (uint8_t *)malloc(len);
Serial.println("RAM: " + String(freeRam(), DEC));
Serial.println("_tester = " + String((uint16_t)_tester, DEC));
Serial.println("Filling _tester");
for (uint8_t i = 0; i < len; i++) {
_tester[i] = 255;
}
Serial.println("RAM: " + String(freeRam(), DEC));
Serial.println("freeing _tester memory");
free(_tester); _tester = NULL;
Serial.println("RAM: " + String(freeRam(), DEC));
Serial.println("_tester = " + String((uint16_t)_tester, DEC));
delay(1000); // quick look
}
RAM 이나이 방법을 사용하여 동적으로 할당 할 수있는 기능이 저하되지 않았으므로 실행 가능한 도구라고 말하고 싶습니다. FWIW.
Arduino와 malloc () 및 free ()를 사용하는 것은 정말 나쁜 생각입니까?
짧은 대답은 그렇습니다. 이유는 다음과 같습니다.
MPU가 무엇인지 이해하고 사용 가능한 리소스의 제약 내에서 프로그래밍하는 방법에 관한 것입니다. Arduino Uno는 32KB ISP 플래시 메모리, 1024B EEPROM 및 2KB SRAM과 함께 ATmega328p MPU를 사용합니다 . 그것은 많은 메모리 리소스가 아닙니다.
2KB SRAM은 모든 전역 변수, 문자열 리터럴, 스택 및 가능한 힙 사용에 사용됩니다. 스택에는 또한 ISR을위한 헤드 룸이 있어야합니다.
메모리 레이아웃 입니다 :
오늘날 PC / 노트북의 메모리 용량은 1.000.000 배 이상입니다. 스레드 당 1MB의 기본 스택 공간은 일반적이지 않지만 MPU에서는 비현실적입니다.
임베디드 소프트웨어 프로젝트는 자원 예산을 수행해야합니다. 이는 ISR 대기 시간, 필요한 메모리 공간, 컴퓨팅 성능, 명령주기 등을 추정하는 것입니다. 불행히도 프리 런치가 없으며 어려운 실시간 임베디드 프로그래밍은 프로그래밍 기술을 마스터 하기 가장 어렵습니다 .
좋아, 나는 이것이 오래된 질문이라는 것을 알고 있지만, 대답을 더 많이 읽을수록 더 두드러진 관찰로 다시 돌아옵니다.
Turing의 Halting Problem과 관련이있는 것 같습니다. 동적 할당을 허용하면 '정지'가능성이 높아 지므로 문제는 위험 허용 범위 중 하나가됩니다. malloc()
실패 가능성 등을 제거하는 것이 편리하지만 여전히 유효한 결과입니다. OP가 묻는 질문은 단지 기술에 관한 것으로 보이며, 사용 된 라이브러리의 세부 사항이나 특정 MPU가 중요합니다. 대화는 프로그램 정지 또는 다른 비정상적인 종료의 위험을 줄이는쪽으로 바뀝니다. 우리는 위험을 크게 다르게 견딜 수있는 환경의 존재를 인식해야합니다. LED 스트립에 예쁜 색상을 표시하는 취미 프로젝트는 이상한 일이 발생하더라도 누군가를 죽이지 않지만 심장 마비 기계 내부의 MCU는 가능성이 있습니다.
내 LED 스트립의 경우 잠금 상태인지 상관하지 않고 재설정 만하면됩니다. 나는 그것의 결과가 잠금 또는 작동 실패 MCU에 의해 제어 심장 - 폐 컴퓨터에 있다면 말 그대로 삶과 죽음, 그래서 질문입니다 약 malloc()
및 free()
씨 시연의 가능성과 방법을위한 프로그램 거래 사이에 분할한다 튜링의 유명한 문제. 그것이 수학적 증거라는 것을 잊어 버리고 우리가 충분히 영리하다면 계산 한계의 사상자를 피할 수 있다고 스스로를 확신시키는 것은 쉬운 일이다.
이 질문에는 두 가지 답변이 있습니다. 하나는 얼굴에 떠오르는 문제를 응시할 때 강제로 깜박이는 사람들을위한 것이고 다른 하나는 다른 사람들을위한 것입니다. arduino의 대부분의 사용은 미션 크리티컬 또는 치명적 응용 프로그램이 아니지만 코딩하는 MPU에 관계없이 여전히 차이점이 있습니다.
아니요, 그러나 할당 된 메모리를 확보 ()하는 것과 관련하여 매우 신중하게 사용해야합니다. 사람들이 직접 메모리 관리를 피해야한다는 이유를 이해하지 못했습니다. 일반적으로 소프트웨어 개발과 호환되지 않는 수준의 무능함을 의미합니다.
Arduino를 사용하여 드론을 제어한다고 가정 해 봅시다. 코드의 일부에 오류가 있으면 코드가 하늘에서 떨어지거나 누군가 또는 무언가를 다치게 할 수 있습니다. 다시 말해, malloc을 사용할 수있는 역량이 부족한 사람이라면 작은 버그로 인해 심각한 문제가 발생할 수있는 다른 영역이 많기 때문에 전혀 코딩하지 않아야합니다.
malloc으로 인한 버그는 추적하고 수정하기가 더 어렵습니까? 그렇습니다. 그러나 그것은 위험보다는 코더 부분에 대한 좌절의 문제입니다. 위험이 진행되는 한, 코드의 올바른 부분을 수행하기 위해 조치를 취하지 않으면 코드의 일부가 malloc보다 동일하거나 더 위험 할 수 있습니다.