C Coding Style for Real-Time Digital Signal Processing Version 1.1 James Dicken Imperial College London Commenting • A comment should be seen as an aid to the understanding of a block of code that is not immediately obvious. • Comments should only be included if they add meaning, or aid in the understanding of the code. • Comments should always be used to justify unusual behaviour or warn of side-effects. • Assume the next person to read your code can see, but do not assume they can read your mind. • You should not attempt to line up your comments or create appealing patterns with indenting. Tabs have differing widths on different systems and this practice usually results in a hideous mess. Bad 1 i ++; // i n c r e m e n t i f l o a t t a b l e [ TABLE SIZE ] ; // d e c l a r e a f l o a t a r r a y o f s i z e TABLE SIZE f l o a t a f u n c t i o n ( f l o a t sample ) { int i ; // d e c l a r e i as an i n t e g e r 3 /∗ l o o p o v e r t h e t a b l e b u f f e r ∗/ f o r ( i =0; i <N; i ++) { t a b l e [ i ] = sample ; } return t a b l e [ 0 ] ; // r e t u r n t h e f i r s t e l e m e n t 8 } None of the comments in this section add any value to the code. It is obvious that the for() loop operates over the table buffer. A more useful comment might have explained what the loop was doing. Good 1 6 /∗ ∗ when b==0 r e t u r n s 0 t o a v o i d t h r o w i n g e x c e p t i o n ∗/ f l o a t s a f e d i v i d e ( int a , int b ) { i f ( a==0 | | b==0) return 0 . 0 f ; return ( f l o a t ) a /b ; } Good double s u r f a c e n o r m a l [ 3 ] ; // i n c a r t e s i a n c o o r d i n a t e system : {x , y , z } James Dicken 1 RTDSP C Coding Style Variable naming • Variable names should be as short as possible without hiding their use. • By convention loop variables should be i, j, k, ... etc. unless there is a good reason to do otherwise. • Variable names should always start with a lower-case letter; #defines should be uppercase with underscores • Variable names requiring multiple words should either use an underscore (i.e table ptr) or ‘camel-case’ (i.e tablePtr) • Avoid variable names with characters that can be misunderstood like float O[10]; or ones containing words with localised spellings like menuColour. Good 4 int i ; float f ; f l o a t sum = s u m v a l u e s ( ) ; int sample ; #define PI 3 . 1 4 1 5 f #define SIX RADIANS IN DEGREES 3 4 3 . 7 7 5 f Bad 4 f l o a t Answer ; // v a r i a b l e names s h o u l d s t a r t w i t h a l o w e r c a s e l e t t e r int l o o p v a r i a b l e ; // use ‘ i ’ int p o s i t i o n i n s i n e t a b l e ; // c o n s i d e r u s i n g ‘ p t r ’ or ‘ t a b l e p t r ’ i n s t e a d double V a l u e o f R e s u l t ; // s t i c k t o one c o n v e n t i o n Indentation Indent your code! Spot the syntax error: Bad 1 6 f o r ( int i =0; i <PIETRO ; i ++) { p i e t r o [ i ]++; i f ( p i e t r o [ i ] > PIETRO MAX) { pietro [ i ] = 0; f o r ( int j =0; j <TABLE SIZE ; j ++) { c o s i n e t a b l e [ j ] = cos ( pietro [ i ] ) ; } } } } James Dicken 2 RTDSP C Coding Style Function Naming • Function names should broadly follow the advice for variable names • They should usually contain a specific adjective and a noun Good void s q u a r e b u f f e r ( f l o a t ∗ b u f f e r ) ; int g e t s a m p l e ( ) ; void d i s p a t c h r e q u e s t ( Request ∗ r e q u e s t ) ; Bad 2 void do work ( ) ; int answer ( ) ; float do processing ( ) ; int m y f r i e n d s b i r t h d a y i s t o m o r r o w ( ) ; Casting Casting is to inform the compiler to interpret the value of one variable as one with a different type. Don’t rely on the compiler to cast variables for you (implicit casting) unless you are sure the types are compatible (such as an implicit cast from int to long or int to double). Good 1 float ratio = 10.0/30.0; int r e s u l t = ( int ) round ( r a t i o ) ; Bad float ratio = 10.0/30.0; int r e s u l t = r a t i o ; // i m p l i c i t c a s t − b e t t e r t o i n c l u d e an e x p l i c i t ( i n t ) Terrible 3 float angle = (100.0/300.0)∗3.0; float table [ 1 0 ] ; return t a b l e [ a n g l e ] ; // no ! The last example is particularly bad because most compilers will presume to insert a cast to an integer. This can cause unexpected results: if the result is almost an integral value due to floating point inaccuracy (i.e 0.9999943) the table will be indexed at 0. James Dicken 3 RTDSP C Coding Style Curly Braces Always open a new scope (use curly braces ({ and })) surrounding blocks of conditional execution or around the body of a loop. It is the only way to ensure that the right block of code is included. Bad 2 while ( a == 7 0 ) background task ( ) ; p r i n t f ( ” a i s no l o n g e r 7 0 ! ” ) ; If the last line were indented by mistake, the printf would appear to be inside the while loop when it is in fact outside. Also another engineer may add a line before background task() intending it to run before it, when in fact it would run instead. The Common Mistake 2 while ( a == 7 0 ) first background task (); b a c k g r o u n d t a s k ( ) ; // d o e s n t run i n s i d e t h e w h i l e l o o p p r i n t f ( ” a i s no l o n g e r 7 0 ! ” ) ; Good 1 i f ( i < 50} { p r i n t f ( ” i i s l e s s than f i f t y ” ) ; } else i f ( i < 100) { p r i n t f ( ” i i s g r e a t e r than 100 but l e s s than f i f t y ” ) ; } Good 5 f o r ( int i =0; table } f o r ( int i =0; table } i <TABLE A LENGTH ; i ++) { a [ i ] = 123; i <TABLE B LENGTH ; i ++) { b [ i ] = 456; Acceptable i f ( p t r >= TABLE SIZE ) p t r = 0 ; i f ( d e g r e e . marks < 1 0 0 . 0 f ) d e g r e e . e f f o r t ++; James Dicken 4 RTDSP C Coding Style Algorithms The priorities for writing your algorithms should be as follows: 1. Correctness & Completeness, then if time permits 2. Speed Premature optimisation is the cause of a great many problems in software engineering. First one should implement the most straight-forward implementation of an algorithm that works. Compilers do a great job of optimising your code, and unless the application is time sensitive or running close to the boundary of available time one should not optimize at the expense of readability. If the application is not performing adequately first profile the code to determine where most time is being wasted. It is often the case that the grandiose, self-indulgent optimisations save a miniscule amount of processor time or memory and usually serve only to make the code impenetrable by the next engineer to read it. The following functions count the number of bits that are set in an unsigned integer: Easy but slow 3 8 int c o u n t b i t s ( unsigned int n ) { int count =0; while ( n ) { count += n & 0 x1u ; n >>= 1 ; } return count ; } Hard but complicated 5 int c o u n t b i t s f a s t ( unsigned int n ) { r e g i s t e r unsigned int tmp ; tmp = n − ( ( n >> 1 ) & 0 3 3 3 3 3 3 3 3 3 3 3 ) − ( ( n >> 2 ) & 0 1 1 1 1 1 1 1 1 1 1 1 ) ; return ( ( tmp + ( tmp >> 3 ) ) & 0 3 0 7 0 7 0 7 0 7 0 7 ) % 6 3 ; } count bits fast() is nearly four times faster than count bits() which seems a valid reason for its inclusion - but it is also cryptic, hard to debug and impossible to reverse-engineer. It is very unlikely that a program spends any significant amount of time in a routine like this so optimising it is probably a waste of time. James Dicken 5 RTDSP C Coding Style Programming Defensively Each module, or function of your code should have a clearly defined purpose. Think about the range of inputs that are required for the function to operate correctly, and what the outputs will be. It is worth commenting these, since even if it seems obvious at the time of writing it is often difficult to work out after the function is written. Good 3 8 13 /∗ ∗ Returns t h e c o r r e c t l y rounded p o s i t i v e s q u a r e r o o t o f a d o u b l e v a l u e . ∗ Special cases : ∗ I f t h e argument i s NaN or l e s s than z e r o , t h e n t h e r e s u l t i s NaN. ∗ I f t h e argument i s p o s i t i v e i n f i n i t y , t h e n t h e r e s u l t i s p o s i t i v e i n f i n i t y . ∗ I f t h e argument i s p o s i t i v e z e r o or n e g a t i v e ze r o , t h e n t h e ∗ r e s u l t i s t h e same as t h e argument . ∗ Otherwise , t h e r e s u l t i s t h e d o u b l e v a l u e c l o s e s t t o ∗ t h e t r u e m a t h e m a t i c a l s q u a r e r o o t o f t h e argument v a l u e . ∗/ double s q r t ( double x ) { // . . . } When there are preconditions for a function’s correctness, it is worth checking these before the function runs. An example is a transform that requires the input size to be a power of two. If the function receives an input array that is not, it might fail elegantly by returning NULL - rather than crashing by de-referencing beyond the end of an array. Bad 2 7 /∗ Returns a new a r r a y o f t h e same l e n g t h as t h e i n p u t a r r a y t h a t ∗ t h a t contains the input array ’ s values squared . ∗ Returns NULL upon e x c e p t i o n . ∗/ double∗ s q u a r e a r r a y ( int l e n g t h , double∗ i n p u t ) { double∗ r e s u l t = ( double ∗ ) m a l l o c ( l e n g t h ∗ s i z e o f ( double ) ) ; f o r ( int i =0; i <l e n g t h ; i ++) { r e s u l t [ i ] = input [ i ] ∗ input [ i ] ; } return r e s u l t ; } This code contains several mistakes. Firstly the length parameter might be zero or negative, in which case it is impossible to do anything useful and the function should simply return NULL. Secondly the call to malloc() trusts implicitly that the length parameter is within a sensible range and will hence dutifully allocate the entire free memory space for the function. This can easily happen if the two parameters length and input were reversed and the compiler warnings ignored. James Dicken 6 RTDSP C Coding Style