malloc ()과 free ()를 사용하는 것이 Arduino에서 정말 나쁜 생각입니까?


49

Arduino 세계에서 사용 malloc()하고 free()꽤 드문 것 같습니다. 순수한 AVR C에서 훨씬 더 자주 사용되지만 여전히주의해야합니다.

Arduino malloc()free()함께 사용하는 것은 정말 나쁜 생각 입니까?


2
그렇지 않으면 메모리 속도가 매우 빠릅니다. 메모리 용량을 알고 있다면 어쨌든 정적으로 할당 할 수도 있습니다.
ratchet freak

1
나는 그것이 나쁜지 모르겠지만 , 대부분의 스케치에서 거의 RAM이 부족하지 않기 때문에 사용되지 않으며 플래시와 소중한 클럭 사이클을 낭비하기 때문에 사용되지 않는다고 생각합니다. 또한 범위를 잊지 마십시오 (해당 공간이 여전히 모든 변수에 할당되어 있는지는 알 수 없지만).
익명 펭귄

4
평소와 같이 정답은 "의존"입니다. 동적 할당이 적합한 지 확실하게 알 수있는 정보가 충분하지 않습니다.
WineSoaked

답변:


40

임베디드 시스템에 대한 나의 일반적인 규칙 malloc()은 프로그램 시작시 (예 : in) 큰 버퍼에만 한 번만 적용하는 것입니다 setup(). 메모리를 할당 및 할당 해제 할 때 문제가 발생합니다. 장기 실행 세션에서, 총 여유 메모리가 요청에 충분한 양을 초과하더라도 충분한 여유 공간이 부족하여 메모리가 조각화되고 결국 할당이 실패합니다.

(역사적 관점, 관심이 없다면 건너 뛰십시오) : 로더 구현에 따라 런타임 할당 대 컴파일 타임 할당 (초기화 전역)의 유일한 이점은 16 진수 파일의 크기입니다. 휘발성 메모리가 모두있는 기성품 컴퓨터를 사용하여 내장형 시스템을 구축 할 때 프로그램은 종종 네트워크 나 계측 컴퓨터에서 내장형 시스템으로 업로드되었으며 업로드 시간이 문제가되기도했습니다. 이미지에서 0으로 가득 찬 버퍼를 제거하면 시간이 상당히 단축 될 수 있습니다.)

임베디드 시스템에서 동적 메모리 할당이 필요한 경우 일반적으로 malloc()또는 큰 풀을 정적으로 할당하여 고정 크기 버퍼 (또는 각각 작은 풀과 큰 버퍼 각각에 하나의 풀)로 나누고 내 자신의 할당 / 해당 풀에서 할당을 해제합니다. 그런 다음 고정 버퍼 크기까지의 메모리 양에 대한 모든 요청에는 해당 버퍼 중 하나가 적용됩니다. 호출 함수는 요청보다 큰지 여부를 알 필요가 없으며 분할 및 재결합 블록을 피함으로써 조각화를 해결합니다. 물론 프로그램에 할당 / 할당 해제 버그가있는 경우 메모리 누수가 여전히 발생할 수 있습니다.


또 다른 역사적 메모로 인해 BSS 세그먼트로 빠르게 이동하여 프로그램로드 중 0을 천천히 복사하지 않고 프로그램이 초기화를 위해 자체 메모리를 0으로 만들 수있었습니다.
rsaxvc

16

일반적으로 Arduino 스케치를 작성할 때 동적 할당 ( C ++ 인스턴스 와 함께 malloc또는 newC ++ 인스턴스) 을 피하고 사람들은 전역 또는 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합니다.


14

malloc()avr-libc에서 사용하는 알고리즘을 살펴 보았으며 힙 조각화 관점에서 안전한 몇 가지 사용 패턴이있는 것 같습니다.

1. 오래 지속되는 버퍼 만 할당

이것으로 나는 프로그램의 시작 부분에 필요한 모든 것을 할당하고 결코 해제하지 않는다는 것을 의미합니다. 물론이 경우 정적 버퍼를 사용할 수도 있습니다 ...

2. 수명이 짧은 버퍼 만 할당

의미 : 다른 것을 할당하기 전에 버퍼를 비 웁니다. 합리적인 예는 다음과 같습니다.

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이 없거나 해당 함수가 할당 된 것을 해제하면 조각화로부터 안전합니다.

3. 항상 마지막에 할당 된 버퍼를 비 웁니다

이것은 이전 두 경우의 일반화입니다. 스택처럼 힙을 사용하면 (마지막 있음) 스택처럼 작동하며 조각화되지 않습니다. 이 경우 마지막으로 할당 된 버퍼의 크기를로 조정하는 것이 안전합니다 realloc().

4. 항상 같은 크기를 할당

이렇게하면 조각화가 방지되지 않지만 힙이 사용 된 최대 크기 보다 커지지 않는다는 점에서 안전 합니다. 모든 버퍼의 크기가 같은 경우 버퍼 중 하나를 비울 때마다 이후 할당에 슬롯을 사용할 수 있습니다.


1
"char buffer [size];"를 사용하여 malloc () 및 free ()에 대한주기를 추가하므로 패턴 2는 피해야합니다. (C ++로). 또한 안티 패턴 "Never from ISR"을 추가하고 싶습니다.
Mikael Patel

9

malloc/ free또는 new/ 를 통한 동적 할당 사용 delete은 본질적으로 나쁘지 않습니다. 사실, 문자열 처리와 같은 것 (예 : String객체 를 통한 것)의 경우 종종 도움이됩니다. 많은 스케치가 여러 개의 작은 문자열 조각을 사용하기 때문에 결국 더 큰 문자열로 결합되기 때문입니다. 동적 할당을 사용하면 각 메모리에 필요한만큼의 메모리 만 사용할 수 있습니다. 대조적으로, 각각 고정 크기 정적 버퍼를 사용하면 컨텍스트에 전적으로 의존하지만 많은 공간을 낭비하게 될 수 있습니다 (메모리가 훨씬 빨리 소모 됨).

이 모든 것을 말하면서 메모리 사용을 예측할 수있게하는 것이 매우 중요합니다. 스케치가 런타임 환경 (예 : 입력)에 따라 임의의 양의 메모리를 사용하도록 허용하면 조만간 문제가 발생할 수 있습니다. 경우에 따라 사용량이 더 이상 증가하지 않는다는 것을 알고 있는 경우에는 완벽하게 안전 할 수 있습니다 . 프로그래밍 과정에서 스케치가 변경 될 수 있습니다. 나중에 변경 될 때 초기에 만들어진 가정은 잊혀 질 수 있으며, 예기치 않은 문제가 발생합니다.

견고성을 위해 일반적으로 가능한 경우 고정 크기 버퍼로 작업하고 처음부터 이러한 한계에 대해 명시 적으로 작동하도록 스케치를 디자인하는 것이 좋습니다. 이는 향후 스케치 변경이나 예기치 않은 런타임 환경이 메모리 문제를 일으키지 않아야 함을 의미합니다.


6

나는 당신이 그것을 사용해서는 안되거나 일반적으로 불필요하다고 생각하는 사람들에 동의하지 않습니다. 나는 당신이 그것의 내용을 알지 못하면 위험 할 수 있다고 생각하지만 유용합니다. 구조 또는 버퍼의 크기를 알지 못하고 알지 않아도되는 경우가 있습니다 (컴파일 타임 또는 런타임시). 특히 라이브러리에 관해서는 세계에 보냅니다. 응용 프로그램이 하나의 알려진 구조 만 처리하는 경우 컴파일 타임에 해당 크기로 구워야한다는 데 동의합니다.

예 : 임의의 길이의 데이터 페이로드 (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.


2
테스트 코드는 사용 패턴 2를 준수합니다 . 이전 답변에서 설명한 단기 버퍼 만 할당하십시오 . 이것은 안전한 것으로 알려진 몇 가지 사용 패턴 중 하나입니다.
Edgar Bonet

다시 말해, 프로세서를 알 수없는 다른 코드 와 공유하기 시작할 때 문제가 발생합니다. 이는 사용자가 피하고 있다고 생각하는 문제입니다. 일반적으로 연결하는 동안 항상 작동하거나 실패하는 것이 필요한 경우 최대 크기의 고정 할당을 수행하고 예를 들어 사용자가 초기화 할 때 사용자에게 전달하여 반복해서 다시 사용하십시오. 일반적으로 모든 보드가 2048 바이트에 맞는 칩에서 실행 중임을 기억하십시오. 어쩌면 일부 보드에서는 더 많을 수도 있고 다른 보드에서는 훨씬 적을 수도 있습니다.
Chris Stratton

@EdgarBonet 네, 맞습니다. 공유하고 싶었습니다.
StuffAndyMakes 17:29에

1
필요한 크기의 버퍼 만 동적으로 할당하는 것은 사용하기 전에 다른 것이 할당되는 것처럼 재사용 할 수없는 메모리 인 조각화를 남길 수 있으므로 위험합니다. 또한 동적 할당에는 오버 헤드가 추적됩니다. 고정 할당은 메모리를 여러 번 사용할 수 없다는 것을 의미하지 않으며 프로그램 디자인에 대한 공유를 수행해야 함을 의미합니다. 순전히 로컬 범위를 가진 버퍼의 경우 스택 사용의 무게를 측정 할 수도 있습니다. malloc ()도 실패 할 가능성이 있는지 확인하지 않았습니다.
Chris Stratton

1
"당신이 그것의 내용과 내용을 모르면 위험 할 수 있지만 유용하다." C / C ++의 모든 개발을 요약합니다. :-)
ThatAintWorking

4

Arduino와 malloc () 및 free ()를 사용하는 것은 정말 나쁜 생각입니까?

짧은 대답은 그렇습니다. 이유는 다음과 같습니다.

MPU가 무엇인지 이해하고 사용 가능한 리소스의 제약 내에서 프로그래밍하는 방법에 관한 것입니다. Arduino Uno는 32KB ISP 플래시 메모리, 1024B EEPROM 및 2KB SRAM과 함께 ATmega328p MPU를 사용합니다 . 그것은 많은 메모리 리소스가 아닙니다.

2KB SRAM은 모든 전역 변수, 문자열 리터럴, 스택 및 가능한 힙 사용에 사용됩니다. 스택에는 또한 ISR을위한 헤드 룸이 있어야합니다.

메모리 레이아웃 입니다 :

스램 맵

오늘날 PC / 노트북의 메모리 용량은 1.000.000 배 이상입니다. 스레드 당 1MB의 기본 스택 공간은 일반적이지 않지만 MPU에서는 비현실적입니다.

임베디드 소프트웨어 프로젝트는 자원 예산을 수행해야합니다. 이는 ISR 대기 시간, 필요한 메모리 공간, 컴퓨팅 성능, 명령주기 등을 추정하는 것입니다. 불행히도 프리 런치가 없으며 어려운 실시간 임베디드 프로그래밍은 프로그래밍 기술을 마스터 하기 가장 어렵습니다 .


"[H] ard 실시간 임베디드 프로그래밍은 마스터하기가 가장 어려운 프로그래밍 기술입니다."
StuffAndyMakes

malloc의 실행 시간은 항상 동일합니까? malloc이 사용 가능한 램에서 더 적합한 슬롯을 검색 할 때 더 많은 시간이 걸리는 것을 상상할 수 있습니까? 이것은 이동 중에 메모리를 할당하지 않는 또 다른 논쟁 (램이 부족한 것을 제외하고)입니까?
바울

@Paul 힙 알고리즘 (malloc 및 free)은 일반적으로 일정한 실행 시간이 아니며 재진입이 아닙니다. 알고리즘에는 스레드 (동시성)를 사용할 때 잠금이 필요한 검색 및 데이터 구조가 포함됩니다.
미카엘 파텔

0

좋아, 나는 이것이 오래된 질문이라는 것을 알고 있지만, 대답을 더 많이 읽을수록 더 두드러진 관찰로 다시 돌아옵니다.

중단 문제는 실제

Turing의 Halting Problem과 관련이있는 것 같습니다. 동적 할당을 허용하면 '정지'가능성이 높아 지므로 문제는 위험 허용 범위 중 하나가됩니다. malloc()실패 가능성 등을 제거하는 것이 편리하지만 여전히 유효한 결과입니다. OP가 묻는 질문은 단지 기술에 관한 것으로 보이며, 사용 된 라이브러리의 세부 사항이나 특정 MPU가 중요합니다. 대화는 프로그램 정지 또는 다른 비정상적인 종료의 위험을 줄이는쪽으로 바뀝니다. 우리는 위험을 크게 다르게 견딜 수있는 환경의 존재를 인식해야합니다. LED 스트립에 예쁜 색상을 표시하는 취미 프로젝트는 이상한 일이 발생하더라도 누군가를 죽이지 않지만 심장 마비 기계 내부의 MCU는 가능성이 있습니다.

안녕하세요 튜링 내 이름은 Hubris입니다

내 LED 스트립의 경우 잠금 상태인지 상관하지 않고 재설정 만하면됩니다. 나는 그것의 결과가 잠금 또는 작동 실패 MCU에 의해 제어 심장 - 폐 컴퓨터에 있다면 말 그대로 삶과 죽음, 그래서 질문입니다 약 malloc()free()씨 시연의 가능성과 방법을위한 프로그램 거래 사이에 분할한다 튜링의 유명한 문제. 그것이 수학적 증거라는 것을 잊어 버리고 우리가 충분히 영리하다면 계산 한계의 사상자를 피할 수 있다고 스스로를 확신시키는 것은 쉬운 일이다.

이 질문에는 두 가지 답변이 있습니다. 하나는 얼굴에 떠오르는 문제를 응시할 때 강제로 깜박이는 사람들을위한 것이고 다른 하나는 다른 사람들을위한 것입니다. arduino의 대부분의 사용은 미션 크리티컬 또는 치명적 응용 프로그램이 아니지만 코딩하는 MPU에 관계없이 여전히 차이점이 있습니다.


힙 사용이 반드시 임의적이지 않다는 사실을 고려할 때 Halting 문제 가이 특정 상황에 적용되는 것으로 생각하지 않습니다. 잘 정의 된 방식으로 사용되면 힙 사용은 "안전"하게됩니다. Halting 문제의 요점은 반드시 임의적이고 잘 정의되지 않은 알고리즘에 어떤 일이 발생하는지 확인할 수 있는지 알아 냈습니다. 그것은 더 넓은 의미에서 프로그래밍에 훨씬 더 많이 적용되며, 따라서 여기서는 특별히 관련이없는 것으로 나타났습니다. 나는 그것이 전적으로 정직하다는 것이 전혀 관련이 없다고 생각합니다.
조나단 그레이

나는 수사 학적 과장을 인정할 것이지만 요점은 실제로 행동을 보장하고 싶다면 힙을 사용하면 스택을 사용하는 것보다 훨씬 높은 위험 수준을 암시합니다.
Kelly S. French

-3

아니요, 그러나 할당 된 메모리를 확보 ()하는 것과 관련하여 매우 신중하게 사용해야합니다. 사람들이 직접 메모리 관리를 피해야한다는 이유를 이해하지 못했습니다. 일반적으로 소프트웨어 개발과 호환되지 않는 수준의 무능함을 의미합니다.

Arduino를 사용하여 드론을 제어한다고 가정 해 봅시다. 코드의 일부에 오류가 있으면 코드가 하늘에서 떨어지거나 누군가 또는 무언가를 다치게 할 수 있습니다. 다시 말해, malloc을 사용할 수있는 역량이 부족한 사람이라면 작은 버그로 인해 심각한 문제가 발생할 수있는 다른 영역이 많기 때문에 전혀 코딩하지 않아야합니다.

malloc으로 인한 버그는 추적하고 수정하기가 더 어렵습니까? 그렇습니다. 그러나 그것은 위험보다는 코더 부분에 대한 좌절의 문제입니다. 위험이 진행되는 한, 코드의 올바른 부분을 수행하기 위해 조치를 취하지 않으면 코드의 일부가 malloc보다 동일하거나 더 위험 할 수 있습니다.


4
드론을 예로 들어서 흥미 롭습니다. 이 기사 ( mil-embedded.com/articles/… ) 에 따르면 , "위험 때문에 DO-178B 표준에 따라 안전에 중요한 내장 항공 전자 장치 코드에서 동적 메모리 할당이 금지됩니다."
Gabriel Staples

DARPA는 계약 업체가 자신의 플랫폼에 맞는 사양을 개발할 수있는 오랜 역사를 가지고 있습니다. 그렇기 때문에 다른 사람들이 10,000 달러로 할 수있는 일을 개발하는 데 100 억 달러가 소요됩니다. 마치 군용 산업 단지를 정직한 참조로 사용하는 것처럼 들립니다.
JSON

동적 할당은 프로그램이 Halting Problem에 설명 된 계산 한계를 보여주기위한 초대처럼 보입니다. 그러한 정지의 소량의 위험을 처리 할 수있는 일부 환경이 있으며 제어 가능한 위험을 허용하지 않는 환경 (우주, 방어, 의료 등)이 존재하므로 "하지 말아야 할"작업을 허용하지 않습니다. 로켓을 발사하거나 심장 / 폐 기계를 제어 할 때 '작동해야합니다'가 충분하지 않기 때문에 실패합니다.
Kelly S. French
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.