C에서 모듈 식 펌웨어 디자인을위한 메모리 할당 가능성


16

모듈 방식은 일반적으로 매우 휴대하기 편리하므로 (휴대 가능하고 깨끗합니다) 가능한 다른 모듈과 독립적으로 모듈을 프로그래밍하려고합니다. 내 접근 방식의 대부분은 모듈 자체를 설명하는 구조체를 기반으로합니다. 초기화 함수는 기본 매개 변수를 설정 한 후 처리기 (Desriptive 구조체에 대한 포인터)가 모듈 내부의 모든 함수에 전달됩니다.

지금은 모듈을 설명하는 구조체에 대한 할당 메모리의 최선의 접근법이 무엇인지 궁금합니다. 가능한 경우 다음을 원합니다.

  • 불투명 한 구조체이므로 제공된 인터페이스 함수를 사용해야 만 구조체를 변경할 수 있습니다.
  • 여러 인스턴스
  • 링커가 할당 한 메모리

다음과 같은 가능성이 있습니다. 모두 나의 목표 중 하나와 충돌합니다.

글로벌 선언

링커에 의해 모두 할당 된 여러 인스턴스 이지만 구조체는 불투명하지 않습니다.

(#includes)
module_struct module;

void main(){
   module_init(&module);
}

Malloc

불투명 구조체, 다중 인스턴스, 힙에 할당

module.h에서 :

typedef module_struct Module;

module.c init 함수, malloc 및 할당 된 메모리에 대한 포인터 반환

module_mem = malloc(sizeof(module_struct ));
/* initialize values here */
return module_mem;

main.c에서

(#includes)
Module *module;

void main(){
    module = module_init();
}

모듈 선언

링커가 할당 한 불투명 구조체, 사전 정의 된 수의 인스턴스 만

전체 구조체와 메모리를 모듈 내부에 유지하고 핸들러 또는 구조체를 노출시키지 마십시오.

(#includes)

void main(){
    module_init(_no_param_or_index_if_multiple_instances_possible_);
}

힙 할당 대신에 다중 구조체와 여러 인스턴스를 불투명 구조체, 링커에 대해 어떻게 든 결합 할 수 있습니까?

해결책

아래의 답변에서 제안한 것처럼 가장 좋은 방법은 다음과 같습니다.

  1. 모듈 소스 파일에서 MODULE_MAX_INSTANCE_COUNT 모듈의 예약 공간
  2. 모듈 자체에서 MODULE_MAX_INSTANCE_COUNT를 정의하지 마십시오
  3. #ifndef MODULE_MAX_INSTANCE_COUNT # 오류를 모듈 헤더 파일에 추가하여 모듈 사용자가이 제한 사항을 인식하고 애플리케이션에 필요한 최대 인스턴스 수를 정의하도록하십시오.
  4. 인스턴스를 초기화하면 설명 적 구조체의 메모리 주소 (* void) 또는 모듈 인덱스 (더 좋아하는 것)를 반환합니다

12
대부분의 임베디드 FW 설계자들은 메모리 사용을 결정적이고 단순하게 유지하기 위해 동적 할당을 피하고 있습니다. 특히 베어 메탈이고 메모리를 관리 할 기본 OS가없는 경우.
유진 Sh.

정확히, 이것이 링커가 할당을 수행하는 이유입니다.
L. Heinrichs

4
잘 모르겠습니다 ... 동적 인스턴스 수가있는 경우 링커에서 메모리를 어떻게 할당 할 수 있습니까? 그것은 저에게 아주 직각 인 것 같습니다.
jcaron

링커가 하나의 큰 메모리 풀을 할당하고 그로부터 자체 할당을 수행하면 오버 헤드 제로 할당 기의 이점을 얻을 수 있습니다. 할당 기능을 사용하여 풀 객체를 파일에 정적으로 만들 수 있습니다. 내 코드 중 일부에서는 다양한 초기화 루틴에서 모든 할당을 수행 한 다음 할당 된 양을 인쇄하여 최종 프로덕션 컴파일에서 풀을 정확한 크기로 설정했습니다.
이 다니엘 크로커

2
그것이 컴파일 타임 결정이라면, Makefile 또는 그에 상응하는 숫자를 정의하면됩니다. 이 번호는 모듈의 소스에는 없지만 응용 프로그램에 따라 다르며 인스턴스 번호 만 매개 변수로 사용합니다.
jcaron

답변:


4

익명 할당 구조, 힙 할당 대신에 링커 및 여러 인스턴스 수에 대해 이들을 결합하는 옵션이 있습니까?

물론입니다. 그러나 먼저 컴파일시 "모든 수"의 인스턴스를 고정하거나 상한을 설정해야합니다. 이는 인스턴스가 정적으로 할당되기위한 전제 조건입니다 ( "링커 할당"이라고 함). 매크로를 지정하여 소스를 수정하지 않고 숫자를 조정할 수 있습니다.

그런 다음 실제 구조체 선언과 관련된 모든 함수를 포함하는 소스 파일도 내부 연결이있는 인스턴스 배열을 선언합니다. 외부 링크와 함께 인스턴스에 대한 포인터 배열을 제공하거나 색인으로 다양한 포인터에 액세스하는 기능을 제공합니다. 기능 변형은 조금 더 모듈화되어 있습니다.

module.c

#include <module.h>

// 4 instances by default; can be overridden at compile time
#ifndef NUM_MODULE_INSTANCES
#define NUM_MODULE_INSTANCES 4
#endif

struct module {
    int demo;
};

// has internal linkage, so is not directly visible from other files:
static struct module instances[NUM_MODULE_INSTANCES];

// module functions

struct module *module_init(unsigned index) {
    instances[index].demo = 42;
    return &instances[index];
}

헤더가 구조체를 불완전한 유형으로 선언하고 모든 함수를 선언하는 방법에 대해 이미 잘 알고 있다고 생각합니다 ( 해당 유형 에 대한 포인터로 작성 ). 예를 들면 다음과 같습니다.

module.h

#ifndef MODULE_H
#define MODULE_H

struct module;

struct module *module_init(unsigned index);

// other functions ...

#endif

이제 struct module이 아닌 다른 번역 단위 불투명 module.c, * 당신은 액세스 및 동적 할당하지 않고 컴파일시에 정의 된 인스턴스의 수를 사용할 수 있습니다.


* 물론 그 정의를 복사하지 않는 한. 요점은 그렇게 module.h하지 않는 것입니다.


클래스 외부에서 인덱스를 전달하는 것이 이상한 디자인이라고 생각합니다. 이와 같은 메모리 풀을 구현할 때 인덱스를 개인 카운터로 지정하여 할당 된 모든 인스턴스에 대해 1 씩 증가시킵니다. "NUM_MODULE_INSTANCES"에 도달 할 때까지 생성자가 메모리 부족 오류를 반환합니다.
Lundin

@Lundin, 그것은 좋은 지적입니다. 디자인의 이러한 측면은 인덱스가 고유 한 의미를 갖는 것으로 가정하며, 실제로는 그렇지 않을 수도 있습니다. 그것은 이다 영업의 시작 경우에, 사소 그래서이기는하지만, 경우. 그러한 의미가 존재한다면, 초기화 하지 않고 인스턴스 포인터를 얻는 수단을 제공함으로써 추가로지지 될 수있다 .
John Bollinger

따라서 기본적으로 n 개의 모듈에 대해 메모리를 예약합니다. 사용되는 수에 관계없이 응용 프로그램이 초기화하면 사용되지 않은 다음 요소에 대한 포인터를 반환합니다. 나는 그것이 효과가 있다고 생각합니다.
L. Heinrichs

@ L.Heinrichs 예. 임베디드 시스템은 결정 론적 특성을 가지고 있기 때문입니다. "끝없는 양의 객체"나 "알 수없는 양의 객체"와 같은 것은 없습니다. 객체도 종종 싱글 톤 (하드웨어 드라이버)이므로 객체의 단일 인스턴스가 존재하기 때문에 메모리 풀이 필요하지 않은 경우가 많습니다.
Lundin

대부분의 경우에 동의합니다. 이 질문에는 이론적 인 관심이 있었다. 그러나 사용 가능한 IO가 충분한 경우 수백 개의 1- 와이어 온도 센서를 사용할 수 있습니다 (예를 들어 지금 당장 생각해 볼 수있는 예).
L. 하인리히 스

22

C ++에서 작은 마이크로 컨트롤러를 프로그래밍하여 원하는 것을 정확하게 달성합니다.

모듈이라고 부르는 것은 C ++ 클래스이며, 데이터 (외부 액세스 가능 여부)와 함수 (같은)를 포함 할 수 있습니다. 생성자 (전용 함수)가 초기화합니다. 생성자는 런타임 매개 변수 또는 (나의 마음에 드는) 컴파일 타임 (템플릿) 매개 변수를 사용할 수 있습니다. 클래스 내의 함수는 암시 적으로 클래스 변수를 첫 번째 매개 변수로 가져옵니다. (또는 종종 선호하는 클래스는 숨겨진 싱글 톤으로 작동 할 수 있으므로이 오버 헤드없이 모든 데이터에 액세스 할 수 있습니다).

클래스 객체는 전역 적이거나 (링크 타임에 모든 것이 적합 할 것임을 알 수 있음) 아마도 메인에 스택 로컬 일 수 있습니다. (정의되지 않은 전역 초기화 순서로 인해 C ++ 전역을 좋아하지 않으므로 스택 로컬을 선호합니다.)

내가 선호하는 프로그래밍 스타일은 모듈이 정적 클래스이고 (정적) 구성은 템플릿 매개 변수에 의한 것입니다. 이것은 거의 모든 오버 하드를 피하고 최적화를 가능하게합니다. 이것을 스택 크기를 계산하는 도구와 결합하면 걱정없이 잠을 잘 수 있습니다 :)

C ++ 에서이 코딩 방법에 대한 이야기 ​​: Objects? 고맙지 만 사양 할게!

많은 임베디드 / 마이크로 컨트롤러 프로그래머들은 C ++을 모두 사용하도록 강요한다고 생각하기 때문에 C ++을 싫어하는 것 같습니다 . 그것은 반드시 필요한 것은 아니며 매우 나쁜 생각입니다. (아마도 C를 모두 사용하지는 않습니다! 힙, 부동 소수점, setjmp / longjmp, printf 등을 생각하십시오.)


코멘트에서 Adam Haun은 RAII 및 초기화에 대해 언급합니다. IMO RAII는 해체와 더 관련이 있지만 그의 요점은 유효합니다. 전역 객체는 메인 시작 전에 구성되므로 잘못된 가정 (예 : 나중에 변경되는 메인 클럭 속도)에서 작동 할 수 있습니다. 이것이 전역 코드 초기화 객체를 사용하지 않는 또 다른 이유입니다. (글로벌 코드로 초기화 된 객체가있을 때 실패하는 링커 스크립트를 사용합니다.) IMO와 같은 '개체'를 명시 적으로 작성하고 전달해야합니다. 여기에는 wait () 함수를 제공하는 'waiting'기능 'object'가 포함됩니다. 내 설정에서 이것은 칩의 클럭 속도를 설정하는 '객체'입니다.

RAII에 대한 이야기 ​​: 이것은 작은 임베디드 시스템에서 매우 유용한 C ++ 기능 중 하나입니다. 대규모 시스템에서 가장 많이 사용되는 이유 (메모리 할당 해제) (소형 임베디드 시스템은 대부분 동적 메모리 할당 해제를 사용하지 않습니다). 리소스 잠금을 생각하십시오. 잠긴 리소스를 래퍼 객체로 만들고 잠금 래퍼를 통해서만 리소스에 대한 액세스를 제한 할 수 있습니다. 랩퍼가 범위를 벗어나면 자원이 잠금 해제됩니다. 이렇게하면 잠금없이 액세스 할 수 없으며 잠금 해제를 잊을 가능성이 훨씬 줄어 듭니다. 어떤 (템플릿) 마술을 사용하면 오버 헤드가 없을 수 있습니다.


원래 질문은 C를 언급하지 않았으므로 C ++ 중심 답변입니다. 그것이 정말로 C 여야한다면 ...

매크로 속임수를 사용할 수 있습니다. 공개를 공개적으로 선언하면 유형이 있고 전역으로 할당 할 수 있지만 일부 매크로가 다르게 정의되지 않는 한 사용 가능성을 넘어 구성 요소 이름을 엉망으로 만들 수 있습니다 (모듈의 .c 파일의 경우). 추가 보안을 위해 맨 글링에서 컴파일 시간을 사용할 수 있습니다.

또는 유용하지 않은 공개 버전의 구조체를 사용하고 .c 파일에만 개인 버전 (유용한 데이터 포함)을 가지고 동일한 크기를 주장하십시오. 약간의 make-file 속임수가 이것을 자동화 할 수 있습니다.


@ 룬딘 스는 나쁜 (임베디드) 프로그래머에 대해 다음과 같이 언급합니다.

  • 당신이 묘사하는 프로그래머의 타입은 아마도 어떤 언어로 엉망이 될 것입니다. 매크로 (C 및 C ++로 제공)는 확실한 방법 중 하나입니다.

  • 툴링은 어느 정도 도움이 될 수 있습니다. 내 학생들에게는 예외가없고 rtti를 지정하고 힙이 사용되거나 코드가 초기화 된 전역이 존재할 때 링커 오류를 발생시키는 빌드 스크립트가 필요합니다. 그리고 warning = error를 지정하고 거의 모든 경고를 활성화합니다.

  • 템플릿을 사용하는 것이 좋지만 constexpr 및 개념을 사용하면 메타 프로그래밍이 점점 적게 필요합니다.

  • "혼란 된 Arduino 프로그래머"Arduino (라이브러리의 코드 복제, 코드 복제) 프로그래밍 스타일을보다 쉽고 안전하며 빠르고 작은 코드를 생성 할 수있는 최신 C ++ 접근 방식으로 대체하고 싶습니다. 내가 시간과 힘을 가졌다면 ...


이 답변에 감사드립니다! C ++ 사용은 옵션이지만 회사에서 C를 사용하고 있습니다 (명시 적으로 언급하지 않은 것). 나는 사람들에게 C에 대해 이야기하고 있음을 알리기 위해 질문을 업데이트했다.
L. Heinrichs

C 만 사용하는 이유는 무엇입니까? 어쩌면 이것이 C ++을 고려하도록 설득 할 수있는 기회를 제공 할 것입니다. 당신이 원하는 것은 C에서 실현 된 C ++의 본질적인 부분입니다.
Wouter van Ooijen

내 (첫 번째 '실제'임베디드 취미 프로젝트)에서하는 일은 생성자에서 간단한 기본값을 초기화하고 관련 클래스에 대해 별도의 Init 메서드를 사용하는 것입니다. 또 다른 이점은 단위 테스트를 위해 스텁 포인터를 전달할 수 있다는 것입니다.
Michel Keijzers

2
취미 프로젝트 @Michel 당신은 언어를 자유롭게 선택할 수 있습니까? C ++를 가져 가라!
Wouter van Ooijen

4
이 임베디드 좋은 C ++ 프로그램을 작성하는 참으로 완벽하게 가능하다 동안, 문제는 일부가> 모든 임베디드 시스템 프로그래머의 50 % 돌팔이, 혼란 PC 프로그래머, 아두 이노 취미 등 등 사람들의이 종류는 단순히 거기에서 할 수 를 사용 얼굴에 설명하더라도 C ++의 하위 집합을 정리하십시오. 그들에게 C ++를 제공하면 알기 전에 STL, 템플릿 메타 프로그래밍, 예외 처리, 다중 상속 등 전체를 사용합니다. 그리고 결과는 물론 완전한 쓰레기입니다. 슬프게도 10 개의 임베디드 C ++ 프로젝트 중 8 개가 어떻게 끝나는가입니다.
룬딘

7

FreeRTOS (아마 다른 OS입니까?)는 2 가지 버전의 구조체를 정의하여 찾고있는 것과 같은 것을 수행한다고 생각합니다.
OS 함수에 의해 내부적으로 사용되는 '실제', '실제'와 같은 크기이지만 내부에 유용한 멤버가없는 '가짜' int dummy1.
'fake'구조체 만 OS 코드 외부에 노출되며 이는 구조체의 정적 인스턴스에 메모리를 할당하는 데 사용됩니다.
내부적으로 OS의 함수가 호출되면 함수는 외부 '가짜'구조체의 주소를 핸들로 전달한 다음 '실제'구조체에 대한 포인터로 유형 캐스트되어 OS 함수가 필요한 작업을 수행 할 수 있습니다 하다.


좋은 생각, 나는 --- #define BUILD_BUG_ON (condition) ((void) sizeof (char [1-2 * !! (condition)])) --- BUILD_BUG_ON (sizeof (real_struct)! = sizeof ( fake_struct)) ----
L. Heinrichs

2

익명의 구조체이므로 제공된 인터페이스 함수를 사용해야 만 구조체를 변경할 수 있습니다.

제 생각에는 이것은 무의미합니다. 거기에 의견을 달 수는 있지만 더 이상 숨기려고 할 필요는 없습니다.

구조체에 대한 선언이 없더라도 C는 절대로 높은 격리를 제공하지 않습니다. 실수로 memcpy () 또는 버퍼 오버플로와 같이 실수로 쉽게 덮어 쓸 수 있습니다.

대신, 구조체에 이름을 지정하고 다른 사람들도 좋은 코드를 작성하도록 믿습니다. 또한 구조체에 참조하는 데 사용할 수있는 이름이 있으면 디버깅이 쉬워집니다.


2

순수 SW 질문은 https : //.com/stackoverflow.com에서 더 잘 부탁드립니다. .

불완전한 유형 의 구조체를 노출시키는 개념 를 호출자 은 종종 "불투명 한 유형"또는 "불투명 한 포인터"라고 불립니다. 익명의 구조체는 완전히 다른 것을 의미합니다.

이것의 문제점은 호출자가 객체의 인스턴스를 할당 할 수없고 포인터 만 할당 할 수 있다는 것입니다. PC malloc에서는 "constructor"라는 객체 내부를 사용 하지만 malloc은 임베디드 시스템에서 절대로 사용하지 않습니다.

임베디드에서하는 일은 메모리 풀을 제공하는 것입니다. RAM 용량이 제한되어 있으므로 만들 수있는 개체 수를 제한하는 것은 일반적으로 문제가되지 않습니다.

SO에서 불투명 데이터 유형의 정적 할당을 참조하십시오 .


Ou 내 이름 혼란을 분명히 해주셔서 감사합니다. OP를 조정하십시오. 오버플로를 스택하려고 생각했지만 임베디드 프로그래머를 구체적으로 타겟팅하기로 결정했습니다.
L. Heinrichs
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.