A Quick Introduction To C Programming PDF

Download as pdf or txt
Download as pdf or txt
You are on page 1of 42

1

A Quick Introduction to C Programming


Lewis Girod
CENS Systems Lab
J uly 5, 2005
http://lecs.cs.ucla.edu/~girod/talks/c-tutorial.ppt
2
or,
What I wish I had known about C
during my first summer internship
With extra info in
the NOTES
3
High Level Question: Why is Software Hard?
Answer(s):
Complexity: Every conditional (if) doubles number of paths
through your code, every bit of state doubles possible states
Solution: reuse code with functions, avoid duplicate state variables
Mutability: Software is easy to change.. Great for rapid fixes ..
And rapid breakage .. always one character away from a bug
Solution: tidy, readable code, easy to understand by inspection.
Avoid code duplication; physically the same logically the same
Flexibility: Programming problems can be solved in many
different ways. Few hard constraints plenty of rope.
Solution: discipline and idioms; dont use all the rope
4
Writing and Running Programs
#i ncl ude <st di o. h>
/ * The si mpl est C Program*/
i nt mai n( i nt argc, char **argv)
{
pri ntf ( Hel l o Worl d\ n) ;
ret urn 0;
}
1. Write text of program (source code) using an editor
such as emacs, save as file e.g. my_program.c
2. Run the compiler to convert program from source to
an executable or binary:
$ gcc Wall g my_program.c o my_program
my_program
$ gcc - Wal l g my_program. c o my_program
t t . c: I n f uncti on `mai n' :
t t . c: 6: parse error bef ore `x'
t t . c: 5: parmt ypes gi ven bot h i n parml i st and separat el y
t t . c: 8: `x' undecl ared ( f i rst use i n t hi s f unct i on)
t t . c: 8: ( Each undecl ared i denti f i er i s reported onl y once
t t . c: 8: f or each f uncti on i t appears i n. )
t t . c: 10: warni ng: cont rol reaches end of non- voi d f unct i on
t t . c: At t op l evel :
t t . c: 11: parse error bef ore `ret urn'
3-N. Compiler gives errors and warnings; edit source
file, fix it, and re-compile
N. Run it and see if it works
$ ./my_program
Hello World
$
-Wall g ?
. / ?
What if it doesnt work?
5
C Syntax and Hello World
#i ncl ude <stdi o. h>
/ * The si mpl est C Program*/
i nt mai n( i nt argc, char **argv)
{
pri ntf ( Hel l o Worl d\ n) ;
return 0;
}
The main() function is always
where your program starts
running.
#include inserts another file. .h files are called
header files. They contain stuff needed to interface to
libraries and code in other .c files.
This is a comment. The compiler ignores this.
Blocks of code (lexical
scopes) are marked by { }
Print out a message. \n means new line. Return 0 from this function
What do the <>
mean?
Can your program have
more than one .c file?
6
A Quick Digression About the Compiler
#i ncl ude <st di o. h>
/ * The si mpl est C Program*/
i nt mai n( i nt argc, char **argv)
{
pri ntf ( Hel l o Worl d\ n) ;
ret urn 0;
}
my_program
__ext ensi on__ t ypedef unsi gned l ong l ong i nt __dev_t;
__ext ensi on__ t ypedef unsi gned i nt __ui d_t;
__ext ensi on__ t ypedef unsi gned i nt __gi d_t;
__ext ensi on__ t ypedef unsi gned l ong i nt __i no_t;
__ext ensi on__ t ypedef unsi gned l ong l ong i nt __i no64_t;
__ext ensi on__ t ypedef unsi gned i nt __nl i nk_t;
__ext ensi on__ t ypedef l ong i nt __of f _t;
__ext ensi on__ t ypedef l ong l ong i nt __of f 64_t;
ext ern voi d f l ockf i l e ( FI LE *__stream) ;
ext ern i nt f t ryl ockf i l e ( FI LE *__st ream) ;
ext ern voi d f unl ockf i l e ( FI LE *__st ream) ;
i nt mai n( i nt argc, char **argv)
{
pri ntf ( Hel l o Worl d\ n) ;
ret urn 0;
}
Compilation occurs in two steps:
Preprocessing and Compiling
In Preprocessing, source code is expanded into
a larger form that is simpler for the compiler to
understand. Any line that starts with # is a line
that is interpreted by the Preprocessor.
Include files are pasted in (#include)
Macros are expanded (#define)
Comments are stripped out ( /* */ , // )
Continued lines are joined ( \ )
Preprocess
Compile
The compiler then converts the resulting text into
binary code the CPU can run directly.
\ ?
Why ?
7
OK, Were Back.. What is a Function?
#i ncl ude <stdi o. h>
/ * The si mpl est C Program*/
i nt mai n( i nt argc, char **argv)
{
pri ntf ( Hel l o Worl d\ n) ;
return 0;
}
Function Arguments
Return type, or void
Calling a Function: printf() is just
another function, like main(). Its defined
for you in a library, a collection of
functions you can call from your program.
A Function is a series of instructions to run. You pass
Arguments to a function and it returns a Value.
main() is a Function. Its only special because it
always gets called first when you run your program.
Returning a value
8
What is Memory?
Memory is like a big table of numbered
slots where bytes can be stored.
Addr Value
0
1
2
3
4 H (72)
5 e (101)
6 l (108)
7 l (108)
8 o (111)
9 \n (10)
10 \0 (0)
11
12
The number of a slot is its Address.
One byte Value can be stored in each slot.
Some logical data values span more than
one slot, like the character string Hello\n
72?
A Type names a logical meaning to a span
of memory. Some simple types are:
char
char [ 10]
i nt
f l oat
i nt64_t
a single character (1 slot)
an array of 10 characters
signed 4 byte integer
4 byte floating point
signed 8 byte integer
not always
Signed?
9
What is a Variable?
char x;
char y= e ;
AVariable names a place in memory where
you store a Value of a certain Type.
Symbol Addr Value
0
1
2
3
x 4 ?
y 5 e (101)
6
7
8
9
10
11
12
You first Define a variable by giving it a
name and specifying the type, and
optionally an initial value
declare vs define?
Type is single character (char)
extern? static? const?
Name
What names are legal?
Initial value
Initial value of x is undefined
The compiler puts them
somewhere in memory.
symbol table?
10
Multi-byte Variables
char x;
char y= e ;
i nt z = 0x01020304;
Different types consume different amounts
of memory. Most architectures store data
on word boundaries, or even multiples of
the size of a primitive data type (int, char)
Symbol Addr Value
0
1
2
3
x 4 ?
y 5 e (101)
6
7
z 8 4
9 3
10 2
11 1
12
0x means the constant is
written in hex
An int consumes 4 bytes
padding
11
Lexical Scoping
Every Variable is Defined within some scope. A
Variable cannot be referenced by name (a.k.a.
Symbol) from outside of that scope.
The scope of Function Arguments is the
complete body of the function.
voi d p( char x)
{
/ * p, x */
char y;
/ * p, x, y */
char z;
/ * p, x, y, z */
}
/ * p */
char z;
/ * p, z */
voi d q( char a)
{
char b;
/ * p, z, q, a, b */
{
char c;
/ * p, z, q, a, b, c */
}
char d;
/ * p, z, q, a, b, d ( not c) */
}
/ * p, z, q */
(Returns nothing)
The scope of Variables defined inside a
function starts at the definition and ends at
the closing brace of the containing block
Lexical scopes are defined with curly braces {}.
The scope of Variables defined outside a
function starts at the definition and ends at
the end of the file. Called Global Vars.
legal?
char b?
12
Expressions and Evaluation
Expressions combine Values using Operators, according to precedence.
1 + 2 * 2 1 + 4 5
( 1 + 2) * 2 3 * 2 6
Symbols are evaluated to their Values before being combined.
i nt x=1;
i nt y=2;
x + y * y x + 2 * 2 x + 4 1 + 4 5
Comparison operators are used to compare values.
In C, 0 means false, and any other value means true.
i nt x=4;
( x < 5) ( 4 < 5) <true>
( x < 4) ( 4 < 4) 0
( ( x < 5) | | ( x < 4) ) ( <true> | | ( x < 4) ) <true>
Not evaluated because
first clause was true
13
Comparison and Mathematical Operators
== equal to
< l ess than
<= l ess than or equal
> greater than
>= greater than or equal
! = not equal
&& l ogi cal and
| | l ogi cal or
! l ogi cal not
+ pl us
- mi nus
* mul t
/ di vi de
% modul o
The rules of precedence are clearly
defined but often difficult to remember
or non-intuitive. When in doubt, add
parentheses to make it explicit. For
oft-confused cases, the compiler will
give you a warning Suggest parens
around do it!
Beware division:
If second argument is integer, the
result will be integer (rounded):
5 / 10 0 whereas 5 / 10.0 0.5
Division by 0 will cause a FPE
& bi twi se and
| bi twi se or
^ bi twi se xor
~ bi twi se not
<< shi f t l ef t
>> shi f t ri ght
Dont confuse & and &&..
1 & 2 0 whereas 1 && 2 <true>
14
Assignment Operators
x = y assi gn y to x
x++ post - i ncrement x
++x pre- i ncrement x
x- - post - decrement x
- - x pre- decrement x
Note the difference between ++x and x++:
Dont confuse =and ==! The compiler will warn suggest parens.
i nt x=5;
i nt y;
y = ++x;
/ * x == 6, y == 6 */
i nt x=5;
i nt y;
y = x++;
/ * x == 6, y == 5 */
i nt x=5;
i f ( x=6) / * al ways true */
{
/ * x i s now 6 */
}
/ * . . . */
i nt x=5;
i f ( x==6) / * f al se */
{
/ * . . . */
}
/ * x i s sti l l 5 */
x += y assi gn ( x+y) to x
x - = y assi gn ( x- y) to x
x *= y assi gn ( x*y) to x
x / = y assi gn ( x/ y) to x
x %= y assi gn ( x%y) to x
recommendation
15
A More Complex Program: pow
#i ncl ude <stdi o. h>
#i ncl ude <i nttypes. h>
f l oat pow( f l oat x, ui nt32_t exp)
{
/ * base case */
i f ( exp == 0) {
return 1. 0;
}
/ * recursi ve case */
return x*pow( x, exp 1) ;
}
i nt mai n( i nt argc, char **argv)
{
f l oat p;
p = pow( 10. 0, 5) ;
pri ntf ( p = %f \ n, p) ;
return 0;
}
Challenge: write pow() so it requires log(exp) iterations
Tracing pow():
What does pow(5,0) do?
What about pow(5,1)?
Induction
if statement
/ * i f eval uated expressi on i s not 0 */
i f ( expr essi on) {
/ * then execute thi s bl ock */
}
el se {
/ * otherwi se execute thi s bl ock */
}
Need braces?
detecting brace errors Short-circuit eval?
X ? Y : Z
16
The Stack
Recall lexical scoping. If a variable is valid
within the scope of a function, what
happens when you call that function
recursively? Is there more than one exp?
#i ncl ude <stdi o. h>
#i ncl ude <i nttypes. h>
f l oat pow( f l oat x, ui nt32_t exp)
{
/ * base case */
i f ( exp == 0) {
return 1. 0;
}
/ * recursi ve case */
return x*pow( x, exp 1) ;
}
i nt mai n( i nt argc, char **argv)
{
f l oat p;
p = pow( 5. 0, 1) ;
pri ntf ( p = %f \ n, p) ;
return 0;
}
Yes. Each function call allocates a stack
frame where Variables within that functions
scope will reside.
f l oat x 5. 0
ui nt 32_t exp 1
f l oat x 5. 0
ui nt 32_t exp 0
i nt ar gc 1
char **ar gv 0x2342
f l oat p undef i ned
Return 1.0
Return 5.0
i nt ar gc 1
char **ar gv 0x2342
f l oat p 5. 0
Grows
static
J ava?
17
Iterative pow(): the while loop
Problem: recursion eats stack space (in C).
Each loop must allocate space for arguments
and local variables, because each new call
creates a new scope.
f l oat pow( f l oat x, ui nt exp)
{
i nt i =0;
f l oat resul t=1. 0;
whi l e ( i < exp) {
resul t = resul t * x;
i ++;
}
return resul t;
}
i nt mai n( i nt argc, char **argv)
{
f l oat p;
p = pow( 10. 0, 5) ;
pri ntf ( p = %f \ n, p) ;
return 0;
}
Other languages?
Solution: while loop.
l oop:
i f ( condi t i on) {
st at ement s;
goto l oop;
}
whi l e ( condi t i on) {
st at ement s;
}
18
The for loop
f l oat pow( f l oat x, ui nt exp)
{
f l oat resul t=1. 0;
i nt i ;
f or ( i =0; ( i < exp) ; i ++) {
resul t = resul t * x;
}
return resul t;
}
i nt mai n( i nt argc, char **argv)
{
f l oat p;
p = pow( 10. 0, 5) ;
pri ntf ( p = %f \ n, p) ;
return 0;
}
f l oat pow( f l oat x, ui nt exp)
{
f l oat resul t=1. 0;
i nt i ;
i =0;
whi l e ( i < exp) {
resul t = resul t * x;
i ++;
}
return resul t;
}
i nt mai n( i nt argc, char **argv)
{
f l oat p;
p = pow( 10. 0, 5) ;
pri ntf ( p = %f \ n, p) ;
return 0;
}
The for loop is just shorthand for this while loop structure.
19
Referencing Data from Other Scopes
So far, all of our examples all of the data values we have
used have been defined in our lexical scope
f l oat pow( f l oat x, ui nt exp)
{
f l oat resul t=1. 0;
i nt i ;
f or ( i =0; ( i < exp) ; i ++) {
resul t = resul t * x;
}
return resul t;
}
i nt mai n( i nt argc, char **argv)
{
f l oat p;
p = pow( 10. 0, 5) ;
pri ntf ( p = %f \ n, p) ;
return 0;
}
Nothing in this scope
Uses any of these variables
20
Can a function modify its arguments?
What if we wanted to implement a function pow_assign() that
modified its argument, so that these are equivalent:
f l oat p = 2. 0;
/ * p i s 2. 0 here */
pow_assi gn( p, 5) ;
/ * p i s 32. 0 here */
f l oat p = 2. 0;
/ * p i s 2. 0 here */
p = pow( p, 5) ;
/ * p i s 32. 0 here */
voi d pow_assi gn( f l oat x, ui nt exp)
{
f l oat resul t=1. 0;
i nt i ;
f or ( i =0; ( i < exp) ; i ++) {
resul t = resul t * x;
}
x = resul t;
}
Would this work?
21
NO!
voi d pow_assi gn( f l oat x, ui nt exp)
{
f l oat resul t=1. 0;
i nt i ;
f or ( i =0; ( i < exp) ; i ++) {
resul t = resul t * x;
}
x = resul t;
}
{
f l oat p=2. 0;
pow_assi gn( p, 5) ;
}
Remember the stack!
f l oat x 2. 0
ui nt 32_t exp 5
f l oat r esul t 1. 0
f l oat p 2. 0 Grows
f l oat x 2. 0
ui nt 32_t exp 5
f l oat r esul t 32. 0
f l oat x 32. 0
ui nt 32_t exp 5
f l oat r esul t 32. 0
In C, all arguments are
passed as values
But, what if the argument is
the address of a variable?
J ava/C++?
22
Passing Addresses
Recall our model for variables stored
in memory
Symbol Addr Value
0
1
2
3
char x 4 H (72)
char y 5 e (101)
6
7
8
9
10
11
12
What if we had a way to find out the
address of a symbol, and a way to
reference that memory location by
address?
address_of ( y) == 5
memory_at[ 5] == 101
voi d f ( address_of _char p)
{
memory_at[ p] = memory_at[ p] - 32;
}
char y = 101; / * y i s 101 */
f ( address_of ( y) ) ; / * i . e. f ( 5) */
/ * y i s now101- 32 = 69 */
23
Pointers
This is exactly how pointers work.
address of or ref erence operator: &
memory_at or deref erence operator: *
voi d f ( char * p)
{
*p = *p - 32;
}
char y = 101; / * y i s 101 */
f ( &y) ; / * i . e. f ( 5) */
/ * y i s now101- 32 = 69 */
voi d f ( address_of _char p)
{
memory_at[ p] = memory_at[ p] - 32;
}
char y = 101; / * y i s 101 */
f ( address_of ( y) ) ; / * i . e. f ( 5) */
/ * y i s now101- 32 = 69 */
A pointer type: pointer to char
Pointers are used in C for many other purposes:
Passing large objects without copying them
Accessing dynamically allocated memory
Referring to functions
24
Pointer Validity
A Valid pointer is one that points to memory that your program controls.
Using invalid pointers will cause non-deterministic behavior, and will
often cause Linux to kill your process (SEGV or Segmentation Fault).
There are two general causes for these errors:
Program errors that set the pointer value to a strange number
Use of a pointer that was at one time valid, but later became invalid
How should pointers be initialized?
char * get_poi nter( )
{
char x=0;
return &x;
}
{
char * ptr = get_poi nter( ) ;
*ptr = 12; / * val i d? */
}
Will ptr be valid or invalid?
25
Answer: Invalid!
A pointer to a variable allocated on the stack becomes invalid when
that variable goes out of scope and the stack frame is popped. The
pointer will point to an area of the memory that may later get reused
and rewritten.
100 char * pt r ? Grows
char * get_poi nter( )
{
char x=0;
return &x;
}
{
char * ptr = get_poi nter( ) ;
*ptr = 12; / * val i d? */
other_f uncti on( ) ;
}
101 char x 0
100 char * pt r 101
101 char x 0
But now, pt r points to a
location thats no longer in
use, and will be reused the
next time a function is called!
Return 101
101 char x 12 101 i nt aver age 456603
26
More on Types
Weve seen a few types at this point: char, int, float, char *
Types are important because:
They allow your program to impose logical structure on memory
They help the compiler tell when youre making a mistake
In the next slides we will discuss:
How to create logical layouts of different types (structs)
How to use arrays
How to parse C type names (there is a logic to it!)
How to create new types using typedef
27
Structures
struct: a way to compose existing types into a structure
#i ncl ude <sys/ ti me. h>
/ * decl are the struct */
struct my_struct {
i nt counter;
f l oat average;
struct ti meval ti mestamp;
ui nt i n_use: 1;
ui nt8_t data[ 0] ;
};
/ * def i ne an i nstance of my_struct */
struct my_struct x = {
i n_use: 1,
ti mestamp: {
tv_sec: 200
}
};
x. counter = 1;
x. average = sum/ ( f l oat) ( x. counter) ;
struct my_struct * ptr = &x;
ptr- >counter = 2;
( *ptr) . counter = 3; / * equi v. */
struct timeval is defined in this header
structs can contain other structs
fields can specify specific bit widths
A newly-defined structure is initialized
using this syntax. All unset fields are 0.
structs define a layout of typed fields
Fields are accessed using . notation.
A pointer to a struct. Fields are accessed
using -> notation, or (*ptr).counter
Packing?
Why?
28
Arrays
Arrays in C are composed of a particular type, laid out in memory in a
repeating pattern. Array elements are accessed by stepping forward in
memory from the base of the array by a multiple of the element size.
/ * def i ne an array of 10 chars */
char x[ 5] = { t , e , s , t , \ 0 };
/ * accessi ng el ement 0 */
x[ 0] = T ;
/ * poi nter ari thmeti c to get el t 3 */
char el t3 = *( x+3) ; / * x[ 3] */
/ * x[ 0] eval uates to the f i rst el ement;
* x eval uates to the address of the
* f i rst el ement, or &( x[ 0] ) */
/ * 0- i ndexed f or l oop i di om*/
#def i ne COUNT 10
char y[ COUNT] ;
i nt i ;
f or ( i =0; i <COUNT; i ++) {
/ * process y[ i ] */
pri ntf ( %c\ n, y[ i ] ) ;
}
Brackets specify the count of elements.
Initial values optionally set in braces.
Arrays in C are 0-indexed (here, 0..9)
x[3] ==*(x+3) ==t (NOT s!)
Symbol Addr Value
char x [0] 100 t
char x [1] 101 e
char x [2] 102 s
char x [3] 103 t
char x [4] 104 \0
Whats the difference
between char x[] and
char *x?
For loop that iterates
from 0 to COUNT-1.
Memorize it!
29
How to Parse and Define C Types
At this point we have seen a few basic types, arrays, pointer types,
and structures. So far weve glossed over how types are named.
i nt x; / * i nt; */ typedef i nt T;
i nt *x; / * poi nter to i nt; */ typedef i nt *T;
i nt x[ 10] ; / * array of i nts; */ typedef i nt T[ 10] ;
i nt *x[ 10] ; / * array of poi nters to i nt; */ typedef i nt *T[ 10] ;
i nt ( *x) [ 10] ; / * poi nter to array of i nts; */ typedef i nt ( *T) [ 10] ;
C type names are parsed by starting at the type name and working
outwards according to the rules of precedence:
i nt ( *x) [ 10] ;
x i s
a poi nter to
an array of
i nt
i nt *x[ 10] ;
x i s
an array of
poi nters to
i nt
Arrays are the primary
source of confusion. When
in doubt, use extra parens to
clarify the expression.
typedef defines
a new type
30
Function Types
The other confusing form is the function type.
For example, qsort: (a sort function in the standard library)
voi d qsort( voi d *base, si ze_t nmemb, si ze_t si ze,
i nt ( *compar) ( const voi d *, const voi d *) ) ;
For more details:
$ man qsort
/ * f uncti on matchi ng thi s type: */
i nt cmp_f uncti on( const voi d *x, const voi d *y) ;
/ * typedef def i ni ng thi s type: */
typedef i nt ( *cmp_type) ( const voi d *, const voi d *) ;
/ * rewri te qsort prototype usi ng our typedef */
voi d qsort( voi d *base, si ze_t nmemb, si ze_t si ze, cmp_type compar) ;
The last argument is a
comparison function
const means the function
is not allowed to modify
memory via this pointer.
void * is a pointer to memory of unknown type.
size_t is an unsigned int
31
Dynamic Memory Allocation
So far all of our examples have allocated variables statically by
defining them in our program. This allocates them in the stack.
But, what if we want to allocate variables based on user input or other
dynamic inputs, at run-time? This requires dynamic allocation.
i nt * al l oc_i nts( si ze_t requested_count)
{
i nt * bi g_array;
bi g_array = ( i nt *) cal l oc( requested_count, si zeof ( i nt) ) ;
i f ( bi g_array == NULL) {
pri ntf ( can t al l ocate %d i nts: %m\ n, requested_count) ;
return NULL;
}
/ * nowbi g_array[ 0] . . bi g_array[ requested_count- 1] are
* val i d and zeroed. */
return bi g_array;
}
calloc() allocates memory
for N elements of size k
Returns NULL if cant alloc
For details:
$ man calloc
%m ? Emstar tips
sizeof() reports the size of a type in bytes
Its OK to return this pointer.
It will remain valid until it is
freed with free()
32
Caveats with Dynamic Memory
Dynamic memory is useful. But it has several caveats:
Whereas the compiler enforces that reclaimed stack space can no longer
be reached, it is easy to accidentally keep a pointer to dynamic memory
that has been freed. Whenever you free memory you must be certain that
you will not try to use it again. It is safest to erase any pointers to it.
Whereas the stack is automatically reclaimed, dynamic allocations must be
tracked and free()d when they are no longer needed. With every
allocation, be sure to plan how that memory will get freed. Losing track of
memory is called a memory leak.
Reference counting
Because dynamic memory always uses pointers, there is generally no way
for the compiler to statically verify usage of dynamic memory. This means
that errors that are detectable with static allocation are not with dynamic
33
Some Common Errors and Hints
/ * al l ocati ng a struct wi th mal l oc( ) */
struct my_struct *s = NULL;
s = ( struct my_struct *) mal l oc( si zeof ( *s) ) ; / * NOT si zeof ( s) ! ! */
i f ( s == NULL) {
pri ntf ( stderr, no memory! ) ;
exi t( 1) ;
}
memset( s, 0, si zeof ( *s) ) ;
/ * another way to i ni ti al i ze an al l oc d structure: */
struct my_struct i ni t = {
counter: 1,
average: 2. 5,
i n_use: 1
};
/ * memmove( dst, src, si ze) ( note, arg order l i ke assi gnment) */
memmove( s, &i ni t, si zeof ( i ni t) ) ;
/ * when you are done wi th i t, f ree i t! */
f ree( s) ;
s = NULL;
sizeof() can take a variable reference in place of a type name. This gurantees the right
allocation, but dont accidentally allocate the sizeof() the pointer instead of the object!
malloc() does not zero the memory,
so you should memset() it to 0.
Always check for NULL.. Even if you just exit(1).
malloc() allocates n bytes
Why?
memmove is preferred because it is
safe for shifting buffers
Why?
Use pointers as implied in-use flags!
34
Macros
Macros can be a useful way to customize your interface to C and make
your code easier to read and less redundant. However, when
possible, use a static inline function instead.
Macros and static inline functions must be included in any file that uses
them, usually via a header file. Common uses for macros:
Whats the difference between a
macro and a static inline function?
/ * Macros are used to def i ne constants */
#def i ne FUDGE_FACTOR 45. 6
#def i ne MSEC_PER_SEC 1000
#def i ne I NPUT_FI LENAME my_i nput_f i l e
/ * Macros are used to do constant ari thmeti c */
#def i ne TI MER_VAL ( 2*MSEC_PER_SEC)
/ * Macros are used to capture i nf ormati on f romthe compi l er */
#def i ne DBG( args. . . ) \
do { \
f pri ntf ( stderr, %s: %s: %d: , \
__FUNCTI ON__, __FI LE__, __LI NENO__) ; \
f pri ntf ( stderr, args. . . ) ; \
} whi l e ( 0)
/ * ex. DBG( error: %d, errno) ; */
Float constants must have a decimal
point, else they are type int
Put expressions in parens.
Why?
Multi-line macros need \
args grabs rest of args
Enclose multi-statement macros in do{}while(0)
Why?
More on C
constants?
enums
35
Macros and Readability
Sometimes macros can be used to improve code readability but
make sure whats going on is obvious.
/ * of ten best to def i ne these types of macro ri ght where they are used */
#def i ne CASE( str) i f ( strncasecmp( arg, str, strl en( str) ) == 0)
voi d parse_command( char *arg)
{
CASE( hel p) {
/ * pri nt hel p */
}
CASE( qui t) {
exi t( 0) ;
}
}
/ * and un- def i ne themaf ter use */
#undef CASE
Macros can be used to generate static inline functions. This is like a C
version of a C++template. See emstar/libmisc/include/queue.h for an
example of this technique.
voi d parse_command( char *arg)
{
i f ( strncasecmp( arg, hel p, strl en( hel p) ) {
/ * pri nt hel p */
}
i f ( strncasecmp( arg, qui t, strl en( qui t) ) {
exi t( 0) ;
}
}
36
Using goto
Some schools of thought frown upon goto, but goto has its place. A
good philosophy is, always write code in the most expressive and clear
way possible. If that involves using goto, then goto is not bad.
An example is jumping to an error case from inside complex logic. The
alternative is deeply nested and confusing if statements, which are
hard to read, maintain, and verify. Often additional logic and state
variables must be added, just to avoid goto.
goto try_again;
goto fail;
37
Unrolling a Failed Initialization using goto
state_t *i ni ti al i ze( )
{
/ * al l ocate state struct */
state_t *s = g_new0( state_t, 1) ;
i f ( s) {
/ * al l ocate sub- structure */
s- >sub = g_new0( sub_t, 1) ;
i f ( s- >sub) {
/ * open f i l e */
s- >sub- >f d =
open( / dev/ nul l , O_RDONLY) ;
i f ( s- >sub- >f d >= 0) {
/ * success! */
}
el se {
f ree( s- >sub) ;
f ree( s) ;
s = NULL;
}
}
el se {
/ * f ai l ed! */
f ree( s) ;
s = NULL;
}
}
return s;
}
state_t *i ni ti al i ze( )
{
/ * al l ocate state struct */
state_t *s = g_new0( state_t, 1) ;
i f ( s == NULL) goto f ree0;
/ * al l ocate sub- structure */
s- >sub = g_new0( sub_t, 1) ;
i f ( s- >sub == NULL) goto f ree1;
/ * open f i l e */
s- >sub- >f d =
open( / dev/ nul l , O_RDONLY) ;
i f ( s- >sub- >f d < 0) goto f ree2;
/ * success! */
return s;
f ree2:
f ree( s- >sub) ;
f ree1:
f ree( s) ;
f ree0:
return NULL;
}
38
High Level Question: Why is Software Hard?
Answer(s):
Complexity: Every conditional (if) doubles number of paths
through your code, every bit of state doubles possible states
Solution: reuse code paths, avoid duplicate state variables
Mutability: Software is easy to change.. Great for rapid fixes ..
And rapid breakage .. always one character away from a bug
Solution: tidy, readable code, easy to understand by inspection.
Avoid code duplication; physically the same logically the same
Flexibility: Programming problems can be solved in many
different ways. Few hard constraints plenty of rope.
Solution: discipline and idioms; dont use all the rope
39
Addressing Complexity
Complexity: Every conditional (if) doubles number of paths
through your code, every bit of state doubles possible states
Solution: reuse code paths, avoid duplicate state variables
On recei ve_packet:
i f queue f ul l , drop packet
el se push packet, cal l run_queue
On transmi t_compl ete:
state=i dl e, cal l run_queue
Run_queue:
i f state==i dl e && ! queue empty
pop packet of f queue
start transmi t, state = busy
reuse code paths
On input, change our state as needed, and
call Run_queue. In all cases, Run_queue
handles taking the next step
40
Addressing Complexity
Complexity: Every conditional (if) doubles number of paths
through your code, every bit of state doubles possible states
Solution: reuse code paths, avoid duplicate state variables
avoid duplicate state variables
i nt transmi t_busy;
msg_t *packet_on_deck;
i nt start_transmi t( msg_t *packet)
{
i f ( transmi t_busy) return - 1;
/ * start transmi t */
packet_on_deck = packet;
transmi t_busy = 1;
/ * . . . */
return 0;
}
msg_t *packet_on_deck;
i nt start_transmi t( msg_t *packet)
{
i f ( packet_on_deck ! = NULL) return - 1;
/ * start transmi t */
packet_on_deck = packet;
/ * . . . */
return 0;
}
Why return -1?
41
Addressing Mutability
Mutability: Software is easy to change.. Great for rapid fixes ..
And rapid breakage .. always one character away from a bug
Solution: tidy, readable code, easy to understand by inspection.
Avoid code duplication; physically the same logically the same
Tidy code.. Indenting, good formatting, comments, meaningful variable and
function names. Version control.. Learn how to use CVS
Avoid duplication of anything thats logically identical.
struct pkt_hdr {
i nt source;
i nt dest;
i nt l ength;
};
struct pkt {
i nt source;
i nt dest;
i nt l ength;
ui nt8_t payl oad[ 100] ;
};
struct pkt_hdr {
i nt source;
i nt dest;
i nt l ength;
};
struct pkt {
struct pkt_hdr hdr;
ui nt8_t payl oad[ 100] ;
};
Otherwise when
one changes, you
have to find and fix
all the other places
42
Solutions to the pow() challenge question
Which is better? Why?
Recursive
f l oat pow( f l oat x, ui nt exp)
{
f l oat resul t;
/ * base case */
i f ( exp == 0)
return 1. 0;
/ * x^( 2*a) == x^a * x^a */
resul t = pow( x, exp >> 1) ;
resul t = resul t * resul t;
/ * x^( 2*a+1) == x^( 2*a) * x */
i f ( exp & 1)
resul t = resul t * x;
return resul t;
}
f l oat pow( f l oat x, ui nt exp)
{
f l oat resul t = 1. 0;
i nt bi t;
f or ( bi t = si zeof ( exp) *8- 1;
bi t >= 0; bi t - - ) {
resul t *= resul t;
i f ( exp & ( 1 << bi t) )
resul t *= x;
}
return resul t;
}
Iterative

You might also like