(Modern) C Programming Tips and Tricks

Spinning off from from Developing Max Externals (Tips and Tricks), and Structured Programming, here’s a (selfishly motivated) thread to collect C programming tips and tricks. I’m puzzling my way through C (coming by way of many years programming with modern conveniences like class inheritance, first class functions, namespaces, etc.) and am only just finding my legs. Maybe some of my bumblings will be useful to others? Better still, maybe some of you with real C chops (err, @tehn, @zebra, @sam) will chime in!

Anyway, I’ll start. Nothing authoritative. Just observations from the road…

Managing Memory.

You need to. Really.

A few pointers (hah):

  • Malloc Scribble FTW. To help keep you honest (and sane), consider using whatever your platform offers in the way of tweaking memory allocation parameters in support of debugging. It’s very easy to convince yourself (especially in unit tests) that you’ve properly initialized your structs when you haven’t. To guard against this, look at options such as M_PERTURB for malloc (in mallopt.h in linux) that cause allocated bytes to be initialized to non-zero values. XCode has memory management diagnostic settings for schemes that are vey handy for this too (notably “Enable Malloc Scribble”: Product>Scheme>Edit Scheme… => Run/Diagnostics).
  • Understand pass by value semantics. Dust off K&R. It’s worth it.
  • Know that there is a stack and a heap. Acceptance is the first step. Lots and lots has been written about the distinction and why you should care. Apparently it’s even a popular interview question!
11 Likes

I would add a bullet-point:

  • get hold of a debugger
  • learn to use it

Even coming from lisp background I somehow missed this crucial point on my aleph hacking, since the aleph doesn’t provide this feature ‘out-of-the-box’. I still only get the luxury of gdb for my prototype code and things that can be simulated off the device. Have seen random unreproducible crashes in my picolisp interpreter on aleph only (same code works fine on linux) - what the heck do you do without a tool that catches that!?

For the embedded stuff like aleph & modular apps I’ve already asked somewhere else about the avr dragon (more readily available and cheaper alternative to the known-working jtagice mk2 avr32 debugger).

However would be good to get hold of a sacrificial avr32 board (NOT my aleph) to test the avrdragon actually works ok to jtag-flash & debug avr32 on my linux laptop. Any suggestions for cheap avr32 dev board stocked in north america from you grizzled embedded-C gurus?

EDIT:
I’d go so far as to gift an avrdragon to anyone with already-working jtag debugger & linux dev environment who could definitely de-brick their aleph if the avrdragon is no good. They’re only 70 bucks canadian it’s chump change compared to an evk1100 avr32 dev board which seems to be the obvious choice…

8 Likes

Absolutely! I’m not a huge fan of XCode, but I was shocked when I set a breakpoint and it actually worked! Especially handy when debugging uninitialized memory.

Seeing something like this is a real tip off (another nod to M_PERTURB and ilk). :thumbsup:

Aside, and regarding the aleph bits, @rick_monster, I’m curious:

  • could they reasonably be factored such that more could be tested off the device? (I’m in the midst of this exercise for teletype, where my goal really is a max external, and it’s going quite well.)
  • could you characterize the crashes in your picolisp interpreter? Have you tried any malloc tricks to rule out memory allocation issues?

Cheers!

3 Likes

A few thoughts of my own, I’m a beginner C programmer, but a very experiences C# one and I’ve dabbled in C++ and Haskell too.

Malloc: watch out with this on embedded platforms, try to use the stack where possible, malloc can lead to heap fragmentation which is a problem when you don’t have much RAM.

By value and by reference: this is a pain in C, I find myself looking towards C++ where you can delete the move and copy constructors (and so disable copying for a struct) if you want. Still you see a lot of memcpy when it isn’t always needed. IMO you should provide a copy function for any struct where there is any ambiguity (i.e. all of them).

K&R: probably not the best book to start with anymore, don’t recommend it to beginners! There are things like assignments in if statements in there…

if ((sign = n) < 0)
    n = -n;

(section 3.6 - 2nd edition)

C99: welcome to the future, it’s only 17 years old now. Use it. GCC 4.4 that we use on the AVR32 supports it (and it’s the default in the Makefiles).

Variable declaration: do it in the least scope possible, and initialise it at the same time. You don’t need to declare them at the top of the function any more.


I flicked through 21st Century C to get myself up to speed, it’s definitely aimed at people that already know a bit of C and how to use the command line. It has chapters on Makefiles and autotools which I appreciated.

4 Likes

second the points that K&R is a bit of a red herring these days, and using c99 syntactic sugar will improve your life. i need to take my own advice on tihs one and start making more use of [designated initializers] (https://gcc.gnu.org/onlinedocs/gcc/Designated-Inits.html) and [so on] (https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html#C-Extensions).

a notable learning experience for me was implementing OOP design patterns in C. will get you comfortable with pointers etc, real quick. here’s a [short article] (http://embeddedgurus.com/state-space/2008/01/object-based-programming-in-c/) on the subject, and a long book.

for an example of some good clean code using said patterns, you could do worse than to take a look at libmonome, close to home.

8 Likes

Nooooooooo! Next thing you’ll tell me that the Dragon Book isn’t a good way to learn about compilers. :wink:

Seriously,

This looks great. Wasn’t aware of it. Thanks!

As a long time Object zealot, this idea fascinates me. Will take a look at libmonome.

Someone pointed me to this recently: Small Memory Software

Good reading for designing software on memory-constrained systems. Some of the techniques are probably overkill for smaller modules but if you have anything that needs to do any sort of dynamic allocation and manage pools of memory between components, it’s a goldmine of information.

5 Likes

Kind of horrified there’s no love for ‘the white bible’ on this thread! What is a good alternative, if 21st century C doesn’t really take you from the ground up like K&R? (not sure I’m really cut out to be a ‘modern’ programmer - besides most of the world is still struggling to wise up to the good ideas in 1984 ANSI common lisp)

I attempted some heavy bees refactoring using beekeep to run the code on linux and catch a lot of segfaults & stuff. Using gdb to bang an instantiated network and do ad-hoc tests.

Seems to be happening very very rarely when I hit buttons on aleph front panel that triggering as-yet-undefined lisp functions. Seen it happen twice in probably an hour or two of interactively coding on the thing. Only guaranteed way to reproduce the crash every time is use the aleph on a gig!

picolisp doesn’t suffer from memory fragmentation because every chunk is the same size. No array data type, just cons-cells, numbers & symbols. Planning to use a similar strategy for fixing bees memory management woes. We will have 2 fixed-size operator memory pools, the rest can be malloc/free-d off the heap. https://github.com/rick-monster/aleph/commit/9f06142902840e045e4557372b1e1a093fe9166a

3 Likes

One question for the more experienced C programmers…

I’m writing some copy functions for the tele_command_t struct, in C it’s always destination as the first argument right?

e.g.

void copy_command(tele_command_t *dst, tele_command_t *src);

If there isn’t a clear convention, I’d vote for source first.

FWIW that’s how the Max C APIs read. For example:

extern void sysmem_copyptr(const void *src, void *dst, long bytes);

t_max_err class_copy(t_symbol *src_name_space, t_symbol *src_classname, t_symbol *dst_name_space, t_symbol *dst_classname);	

Ye elder gods demandeth though typest first thy dest, secondly thy src:

void *memcpy(void *dest, const void *src, size_t n); char *strcpy(char *dest, const char *src);

3 Likes

Yeah, I’d started with src, dst, as that is what I would write in almost any other language, but then got a bit worried after using memcpy… I’m just trying to figure out what is idiomatic.

1 Like

if anybody has any questions about the libmonome source, i’ll answer them.

a few random notes about how i approach modern C development:

i generally follow the linux kernel coding style when i write c. you can take or leave the bits about indentation and brace positioning, but the sections about macros, functions, and typedefs are important. in particular, the bit about not using typedefs for structs is a big deal to me and something i enforce in all of the code i work on (basically, don’t typedef structs).

also from the linux style guide: use gotos for error handling. we don’t have RAII or anything nice like that so gotos are the best option. you can end up with some fairly decent-looking code that cleans up well in the event of errors, and CERT even recommends its use to boot.

lifecycle management is important. no matter what data structure i’m implementing, i’ll at the very least have thing_init(struct thing *) and thing_fini(struct thing *) functions. implementing a struct thing *thing_new(void) can be nice but can also be a pain if client code would rather have a thing embedded inside a structure somewhere. don’t assume that client code wants you to allocate things; it’s easy enough for client code to do itself.

above all, C has a special place in my heart and toolbelt because of how straightforward the code is. yeah, it’s not the most expressive language out there, but it’s no-nonsense and to-the-point. i enjoy the way it forces me to lay my ideas out in fine detail while (and before) implementing in order to get the design right. this isn’t a tip or trick, but it’s just a nice thing to keep in mind, i think.

7 Likes

I always use destination first, mostly because of C conventions. That has also carried over to other languages I use like Go. If copy was an operator then the destination would be on the left hand side.

Library gem: strcspn

[C]omputes the string array index of the first character of s which is not in charset, else the index of the first null character.

One-liner to trim newlines:

#include <string.h>

char buffer[]  = ...;
buffer[strcspn(buffer, "\n")] = 0;

(Bonus points for working even if there is no newline!)

looking for a reference/book on how to manage/structure data for save/recall with flash and memory cards. looking at the BEES code, it covers a lot of ground! but I think I need a general overview to complement with. some things of interest;
syncing data between devices
recall with streaming/small buffers
structuring data/hierarchy (composition vs pattern vs track vs step…)
file format

1 Like

This paper might be helpful:

Algorithms and Data Structures for Flash Memories

also:

Competitive Analysis of Flash-Memory Algorithms

5 Likes

Stanford’s Essential C looks really good too.

1 Like

thanks for the links! “Vitamin C, We’ll need all we can get” :bug::bug::bug:

1 Like

looking for a nice way to store and recall data from arrays of structs (that have several types in them) to a byte buffer and vice versa! hoping to avoid a manual hack, that I will regret as soon as I have to make changes in them …! thanks for any help!