Lexical: Whitespace
Whitespace in source code is one or more
unprintable characters: space, CR, LF, HT, VT, FF
Other unprintable characters are illegal outside of
quotes; should not be used directly inside of
quotes (use hex notation instead)
Whitespace is ignored by compiler, except where
needed to separate identifiers from keywords, etc.
It is best to completely avoid VT and FF
Use of tabs (HT) vs. spaces for indentation is a
matter of taste, convenience, adherence to project
standards and facilities offered by IDE.
Lexical: Identifiers
Identifiers are case-sensitive: abc and ABC are
different names; if is a reserved word, IF is not
Allowable characters: A-Z, a-z, 0-9, _
Cannot start with 0-9
Sometimes $ is allowed, but generally should be
avoided
Underscores may start identifier, but some uses
are reserved for special/system names; be careful
Modern compilers dont restrict identifier length;
check compiler for specific limitations
Lexical: Identifiers
All-capital names are generally reserved for
constants (const) and #define names
Initial-capital names sometimes used as a
convention for type names (typedef,
struct, union)
Naming styles vary by taste:
threePartName
three_part_name
Lexical: Comments
A comment is equivalent to whitespace; it
acts as a separator, and is otherwise ignored
C comments start with /* and end with */
No spaces between / and * are allowed
C comments do not nest
/* this /* is */ bad */
Lexical: Comments
Many C compilers have features borrowed
from C++ compilers
C++ style line-comments may be available
These comment starts with //, ends with end
of line
Not strictly ANSI-compliant language, but
often useful
When combining /* and // comments:
whoever gets there first, gets it
Lexical: Comments
Convention to comment-out a large block of
code: use preprocessor #if 0
Because its a preprocessor block construct, it
is nestable
Additional preprocessor commands discussed
later
Example:
#if 0
this line is commented out
#endif
Lexical: Characters
A character is enclosed in single quotes, and defines
one and only one 8-bit character
A character is of data type char
Backslash notation can be used to escape special
characters, octal or hex values
See compiler documentation for complete list of
special escaped values. Most common are '\n' for
newline (LF) and '\t' for horizontal tab (HT)
Literal backslash or quote character must be escaped
'a' '\033' '\xFF'
'\'' '\\'
Lexical: Characters
Value of a character is an integer, equal to the
collating sequence of the character in ASCII
Check compiler to see if char data type is signed or
unsigned
' ' numerically the same as 32 (0x20)
'1' numerically the same as 49 (0x31)
Notation is L'x'
Limited usage in embedded systems
Lexical: Strings
Strings are zero or more ASCII characters enclosed
in double quotes
Strings are of data type char * (pointer to
character)
"Hello, World"
C compiler automatically adds a binary-zero
character at the end of a string literal in memory
This zero-byte is called the null terminator
Strings have limited use in embedded systems
Lexical: Strings
The value of a string is a pointer, a
memory address
A string of length one is not a character
'x'
Lexical: Increment/Decrement
Operators
Increment, decrement: ++ -These operators have side effects: they change a
variable in memory and produce a value result
Somewhat like compound operators += and -=
Example: x++; /* add 1 to x */
Two formats: prefix and postfix
Prefix format: ++x; first increments or
decrements a value by 1, then uses newly modified
value in rest of expression
Postfix format: x++; uses old value in
expression, then increments or decrements the
value by 1 later
Lexical: Increment/Decrement
Operators
Example:
int
y =
y =
y =
y =
x1=10,
++x1;
x2++;
--x3;
x4--;
Preprocessor: Basics
The C compiler has a preprocessor, which uses
directives embedded in your C program, to insert
additional C code, to selectively incorporate and/or
exclude code, and to selectively modify code
The resultant effective program is what is actually
compiled
Your source program is never actually changed
Old days: preprocessor actually created an
intermediate file which was then compiled
Now: preprocessor is integrated; creation of
intermediate file is done internally in one pass
All preprocessor directives start with #
Preprocessor: #include
#include copies files from an include library, which
are compiled as if the contents of the file were
actually part of your program
Included files are often referred to as header files,
since they usually appear at the head (the
beginning) of your program
Header files normally have a file extension of .h,
but can have any valid file name
Examples:
#include <stdio.h>
#include "myfile.h"
Preprocessor: #include
Compiler looks for included files using an include
path
Preprocessor: #include
Commonly used system include files
<stdio.h>
<stdlib.h>
<math.h>
<ctype.h>
<string.h>
<assert.h>
<malloc.h>
<time.h>
//
//
//
//
//
//
//
//
Preprocessor: #define
Used for substituting a symbol with a text value
Never use constants in code, use #define to define
constants with appropriate names.
The name introduced in this way is often called a
define name or a pound-define name
Definition may be a simple symbol or a parameterized
this is often called a macro, a define macro or a pound-
define macro
Preprocessor: #define
Rules and guidelines
Define names traditionally are written in UPPER
CASE; this is a convention, but not a requirement
For macros, no spaces allowed between name and
parameter list, or inside the list
If a name is already defined, defining it again is a
warning or error condition
A definition with no value is OK; it just makes a name
known to be defined to the compiler
#define TEST_MODE
It is possible to remove a definition of ABC using
#undef ABC
Preprocessor: #define
Traditional uses for #define:
Definitions of constants
Name substitution for variables and functions
Definitions of flags to control compile using #if
Selective debugging
Examples:
#define
#define
#define
#define
PORT 0x100
MYFUNCTION your_function
SUM(x,y) (x+y)
DEBUG_MODE
Preprocessor: #define
Defined names can also be introduced by commandline or through IDE
They appear to C program exactly like a name
defined using #define directive
This is often done to set up compile-time options
Typical format:
cc DNAME=value myprogram.c
Compiler has list of predefined names, like
__DATE__, __TIME__, __LINE__, __FILE__
See compiler documentation for details
Preprocessor: #pragma
Short for pragmatic comment
Used to define vendor-specific compiler
options
Typical use in embedded systems:
memory load addresses for functions
and static data (passed to linker)
See compiler documentation for details
Function pointers
Pointer variables can be used to store addresses of functions. These pointer are
called function pointers. Function pointers are useful to implement callback
function mechanism, used in event driven programming.
Reentrant function:
Reentrant function is usable by the several tasks and routines synchronously (at the same
time). This is because all its argument values are retrievable from the stack. A function is
called reentrant function when the following three conditions are satisfied.
1) All the arguments pass the values and none of the argument is a pointer whenever a
calling function calls that function.
2) When an operation is not atomic, that function should not operate on any variable,
which is declared outside the function or which an interrupt service routine uses or which
is a global variable but passed by reference and not passed by value as an argument
into the function.
3)That function does not call any other function that is not itself Reentrant.
Pointers:
Concept of Address and Size
In a program, every variable and function will have unique address associated
with it. Whenever a program is loaded into the memory for executing, the
variables and functions and functions will be loaded into their respective
addresses.
Besides the address, every variable will also have size. Same way every function
will occupy some space in the memory.
Direct Addressing:
Whenever any variable is defined, some memory will be allocated for that
variable. The address of this memory is the address of this variable, the value
will be placed in the memory location of its address. This is called direct
addressing.
int a
;/** memory allocated say at address 100**/
a = 12 ;/**a value 12 will be placed in memory starting at address 100**/
Pointer Dereference:
Putting * in front of a pointer variable and using is called dereferencing.
When you dereference a pointer variable, it represents variable of that
pointer type.
float *pf ;
Compiler understands two things. One is pf is a pointer variable. So
memory space will be allocated to this pointer variable. Second is that when
you dereference this pf pointer it acts as float variables. So *pf is a float
variable.
Dereferencing of pointer variable provides the indirect addressing capability.
Pointer Type:
Every pointer variable will have a type.
int
*pi; // pointer of type integer
short *ps; //pointer of type short
char *pc; //pointer of type char
Use of pointers:
Arrays:
We use arrays when we need multiple elements of same type. These elements
of an array could be simple variables like chars and integers or could be
structures also. These structures may contain arrays within themselves. In this
way we can build complex data structures with the help of arrays and
structures.
we can get address of any element of array by using & operator &arr[1]
//*address of second element of array *//
&arr[2] //*address of third element of array *//
Structures
Organizing the information as structures or objects
It is a good practice to organize all the global data as structures or as array of
structures . It is not good to have so many global scalar variables. Organizing all
the global data into a set of structures provides modularity to the data. In fact
we can equate objects in object oriented programming to structures in C
programming. So similar to how object oriented programmers try to identify the
objects. C programmers should identify the good structures and build functions
to operate on these global structures.
eg: typedef struct
(defining a graphic point structure, which contains
{ int x
x and y c-oordinate fields.)
int y
}point_t;
point_t
point;
Structure pointers
Unlike arrays, name of a structure does not represent a pointer. Structure
variable behave like normal scalar variables (int, char..). So to get the address
of a structure we should use & operator.
sampStruct_t st;
/* structure variable */
sampStruct_t pSt;
/* pointer to structure variable */
pSt
&st;
The way we represent fields of a structure is different for structure variables and
structure pointers.
st.chVal; /* we use (.) To separate structure variable name and field name*/
st.inVal;
pStchVal; /* we use arrow to access fields through structure pointer */
In fact the above is the shorthand notation for (*pSt).chVal.
5)
6)
7)
8)
Byte arithmetic takes less time than integer arithmetic. So use unsigned
bytes for short and a short for integer if possible, to optimize use of RAM
and ROM available in the system.
Avoid use of library functions in case a generalized function is expected to
take more memory when its coding is simple.
Use modifier register for frequently used variables.
Use global variables if shared data problems are tackled and use static
variables in case frequently saving on the stack is needed. ( when a variable
is declared static, the processor accesses with less instructions than for the
stack).
Whenever feasible combine two function of more or less similar codes.
Reduce use of frequent function calls and nested call and thus reduce the
time and RAM memory needed for the stacks.
Use the delete function when there is no longer a need for a set of
statements after they execute.
If feasible use alternatives to switch statements with a table of pointers to
the functions.
Useful links:
Quality and Standards (automotive):
http://www.misra-c2.com/ (look in resources)
Cosmic compilers:
http://www.cosmic-software.com/download.php