문제

임베디드 시스템에서 C ++의 어떤 기능을 피해야합니까?

다음과 같은 이유로 답을 분류하십시오.

  • 메모리 사용
  • 코드 크기
  • 속도
  • 이식성

편집 : 답변의 범위를 제어하기위한 대상으로 64K RAM이있는 ARM7TDMI를 사용하자.

도움이 되었습니까?

해결책

RTTI 및 예외 처리 :

  • 코드 크기가 증가합니다
  • 성능을 줄입니다
  • 종종 저렴한 메커니즘이나 더 나은 소프트웨어 디자인으로 대체 할 수 있습니다.

템플릿 :

  • 코드 크기가 문제라면 조심하십시오. 대상 CPU에 아주 작은 유사 캐시가 없거나 단지 성능을 줄일 수 있습니다. (템플릿은주의없이 사용하는 경우 코드를 부풀리는 경향이 있습니다). OTOH 영리한 메타 프로그래밍은 코드 크기를 줄일 수 있습니다. 그의 명확한 컷 답변은 없습니다.

가상 기능 및 상속 :

  • 이것들은 나에게 괜찮습니다. 나는 내 임베디드 코드를 C에 씁니다. 그들은 결코 성가신 문제가되지 않았습니다.

다른 팁

특정 기능을 피하기 위해 선택하는 것은 항상 행동에 대한 정량적 분석에 의해 주도되어야합니다. 당신의 소프트웨어, on 당신의 하드웨어 당신의 제약 조건 하에서 선택된 도구 체인 당신의 도메인에는 수반됩니다. C ++ 개발에는 하드 데이터보다는 미신과 고대 역사를 기반으로하는 많은 기존의 지혜 "Do n'ts"가 있습니다. 불행히도, 이것은 종종 어딘가에 누군가가 한 번에 문제가있는 기능을 사용하지 않기 위해 많은 추가 해결 방법 코드가 작성됩니다.

예외는 피해야 할 것에 대한 가장 일반적인 대답 일 것입니다. 대부분의 구현에는 정적 메모리 비용이 상당히 큰 또는 런타임 메모리 비용이 있습니다. 또한 실시간 보장을 더 어렵게 만드는 경향이 있습니다.

바라보다 여기 임베디드 C ++를 위해 작성된 코딩 표준의 꽤 좋은 예.

문서 "정보 기술 - 프로그래밍 언어, 환경 및 시스템 소프트웨어 인터페이스 - C ++ 성능에 대한 기술 보고서"내장 장치의 C ++의 프로그래밍에 대한 좋은 정보도 제공합니다.

그것은 흥미로운 읽기입니다 이론적 해석 초기에 내장 된 C ++ standrard

이것 좀 봐 기사 EC ++에서도.

임베디드 C ++ STD는 C ++의 적절한 서브 세트였으며, 즉, 추가가 없습니다. 다음과 같은 언어 기능이 제거되었습니다.

  • 다중 상속
  • 가상 기본 클래스
  • 런타임 유형 정보 (typeid)
  • 새로운 스타일 캐스트 (static_cast, dynamic_cast, reinterpret_cast 및 const_cast)
  • 돌연변이 유형 예선
  • 네임 스페이스
  • 예외
  • 템플릿

그것은 주목합니다 위키 페이지 Bjarne Stroustrup은 (EC ++ STD의), "내 지식의 최선을 다해 EC ++는 죽었다 (2004). Stroustrup은 계속해서 추천합니다 문서 Prakash의 답변에 의해 언급 된.

ARM7을 사용하고 외부 MMU가 없다고 가정하면 동적 메모리 할당 문제가 디버그하기 어려울 수 있습니다. 가이드 라인 목록에 "New / Delete / Free / Malloc의 신중한 사용"을 추가했습니다.

ARM7TDMI를 사용하는 경우 피하다 정렬되지 않은 메모리 액세스 반드시.

기본 ARM7TDMI 코어에는 정렬 점검이 없으며 정렬되지 않은 읽기를 수행 할 때 회전 데이터를 반환합니다. 일부 구현에는 AN을 높이기위한 추가 회로가 있습니다 ABORT 예외이지만 이러한 구현 중 하나가없는 경우, 정렬되지 않은 액세스로 인해 버그를 찾는 것은 매우 고통 스럽습니다.

예시:

const char x[] = "ARM7TDMI";
unsigned int y = *reinterpret_cast<const unsigned int*>(&x[3]);
printf("%c%c%c%c\n", y, y>>8, y>>16, y>>24);
  • x86/x64 CPU 에서이 이것은 "7TDM"을 인쇄합니다.
  • SPARC CPU에서는 버스 오류로 코어를 덤프합니다.
  • ARM7TDMI CPU에서는 변수 "X"가 32 비트 경계에 정렬되었다고 가정 할 때 "7ARM"또는 "ITDM"과 같은 것을 인쇄 할 수 있습니다 ( "X"가 위치한 위치와 사용중인 컴파일러 옵션에 따라 다릅니다. 등) 그리고 당신은 작은 엔디안 모드를 사용하고 있습니다. 정의되지 않은 행동이지만 원하는 방식으로 작동하지 않도록 보장됩니다.

대부분의 시스템에서 사용하고 싶지 않습니다 새로운 / 삭제 자신의 관리 힙에서 끌어 당기는 자신의 구현으로 우선하지 않는 한. 예, 작동하지만 메모리 제한 시스템을 다루고 있습니다.

나는 이것에 어렵고 빠른 규칙이 있다고 말하지 않았을 것입니다. 응용 프로그램에 크게 의존합니다. 임베디드 시스템은 일반적으로 다음과 같습니다.

  • 그들이 사용할 수있는 메모리의 양이 더 제한적입니다.
  • 종종 느린 하드웨어에서 실행됩니다
  • 하드웨어에 더 가까운 경향이 있습니다. 즉, 레지스터 설정과 같은 방식으로 운전하는 방식으로 운전합니다.

다른 개발과 마찬가지로, 언급 한 모든 요점과 귀하가 제공 / 파생 된 요구 사항과 균형을 맞추어야합니다.

Code Bloat와 관련하여 범인이 훨씬 더 가능성이 높다고 생각합니다. 인라인 템플릿보다.

예를 들어:

// foo.h
template <typename T> void foo () { /* some relatively large definition */ }

// b1.cc
#include "foo.h"
void b1 () { foo<int> (); }

// b2.cc
#include "foo.h"
void b2 () { foo<int> (); }

// b3.cc
#include "foo.h"
void b3 () { foo<int> (); }

링커는 아마도 'foo'의 모든 정의를 단일 번역 단위로 병합 할 것입니다. 따라서 'foo'의 크기는 다른 네임 스페이스 함수의 크기와 다르지 않습니다.

링커 가이 작업을 수행하지 않으면 명시 적 인스턴스화를 사용하여이를 수행 할 수 있습니다.

// foo.h
template <typename T> void foo ();

// foo.cc
#include "foo.h"
template <typename T> void foo () { /* some relatively large definition */ }
template void foo<int> ();        // Definition of 'foo<int>' only in this TU

// b1.cc
#include "foo.h"
void b1 () { foo<int> (); }

// b2.cc
#include "foo.h"
void b2 () { foo<int> (); }

// b3.cc
#include "foo.h"
void b3 () { foo<int> (); }

이제 다음을 고려하십시오.

// foo.h
inline void foo () { /* some relatively large definition */ }

// b1.cc
#include "foo.h"
void b1 () { foo (); }

// b2.cc
#include "foo.h"
void b2 () { foo (); }

// b3.cc
#include "foo.h"
void b3 () { foo (); }

컴파일러가 당신을 위해 'foo'를 인라인으로 결정하면 'foo'의 3 개의 다른 사본이 나타납니다. 템플릿이 보이지 않습니다!

편집하다: Inscitek Jeff의 위의 의견에서

알고있는 함수에 대한 명시 적 인스턴스화를 사용하여 사용되는 기능 만 사용하면 사용하지 않은 기능이 제거 될 수도 있습니다 (실제로 비 템플릿 케이스와 비교하여 코드 크기를 줄일 수 있음).

// a.h
template <typename T>
class A
{
public:
  void f1(); // will be called 
  void f2(); // will be called 
  void f3(); // is never called
}


// a.cc
#include "a.h"

template <typename T>
void A<T>::f1 () { /* ... */ }

template <typename T>
void A<T>::f2 () { /* ... */ }

template <typename T>
void A<T>::f3 () { /* ... */ }

template void A<int>::f1 ();
template void A<int>::f2 ();

공구 체인이 완전히 고장나지 않으면 위는 'f1'및 'f2'에 대한 코드 만 생성됩니다.

시간 함수는 일반적으로 OS 종속입니다 (다시 작성하지 않는 한). 자신의 기능을 사용하십시오 (특히 RTC가있는 경우)

코드를위한 충분한 공간이있는 한 템플릿은 사용해도 괜찮습니다.

예외도 휴대용이 아닙니다

printf 기능 ~하지 않다 버퍼에 쓰기는 휴대용이 아닙니다 (printf를 사용하여 파일*에 쓸 수 있도록 파일 시스템에 연결되어 있어야합니다). sprintf, snprintf 및 str* functions (strcat, strlen) 만 사용하고 물론 넓은 char 응답자 (wcslen ...)를 사용하십시오.

속도가 문제 인 경우 STL 대신 자신의 컨테이너를 사용해야합니다 (예 : STD :: MAP 컨테이너는 키가 동일하게 확인해야합니다. 2 (예 2) 'Less'연산자와 비교합니다 (a [a보다 작음] b == false && b [a == 거짓 평균 a == b). 'Less'는 STD :: MAP 클래스에서받은 유일한 비교 매개 변수입니다 (뿐만 아니라). 이로 인해 중요한 루틴에서 성능 손실이 발생할 수 있습니다.

템플릿, 예외는 코드 크기를 증가시킵니다 (이를 확인할 수 있음). 때로는 더 큰 코드를 가질 때 성능조차도 영향을받습니다.

메모리 할당 함수는 여러 가지면에서 OS 의존적이기 때문에 (특히 스레드 안전 메모리 할당을 처리 할 때) 다시 작성해야 할 것입니다.

Malloc은 _end 변수 (일반적으로 링커 스크립트에서 선언)를 사용하여 메모리를 할당하지만 "알 수없는"환경에서는 스레드 안전하지 않습니다.

때로는 사용해야합니다 무지 팔 모드보다는. 성능을 향상시킬 수 있습니다.

따라서 64K 메모리의 경우 멋진 기능 (STL, 예외 등)이있는 C ++가 과잉 일 수 있다고 말할 수 있습니다. 나는 확실히 C를 선택할 것이다.

GCC ARM 컴파일러와 ARM 자체 SDT를 모두 사용한 후에는 다음과 같은 의견이 있습니다.

  • ARM SDT는 더 단단하고 빠른 코드를 생성하지만 매우 비싸다 (좌석 당 EUR5K!). 이전 작업에서 우리는이 컴파일러를 사용했고 괜찮 았습니다.

  • GCC ARM 도구는 매우 잘 작동하며 제가 자체 프로젝트 (GBA/DS)에서 사용하는 것입니다.

  • 코드 크기가 크게 줄어드하므로 '썸'모드를 사용하십시오. ARM의 16 비트 버스 변형 (예 : GBA)에는 속도 이점도 있습니다.

  • 64K는 C ++ 개발에 대해 심각하게 작습니다. 나는 그 환경에서 C & 어셈블러를 사용합니다.

이러한 작은 플랫폼에서는 스택 사용에주의해야합니다. 재귀, 대규모 자동 (로컬) 데이터 구조 등을 피하십시오 힙 사용도 문제가됩니다 (신규, Malloc 등). C는 이러한 문제를 더 많이 제어 할 수 있습니다.

임베디드 개발 또는 특정 임베디드 시스템을 대상으로 한 개발 환경을 사용하는 경우 이미 일부 옵션이 제한되어 있어야합니다. 대상의 리소스 기능에 따라 앞서 언급 한 항목 (RTTI, 예외 등)을 끕니다. 이것은 크기 나 메모리 요구 사항을 증가시키는 것을 염두에두기보다는 더 쉬운 경로입니다 (어쨌든 정신적으로 알아야합니다).

임베디드 시스템의 경우 주로 비정상적인 런타임 비용이있는 것을 피하고 싶을 것입니다. 몇 가지 예 : 예외 및 RTTI (포함하는 dynamic_cast 그리고 타입).

임베디드 플랫폼의 컴파일러에서 어떤 기능을 지원하는지 확인하고 플랫폼의 특성을 알고 있는지 확인하십시오. 예를 들어 TI의 코드 컴포저 컴파일러는 자동 템플릿 인스턴스화를 수행하지 않습니다. 결과적으로 STL의 정렬을 사용하려면 다섯 가지를 수동으로 인스턴스화해야합니다. 또한 스트림을 지원하지 않습니다.

또 다른 예는 플로팅 포인트 작업에 대한 하드웨어 지원이없는 DSP 칩을 사용하는 것입니다. 즉, 플로트 또는 두 배를 사용할 때마다 함수 호출 비용을 지불합니다.

요약하려면 내장 플랫폼과 컴파일러에 대해 알아야 할 모든 것을 알고 피해야 할 기능을 알 수 있습니다.

Atmega GCC 3.에 놀랐던 한 가지 특별한 문제 : 수업 중 하나에 가상 Ember 기능을 추가했을 때 가상 소멸자를 추가해야했습니다. 이 시점에서 링커는 연산자 삭제 (void *)를 요청했습니다. 왜 그런 일이 발생하는지 전혀 모르고 해당 연산자에 대한 빈 정의를 추가하면 문제가 발생했습니다.

예외 비용은 코드에 따라 다릅니다. 한 응용 분야에서 프로파일 링 (ARM968에서 비교적 작은 것)에서 예외 지지대는 실행 시간에 2 %를 추가했으며 코드 크기는 9.5kb 증가했습니다. 이 응용 프로그램에서 예외는 심각하게 나쁜 일이 발생한 경우에만 발생했습니다. 즉, 실행 시간은 실행 시간을 매우 낮게 유지했습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top