Muitas pessoas ainda acreditam que usar C++ para microcontroladores é perda de espaço, porém aqui vão algumas observações:

  • Código bem feito em C++ é melhor que código mal feito em C;
  • O uso de classes não necessariamente gera código maior, mas pode organizar muito melhor com o paradigma de orientação a objetos;
  • O uso de templates pode sim deixar o código maior, mas qual a alternativa para se usar templates? Se for copiar-colar o código, então dá no mesmo, mas com muito menos trabalho em C++;
  • A maioria dos programas C também é válida em C++, ou seja, pode-se escrever um programa C++ somente com a linguagem C. Isto nos remete ao fato de que só porque uma linguagem tem vários recursos, não é obrigatório utilizar todos estes recursos.

Se você prestar bem atenção, verá que uma das plataformas microcontroladas de maior sucesso é o Arduino, que é baseado na linguagem Wiring. Esta, nada mais é do que C++ com uma biblioteca padronizada para os periféricos do Atmega. Na verdade, o compilador usado no Arduino é o próprio WinAVR, que é uma versão do GCC.

Como também uso o STM32, por que não usar também o C++, já que a plataforma possui até mais recursos? Além disso, também é possível usar o mesmo compilador, o GCC.

Como demonstração, um programa embarcado para o STM32 que eu converti para C++ me deu as seguintes saídas em tamanho (de acordo com a saída do arm-none-eabi-size):

Em C puro:

 text      data     bss     dec     hex filename
64620      1660    3216   69496   10f78 project.elf

Convertendo de forma simples para C++ (mudança de main.c para main.cpp e uso do G++ em vez do GCC):

 text      data     bss     dec     hex filename
69384      1660    3216   74260   12214 project.elf

Usando no linker C++ as opções -no-exceptions -no-rtti:

 text      data     bss     dec     hex filename
64548      1652    3216   69416   10f28 project.elf

Ou seja, o programa ficou, na realidade, menor, sem nenhuma mudança no código!

Agora outro problema é o uso do operador new. Quando este operador é usado, devido à necessidade da linkagem da biblioteca libstdc++, o tamanho aumenta em muito:

Usando uma única vez o operador new, sem nenhum outro recurso de C++:

  text     data     bss     dec     hex filename
120120     1664    5292  127076   1f064 project.elf

O tamanho aumentou em mais de 55KB, simplesmente por se usar o operador new em vez do malloc, mesmo usando a opção (nothrow). Isto acontece devido ao fato da biblioteca libstdc++ puxar as exceções e outros recursos para o código, mesmo com a opção -no-exceptions, pois ela foi pré-compilada. A única alternativa neste caso é modificar o código da biblioteca e recompilar conforme mencionado em http://www.codesourcery.com/archives/arm-gnu/msg03068.html

Uma alternativa para utilizar o operador new sem todo o overhead das chamas a throw, try e catch é ter sua própria implementação deste operador. Eu encontrei uma que funcionou em http://svnmios.midibox.org/filedetails.php?repname=svn.mios32&path=/trunk/programming_models/traditional/mini_cpp.cpp&rev=847, reproduzida abaixo:

mini_cpp.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// $Id: mini_cpp.cpp 847 2010-01-23 00:42:42Z tk $
//////////////////////////////////////////////////////////////////////////////
// Minimal Embedded C++ support, no exception handling, no RTTI
// Date of the Last Update:  Jun 15, 2007
//
//                    Q u a n t u m     L e a P s
//                    ---------------------------
//                    innovating embedded systems
//
// Copyright (C) 2002-2007 Quantum Leaps, LLC. All rights reserved.
//
// Contact information:
// Quantum Leaps Web site:  http://www.quantum-leaps.com
// e-mail:                  [email protected]
//////////////////////////////////////////////////////////////////////////////
// very minor modification to avoid warnings by Martin Thomas 12/2009
// Linking with the object-code from this file saves around 20kB program-memory
// in a demo-application for a Cortex-M3 (thumb2, CS G++ lite Q1/2009).
// Further information can be found in the documents from Quantum Leaps.
//////////////////////////////////////////////////////////////////////////////
// another very minor modification by Thorsten Klose (2010-01)
// just added comments to new() and delete(), that the versions of
// freertos_heap.cpp are used
//////////////////////////////////////////////////////////////////////////////

#include <stdlib.h>                   // for prototypes of malloc() and free()

//............................................................................
void *operator new(size_t size) throw() {
    return malloc(size); // note: will use FreeRTOS based malloc() in freertos_malloc.cpp
}
//............................................................................
void operator delete(void *p) throw() {
    free(p); // note: will use FreeRTOS based free() in freertos_malloc.cpp
}
//............................................................................
extern "C" int __aeabi_atexit(void *object,
                              void (*destructor)(void *),
                              void *dso_handle)
{
    // avoid "unused" warnings (mthomas):
    object = object; destructor=destructor; dso_handle=dso_handle;

    return 0;
}

Ainda, o operador new também faz chamadas a algumas funções que devem ser providas pelo sistema. Como estamos programando para bare-metal, podemos utilizar o arquivo syscalls.c abaixo:

syscalls.c:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/**************************************************************************//*****
 * @file     stdio.c
 * @brief    Implementation of newlib syscall
 ********************************************************************************/

#include <stdio.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>

#undef errno
extern int errno;
extern int  _end;

caddr_t _sbrk ( int incr )
{
  static unsigned char *heap = NULL;
  unsigned char *prev_heap;

  if (heap == NULL) {
    heap = (unsigned char *)&_end;
  }
  prev_heap = heap;

  heap += incr;

  return (caddr_t) prev_heap;
}

int link(char *old, char *new) {
return -1;
}

int _close(int file)
{
  return -1;
}

int _fstat(int file, struct stat *st)
{
  st->st_mode = S_IFCHR;
  return 0;
}

int _isatty(int file)
{
  return 1;
}

int _lseek(int file, int ptr, int dir)
{
  return 0;
}

int _read(int file, char *ptr, int len)
{
  return 0;
}

int _write(int file, char *ptr, int len)
{
  return len;
}

void abort(void)
{
  /* Abort called */
  while(1);
}

/* --------------------------------- End Of File ------------------------------ */

Isso gerou uma saída da forma:

 text      data     bss     dec     hex filename
64572      1652    3216   69440   10f40 project.elf

Considerações sobre tamanho e performance

  • Usar o operador new introduz um grande aumento no uso de ROM, mesmo com o programa acima. Para uma variável estática, o uso aumentou cerca de 4.5KB. Isso ocorre devido ao linker puxar a função malloc().
  • Usar funções virtuais aumenta o uso de ROM devido à criação da tabela de chamada virtual. Se você usar destrutores virtuais para objetos estáticos, isso gastará ROM à toa.
  • Usar métodos inline pode diminuir o uso de ROM e aumentar a performance se o método for muito pequeno.
  • O compilador C++ nem sempre transforma chamada a métodos para inline, mesmo métodos de apenas uma linha.

Template pronto para o Eclipse

Está disponível na seção Projeto C/C++ completo um projeto para rodar um programa em C++ no Eclipse.

Referências