Languages/C/tips
C tips, tricks and... traps
After ten years of C programming, let me gather some piece of code I saw...
Trap : delay loop optimized away
Modern compiler will remove stupid code which seems to do nothing.
/** * This loop will probably take 0 nanosecond, it does really nothing * and the compiler can throw it away. */ void zero_delay(void) { int i; for (i = 0 ; i < 10*1000 ; i++) { } } /** * This loop will take some time, but using the cpu for wasting time is not a * so good idea. */ void dumb_delay(void) { int i; for (i = 0 ; i < 10*1000 ; i++) { asm("nop"); /* replace with the nop instruction from your cpu*/ } } /** * The good way(tm) */ void smart_delay(void) { /* Initialize a timer for the delay you want */ /* put the cpu to sleep until the timer has fired */ /* In any case if your OS already provide this kind of function, use it !*/ #include <unistd.h> usleep(33); }
Trap : compilation and memory mapped registers
Don't forget to use the volatile
keyword when accessing a register
Tip : Use static functions
Trust the compiler, use static
(inline
) function instead of #define
,
The function is far more readable, and a decent compiler will generate exactly the same code.
<stdint.h> /* This function */ static uint8_t increment_func(uint8_t a) { return ++a; } /* Will work as fast as this define */ #define increment(a) ((a)+1)
Trick : Initializing a struct
<stdint.h> <string.h> struct toto_t { uint8_t a; uint8_t b; uint8_t c; } void init_this_struct(struct toto_t *toto) { /* This is the dumb way */ toto->a = 0; toto->b = 0; toto->c = 0; /* This is the smart way */ memset(toto, 0x0, sizeof(*toto)); }
Tip : Write readable code
- First, write and test code that works can be easily understood.
- If it has no efficiency (CPU or memory) problems, let it.
Tip : dummy read of a register
Some register peripherals, especially on embedded systems must be read to be cleared. Here is a nice way of doing it.
/* Suppose this is the address of a memory mapped status register */ volatile uint32_t *some_status_register; /** * Now suppose that we want to clear it, so here is a way */ void read_and_discard_status_reg(void) { uint32_t dummy; dummy = *some_status_register; } /** * And here is another way, cleaner */ void read_and_discard_status_reg(void) { /* the void cast will do the read, sure ;) */ (void)*some_status_register; }
Trap : if (a = b)
Remember, '=' is different from '=='. Any decent compiler used with decent options will warn you.
int a = 12; int b = 13; if (a = b) { printf("12 = 13 ?!?"); }
Tip : if (result = function_call()) warning
Your compiler warn you if you test the return value in the if ? add one stage of braces around and it's gone
/* This should do a warning */ if (a = strlen("toto)") { } /* This will not warn */ if ((a = strlen("toto")) { }
Trap : sizeof('a')
Here is a bad joke, what is sizeof('a') ?
#include <stdio.h> int main(int argc, char *argv[]) { return printf("sizeof('a') = %ld\n", sizeof('a')); }
And the result is... 4, or at least sizeof(int) which depends on your compiler!
Tip : signed
or unsigned
char ?!?
gcc char
type is neither signed
nor unsigned
.
In the following code, gcc will only compile the test_char
function without warning.
Solution : cast your unsigned char*
to a char*
((char*)msg
)
#include <stdint.h> #include <string.h> int test_unsigned_char(const unsigned char *msg) { return strlen(msg); } int test_signed_char(const signed char *msg) { return strlen(msg); } int test_char(const char *msg) { return strlen(msg); }
Trap : C++ : virtual
destructor
Here is why you shouldn't forget to declare your destructors virtual
.
/** * \brief Why use virtual destructors in c++, a definitive answer * * \author Marc Pignat < pim at hevs dot ch > */ #include <iostream> using namespace std; /** * Why not? */ #define LESS_COPY_PASTE {cout << __FUNCTION__ << endl;} /** * Let's define 4 classes * * SuperA SuperB * | | * v v * SubA SubB * * All 4 classes only have a default constructor and a destructor. * * The SuperA class has it's destructor declared *virtual* and the SuperB don't. */ class SuperA { public: SuperA() LESS_COPY_PASTE virtual ~SuperA() LESS_COPY_PASTE }; class SubA : public SuperA { public: SubA() LESS_COPY_PASTE ~SubA() LESS_COPY_PASTE }; class SuperB { public: SuperB() LESS_COPY_PASTE ~SuperB() LESS_COPY_PASTE }; class SubB : public SuperB { public: SubB() LESS_COPY_PASTE ~SubB() LESS_COPY_PASTE }; int main(int argc, char *argv[]) { { /** * output : "SuperA SubA ~SubA ~SuperA" */ SubA a; } cout << endl; { /** * output : "SuperB SubB ~SubB ~SuperB" */ SubB b; } cout << endl; /** * output : "SuperA SubA ~SubA ~SuperA" */ SuperA *pA = new SubA(); delete pA; cout << endl; /** * * ,--.!, * __/ -*- * ,d08b. '|` * 0088MM * `9MMP' * hjm * * output : "SuperB SubB ~SuperB" * */ SuperB *pB = new SubB(); delete pB; } /** * Explaining why this language is so overly complicated is an exercise left * to the reader. */