tags:

views:

46123

answers:

9

After a few years coding in C++, I was recently offered a job coding in C, in the embedded field.

Putting aside the question of whether it's right or wrong to dismiss C++ in the embedded field, there are some features/idioms in C++ I would miss a lot. Just to name a few:

  • Generic, type-safe data structures (using templates).
  • RAII. Especially in functions with multiple return points, e.g. not having to remember to release the mutex on each return point.
  • Destructors in general. I.e. you write a d'tor once for MyClass, then if a MyClass instance is a member of MyOtherClass, MyOtherClass doesn't have to explicitly deinitialize the MyClass instance - its d'tor is called automatically.
  • Namespaces.

What are your experiences moving from C++ to C?
What C substitutes did you find for your favorite C++ features/idioms? Did you discover any C features you wish C++ had?

+2  A: 

Couple of observations

  • Unless you plan to use your c++ compiler to build your C (which is possible if you stick to a well define subset of C++) you will soon discover things that your compiler allows in C that would be a compile error in C++.
  • No more cryptic template errors (yay!)
  • No (language supported) object oriented programming
hhafez
+49  A: 

Working on an embedded project, I tried working in all C once, and just couldn't stand it. It was just so verbose that it made it hard to read anything. Also, I liked the optimized-for-embedded containers I had written, which had to turn into much less safe and harder to fix #define blocks.

Code that in C++ looked like:

if(uart[0]->Send(pktQueue.Top(), sizeof(Packet)))
    pktQueue.Dequeue(1);

turns into:

if(UART_uchar_SendBlock(uart[0], Queue_Packet_Top(pktQueue), sizeof(Packet)))
    Queue_Packet_Dequeue(pktQueue, 1);

which many people will probably say is fine but gets ridiculous if you have to do more than a couple "method" calls in a line. Two lines of C++ would turn into five of C (due to 80-char line length limits). Both would generate the same code, so it's not like the target processor cared!

One time (back in 1995), I tried writing a lot of C for a multiprocessor data-processing program. The kind where each processor has its own memory and program. The vendor-supplied compiler was a C compiler (some kind of HighC derivative), their libraries were closed source so I couldn't use GCC to build, and their APIs were designed with the mindset that your programs would primarily be the initialize/process/terminate variety, so inter-processor communication was rudimentary at best.

I got about a month in before I gave up, found a copy of cfront, and hacked it into the makefiles so I could use C++. Cfront didn't even support templates, but the C++ code was much, much clearer.

Generic, type-safe data structures (using templates).

The closest thing C has to templates is to declare a header file with a lot of code that looks like:

TYPE * Queue_##TYPE##_Top(Queue_##TYPE##* const this)
{ /* ... */ }

then pull it in with something like:

#define TYPE Packet
#include "Queue.h"
#undef TYPE

Note that this won't work for compound types (e.g. no queues of unsigned char) unless you make a typedef first.

Oh, and remember, if this code isn't actually used anywhere, then you don't even know if it's syntactically correct.

EDIT: One more thing: you'll need to manually manage instantiation of code. If your "template" code isn't all inline functions, then you'll have to put in some control to make sure that things get instantiated only once so your linker doesn't spit out a pile of "multiple instances of Foo" errors.

To do this, you'll have to put the non-inlined stuff in an "implementation" section in your header file:

#ifdef implementation_##TYPE

/* Non-inlines, "static members", global definitions, etc. go here. */

#endif

And then, in one place in all your code per template variant, you have to:

#define TYPE Packet
#define implementation_Packet
#include "Queue.h"
#undef TYPE

Also, this implementation section needs to be outside the standard #ifndef/#define/#endif litany, because you may include the template header file in another header file, but need to instantiate afterward in a .c file.

Yep, it gets ugly fast. Which is why most C programmers don't even try.

RAII.

Especially in functions with multiple return points, e.g. not having to remember to release the mutex on each return point.

Well, forget your pretty code and get used to all your return points (except the end of the function) being gotos:

TYPE * Queue_##TYPE##_Top(Queue_##TYPE##* const this)
{
    TYPE * result;
    Mutex_Lock(this->lock);
    if(this->head == this->tail)
    {
        result = 0;
        goto Queue_##TYPE##_Top_exit:;
    }

    /* Figure out `result` for real, then fall through to... */

Queue_##TYPE##_Top_exit:
    Mutex_Lock(this->lock);
    return result;
}

Destructors in general.

I.e. you write a d'tor once for MyClass, then if a MyClass instance is a member of MyOtherClass, MyOtherClass doesn't have to explicitly deinitialize the MyClass instance - its d'tor is called automatically.

Object construction has to be explicitly handled the same way.

Namespaces.

That's actually a simple one to fix: just tack a prefix onto every symbol. This is the primary cause of the source bloat that I talked about earlier (since classes are implicit namespaces). The C folks have been living this, well, forever, and probably won't see what the big deal is.

YMMV

Mike DeSimone
Of course you hate C if you try to force it to be C++. I doubt C++ would look great if you tried to impose features from $more_expressive_language into it either. Not a critique to your post, just an observation :-)
Mads Elvheim
"Yep, it gets ugly fast. Which is why most C programmers don't even try" - what do they do, then? Rewrite the whole thing for each contained data type?
george
Regarding the goto-instead-of-RAII technique: Isn't it a maintenance nightmare? i.e. whenever you add a code path that needs cleanup, or even just change the order of things inside the function, you have to remember to go to the goto labels at the end and change them as well. I'd like to see a technique that somehow registers the cleanup code right next to what needs to be cleaned up.
george
@george: I hate to say it, but most embedded C code I've seen is pretty bad by C standards. For example, I'm working with Atmel's at91lib right now, and it requires you to write a "board.h" file that the majority of their code pulls in as a dependency. (For their demo board, this header is 792 lines long.) Also, a "LowLevelInit()" function that you have to customize for your board is almost entirely register accesses, with lines like `AT91C_BASE_PMC->PMC_MOR = (0x37 << 16) | BOARD_OSCOUNT | AT91C_CKGR_MOSCRCEN | AT91C_CKGR_MOSCXTEN | AT91C_CKGR_MOSCSEL;`
Mike DeSimone
Oh, and nothing there tells you that `BOARD_OSCOUNT` (which is the value of the timeout for waiting for a clock to switch; clear, huh?) is actually a `#define` in `board.h`. There is also, in that same function, a lot of copy-and-pasted spin loop code, which should have been turned into a two-line `#define` (and, when I did just that, it saved a few bytes of code and made the function more readable by making register sets and spin loops more distinct). One of the big reasons to use C is that it lets you micromanage and optimize everything, but most code I've seen doesn't bother.
Mike DeSimone
@Mads: Please don't get me wrong. I don't hate C. I'm not a huge fan, granted, but I do acknowledge its strengths. Historically, C has been more portable and standardized, and even now its standards have a lot of features I'd love to see in C++, like the ability to generate an array of static, global structs using non-default constructors. Serial ports are a good example; for a serial port class on a chip with 4 ports, I'd love to create an array of `SerialPort` objects, each with a different base address. Then in some places I can loop through them as an array without extra init code.
Mike DeSimone
Agree with @Mads. No reason to go through all of that for features you don't really need. I find I enjoy a style similar to the GTK library. Define your "classes" as structs and then create consistent methods such as my_class_new() and then pass that into the "methods": my_class_do_this(my_class_instance)
Max
I don't really care for the Gtk+ way, and yes, I've written Gtk+ code... and moved to gtkmm. The problems I had with Gtk+ is how you have a macro for everything, which you need to remember, and if you use a normal typecast instead of Gtk+'s (say, to go from an object pointer to a pointer to its superclass form), it isn't caught as an error. Also, whenever I run a Gtk+ program from the command line, there's endless "Gtk+ WARNING" spam, even from included Gnome programs. YMMV.
Mike DeSimone
+8  A: 

Nothing like the STL exists for C.
There are libs available which provide similar functionality, but it isn't builtin anymore.

Think that would be one of my biggest problems... Knowing with which tool I could solve the problem, but not having the tools available in the language I have to use.

MOnsDaR
+13  A: 

I moved from C++ to C for a different reason (some sort of allergic reaction ;) and there are only a few thing that I miss and some things that I gained. If you stick to C99, if you may, there are constructs that let you program quite nicely and safely, in particular

  • designated initializers (eventually combined with macros) make initialization of simple classes as painless as constructors
  • compound literals for temporary variables
  • for-scope variable may help you to do scope bound resource management, in particular to ensure to unlock of mutexes or free of arrays, even under preliminary function returns
  • __VA_ARGS__ macros can be used to have default arguments to functions and to do code unrolling
  • inline functions and macros that combine well to replace (sort of) overloaded functions
Jens Gustedt
Examples, please?
Mike DeSimone
@Mike: for which part in particular? If you follow the link I gave for the `for` scopes, you'll land on P99, where you may look around also for examples and descriptions of the other parts.
Jens Gustedt
@Mike: [There you go.](http://p99.gforge.inria.fr/p99-html/utilities.html#blocks)
george
@george: Thanks! @Jens: Examples of the the other four. I've fallen behind on my C; last I heard they added runtime-sized automatic-allocated (i.e. stack) arrays (e.g. `void DoSomething(unsigned char* buf, size_t bufSize) { unsigned char temp[bufSize]; ... }`) and structure initialization by field names (e.g. `struct Foo bar = { .field1 = 5, .field2 = 10 };`), the latter which I'd love to see in C++, especially with non-POD objects (e.g. `UART uart[2] = { UART(0x378), UART(0x278) };`).
Mike DeSimone
@Mike: yes there are variable length arrays (VLA), but they may be a bit dangerous to use because of potential stackoverflow. The second that you describe are exactly the "designated initializer", so there you go with your own example ;-) For the others you find info on the P99 link above if you click on "related Pages".
Jens Gustedt
A: 

Browse the source of the GNU Scientific Library, and try to adhere to their practices.

http://www.gnu.org/software/gsl/

Chad Brewbaker
Explain how this is relevant.
mikerobi
+6  A: 

In my line of work - which is embedded, by the way - I am constantly switching back & forth between C and C++.

When I'm in C, I miss from C++:

  • templates (including but not limited to STL containers). I use them for things like special counters, buffer pools, etc. (built up my own library of class templates & function templates that I use in different embedded projects)

  • very powerful standard library

  • destructors, which of course make RAII possible (mutexes, interrupt disable, tracing, etc.)

  • access specifiers, to better enforce who can use (not see) what

I use inheritance on larger projects, and C++'s built-in support for it is much cleaner & nicer than the C "hack" of embedding the base class as the first member (not to mention automatic invocation of constructors, init. lists, etc.) but the items listed above are the ones I miss the most.

Also, probably only about a third of the embedded C++ projects I work on use exceptions, so I've become accustomed to living without them, so I don't miss them too much when I move back to C.

On the flip side, when I move back to a C project with a significant number of developers, there are whole classes of C++ problems that I'm used to explaining to people which go away. Mostly problems due to the complexity of C++, and people who think they know what's going on, but they're really at the "C with classes" part of the C++ confidence curve.

Given the choice, I'd prefer using C++ on a project, but only if the team is pretty solid on the language. Also of course assuming it's not an 8K μC project where I'm effectively writing "C" anyway.

Dan
That "C++ Confidence Curve" bothers me a bit. The way it's written, and the comments, implies that C++ is hopeless, a lost cause, or whatever. Am I missing something?
Mike DeSimone
+1  A: 

Pretty much the same reasons I have for using C++ or a mix of C/C++ rather than pure C. I can live without namespaces but I use them all the time if the code standard allows it. The reasons is that you can write much more compact code in C++. This is very usefull for me, I write servers in C++ which tend to crash now and then. At that point it helps a lot if the code you are looking at is short and consist. For example consider the following code:

uint32_t 
ScoreList::FindHighScore(
  uint32_t p_PlayerId)
{
  MutexLock lock(m_Lock); 

  uint32_t highScore = 0; 
  for(int i = 0; i < m_Players.Size(); i++)
  {
    Player& player = m_Players[i]; 
    if(player.m_Score > highScore)
      highScore = player.m_Score; 
  }

  return highScore; 
}

In C that looks like:

uint32_t 
ScoreList_getHighScore(
  ScoreList* p_ScoreList)
{
  uint32_t highScore = 0; 

  Mutex_Lock(p_ScoreList->m_Lock); 

  for(int i = 0; i < Array_GetSize(p_ScoreList->m_Players); i++)
  {
    Player* player = p_ScoreList->m_Players[i]; 
    if(player->m_Score > highScore)
      highScore = player->m_Score; 
  }

  Mutex_UnLock(p_ScoreList->m_Lock);

  return highScore; 
}

Not a world of difference. One more line of code, but that tends to add up. Nomally you try your best to keep it clean and lean but sometimes you have to do something more complex. And in those situations you value your line count. One more line is one more thing to look at when you try to figure out why your broadcast network suddenly stops delivering messages.

Anyway I find that C++ allows me to do more complex things in a safe fashion.

Antihero
In C you cannot do this "for(int i = 0".
Victor
Victor it is valid c99 though(or compiling c with a c++ compiler for some typing issue).
Roman A. Taycher
+7  A: 

The difference between C and C++ is the predictability of the code's behavior.

It is a easier to predict with great accuracy what your code will do in C, in C++ it might become a bit more difficult to come up with an exact prediction.

The predictability in C gives you better control of what your code is doing, but that also means you have to do more stuff.

In C++ you can write less code to get the same thing done, but (at leas for me) I have trouble occasionally knowing how the object code is laid out in memory and it's expected behavior.

d.s.
Whenever I worry about what the code is really doing, I add the `-s` flag to `gcc` to get the assembly dump, search for the function of concern, and start reading. It's a great way to learn any compiled language's quirks.
Mike DeSimone
A: 

I think the main problem why c++ is harder to be accepted in embedded environment is because of the lack of engineers that understand how to use c++ properly.

Yes, the same reasoning can be applied to C as well, but luckily there aren't that many pitfalls in C that can shoot yourself in the foot. C++ on the other hand, you need to know when not to use certain features in c++.

All in all, I like c++. I use that on the O/S services layer, driver, management code, etc. But if your team doesn't have enough experience with it, it's gonna be a tough challenge.

I had experience with both. When the rest of the team wasn't ready for it, it was a total disaster. On the other hand, it was good experience.

KOkon