C Coding Style for Real-Time Digital Signal Processing Version 1.1 James Dicken

advertisement
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
Download