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çãomalloc()
. - 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
- http://unthought.net/c++/c_vs_c++.html
- http://warp.povusers.org/grrr/cplusplus_vs_c.html
- http://www.codesourcery.com/archives/arm-gnu/msg03068.html
- http://pt.scribd.com/doc/44278596/Qdk-Arm-Cortex-Stm32-Gnu
- http://svnmios.midibox.org/filedetails.php?repname=svn.mios32&path=/trunk/programming_models/traditional/mini_cpp.cpp&rev=847