Sei sulla pagina 1di 39

C_Lang_Ref Raja 1

C Language Reference
C_Lang_Ref Raja 2

C IS A STRUCTURED LANGUAGE...........................................................................................................3
HOW A DECLARATION IS FORMED: ....................................................................................................3
POINTER........................................................................................................................................................5
STRUCTURE..................................................................................................................................................6
MACROS.........................................................................................................................................................6
LIBRARY AND LINKING:..........................................................................................................................6
PROCESS MEMORY LAYOUT..................................................................................................................6
TYPE QUALIFIERS......................................................................................................................................9
CONST............................................................................................................................................................9
VOLATILE......................................................................................................................................................10
Volatile Article ......................................................................................................................................11
STORAGE CLASS SPECIFIERS: ............................................................................................................15
EXTERN........................................................................................................................................................15
STATIC..........................................................................................................................................................16
Static Local variables...........................................................................................................................16
Static Global Variable...........................................................................................................................16
REGISTER .....................................................................................................................................................17
VARIABLE INITIALIZATION................................................................................................................................17
CONSTANTS................................................................................................................................................17
OPERATORS................................................................................................................................................17
ASSIGNMENT..................................................................................................................................................18
INCREMENT AND DECREMENT OPERATORS..........................................................................................................18
RELATIONAL AND LOGICAL OPERATORS.......................................................................................18
THE COMPILE-TIME OPERATOR SIZEOF..............................................................................................................19
BITWISE OPERATORS.......................................................................................................................................19
OPERATOR PRECEDENCE..................................................................................................................................19
SOME OF THE OPERATORS HAVE THE WRONG PRECEDENCE..................................................................................20
ASSOCIATIVITY...............................................................................................................................................21
TYPEDEF......................................................................................................................................................21
FUNCTION POINTERS..............................................................................................................................22
THE CONST FUNCTION ATTRIBUTE...................................................................................................22
SORTING AND SEARCHING ALGORITHMS.......................................................................................22
COMPARISON SORT:.........................................................................................................................................22
SORTING ALGORITHMS:...................................................................................................................................23
COMPARISION:................................................................................................................................................23
ORDERS OF COMMON FUNCTIONS.......................................................................................................................24
C QUESTIONS:............................................................................................................................................25
THE PRECEDENCE RULE.......................................................................................................................27
A 'C' TEST: THE 0X10 BEST QUESTIONS FOR WOULD-BE EMBEDDED PROGRAMMERS . 30
REFERENCE:..............................................................................................................................................39
C_Lang_Ref Raja 3

C is a Structured Language

How a declaration is formed:

An important building block is a declarator—the heart of any declaration; roughly, a declarator


is the identifier and any pointers, function brackets, or array indica-tions that go along with it
C_Lang_Ref Raja 4
C_Lang_Ref Raja 5

Pointer

• Pointer is an address variable which contains address of another variable.


• Pointer operators are * and &.
• int pointer contains address of int quantity, float contains float quantity, same holds
good for each data type even for user defined data types.
• Maximum size of pointer is 2 bytes.
C_Lang_Ref Raja 6

Structure
A structure is a collection of one or more variables, possibly of different data types, grouped
together under a single name for convenient handling.

Macros

Macros are preprocessor directives. They get replaced in source code before compilation.

Advantages:

• We can replace any valid C statement or condition in user own words. We can do File
inclusion also. We can do conditional compilation. We can pass arguments also in
Macros. They can be used anywhere inside the program.

Disadvantages:

• Macro will replace macro template with its macro expansion without checking any
error. If there r number of occurrence of macros then it will increase the size of
program.

Library and Linking:

Process Memory Layout

 A running program is called a process and when a program is run, its executable
image is loaded into memory area that normally called a process address space in an
organized manner.
 This is a physical memory space and do not confuse yourself with the virtual address
space .
 Process address space is organized into three memory areas, called segments: the
text segment, stack segment, and data segment (bss and data) and can be
illustrated below.
 The text segment (also called a code segment) is where the compiled code of the
program itself resides.
 In the disk file (object files) the segments were called sections.
C_Lang_Ref Raja 7
C_Lang_Ref Raja 8
C_Lang_Ref Raja 9

Type Qualifiers
• const
• volatile
• restrict

Const
C_Lang_Ref Raja 10

Variables of type const may not be changed by your program. The compiler is free to place
variables of this type into read-only memory (ROM). Many functions in the standard library use
const in their parameter declarations.
You can also use const to verify that your program does not modify a variable.

 Remember, a variable of type const can be modified by something outside your


program. For example, a hardware device may set its value. However, by declaring a
variable as const, you can prove that any changes to that variable occur because of
external events.

where do, const variables (i.e constants or literals) get memory , is there is
separate area for them or they are allocated in the data region only.

This is very much system-specific. Const variables can be allocated in three different places: in
ROM, in RAM or in code ROM. On a RAM-based system such as a PC, the ROM is just another
segment of the RAM, as there is no physical ROM.

- The variable ends up in ROM if you declare it as a global constant.


- The variable ends up in RAM if you declare it as a local constant or a parameter to a function.
- The variable ends up in code ROM if the compiler optimizes the expression where the
variable is used. It can also end up in ROM if you use numeric constants or strings:

char x = 5;/* 5 is allocated in code ROM */


char* ptr = “abc”;/* “abc” is allocated in code ROM */
Constants allocated in ROM or RAM can be viewed while debugging, as opposed to constants in
code ROM.

ROM is the preferred memory type in most cases. First of all, it isn’t volatile, so constants
placed in ROM on ROM-based systems are much safer than constants placed in RAM. In most
cases, constants in ROM can also be placed at fixed locations; although the method of how a
constant is placed at a specific address in ROM is not specified by ISO C. Constants in RAM
usually end up on the stack, and are allocated in runtime.

Volatile

The modifier volatile tells the compiler that a variable's value may be changed in ways not
explicitly specified by the program. For example, a global variable's address may be passed to
the operating system's clock routine and used to hold the system time. In this situation, the
contents of the variable are altered without any explicit assignment statements in the
program. This is important because most C compilers automatically optimize certain
expressions by assuming that a variable's content is unchanging if it does not occur on the left
side of an assignment statement; thus, it might not be reexamined each time it is referenced.
Also, some compilers change the order of evaluation of an expression during the compilation
process. The volatile modifier prevents these changes.

 You can use const and volatile together. For example, if 0x30 is assumed to be the
value of a port that is changed by external conditions only, the following declaration
would prevent any possibility of accidental side effects:

const volatile char *port = (const volatile char *) 0x30;


C_Lang_Ref Raja 11

Volatile Article

Have you experienced any of the following in your C or C++ embedded code?

 Code that works fine--until you enable compiler optimizations


 Code that works fine--until interrupts are enabled
 Flaky hardware drivers
 RTOS tasks that work fine in isolation--until some other task is spawned

If you answered yes to any of the above, it's likely that you didn't use the C keyword volatile.

C's volatile keyword is a qualifier that is applied to a variable when it is declared. It tells the
compiler that the value of the variable may change at any time--without any action being
taken by the code the compiler finds nearby.

volatile keyword syntax

To declare a variable volatile, include the keyword volatile before or after the data type in the
variable definition. For instance both of these declarations will declare foo to be a volatile
integer:

volatile int foo;

int volatile foo;

Now, it turns out that pointers to volatile variables are very common, especially with memory-
mapped I/O registers. Both of these declarations declare pReg to be a pointer to a volatile
unsigned 8-bit integer:

volatile uint8_t * pReg;

uint8_t volatile * pReg;

Volatile pointers to non-volatile data are very rare (I think I've used them once), but I'd better
go ahead and give you the syntax:

int * volatile p;

And just for completeness, if you really must have a volatile pointer to a volatile variable,
you'd write:

int volatile * volatile p;

Incidentally, for a great explanation of why you have a choice of where to place volatile and
why you should place it after the data type (for example, int volatile * foo), read Dan Sak's
column "Top-Level cv-Qualifiers in Function Parameters" (Embedded Systems Programming,
February 2000, p. 63).

Finally, if you apply volatile to a struct or union, the entire contents of the struct/union are
volatile. If you don't want this behavior, you can apply the volatile qualifier to the individual
members of the struct/union.

Proper use of volatile


C_Lang_Ref Raja 12

A variable should be declared volatile whenever its value could change unexpectedly. In
practice, only three types of variables could change:

1. Memory-mapped peripheral registers

2. Global variables modified by an interrupt service routine

3. Global variables accessed by multiple tasks within a multi-threaded application

We'll talk about each of these cases in the sections that follow.

Peripheral registers

Embedded systems contain real hardware, usually with sophisticated peripherals. These
peripherals contain registers whose values may change asynchronously to the program flow.
As a very simple example, consider an 8-bit status register that is memory mapped at address
0x1234. It is required that you poll the status register until it becomes non-zero. The naive
and incorrect implementation is as follows:

uint8_t * pReg = (uint8_t *) 0x1234;

// Wait for register to become non-zero

while (*pReg == 0) { } // Do something else

This will almost certainly fail as soon as you turn compiler optimization on, since the compiler
will generate assembly language that looks something like this:

mov ptr, #0x1234 mov a, @ptr

loop:

bz loop

The rationale of the optimizer is quite simple: having already read the variable's value into the
accumulator (on the second line of assembly), there is no need to reread it, since the value
will always be the same. Thus, in the third line, we end up with an infinite loop. To force the
compiler to do what we want, we modify the declaration to:

uint8_t volatile * pReg = (uint8_t volatile *) 0x1234;

The assembly language now looks like this:

mov ptr, #0x1234

loop:
C_Lang_Ref Raja 13

mov a, @ptr

bz loop

The desired behavior is achieved.

Subtler problems tend to arise with registers that have special properties. For instance, a lot of
peripherals contain registers that are cleared simply by reading them. Extra (or fewer) reads
than you are intending can cause quite unexpected results in these cases.

Interrupt service routines

Interrupt service routines often set variables that are tested in mainline code. For example, a
serial port interrupt may test each received character to see if it is an ETX character
(presumably signifying the end of a message). If the character is an ETX, the ISR might set a
global flag. An incorrect implementation of this might be:

int etx_rcvd = FALSE;

void main()

...

while (!ext_rcvd)

// Wait

...

interrupt void rx_isr(void)

...

if (ETX == rx_char)

etx_rcvd = TRUE;
C_Lang_Ref Raja 14

...

With compiler optimization turned off, this code might work. However, any half decent
optimizer will "break" the code. The problem is that the compiler has no idea that etx_rcvd can
be changed within an ISR. As far as the compiler is concerned, the expression !ext_rcvd is
always true, and, therefore, you can never exit the while loop. Consequently, all the code after
the while loop may simply be removed by the optimizer. If you are lucky, your compiler will
warn you about this. If you are unlucky (or you haven't yet learned to take compiler warnings
seriously), your code will fail miserably. Naturally, the blame will be placed on a "lousy
optimizer."

The solution is to declare the variable etx_rcvd to be volatile. Then all of your problems (well,
some of them anyway) will disappear.

Multi-threaded applications

Despite the presence of queues, pipes, and other scheduler-aware communications


mechanisms in real-time operating systems, it is still fairly common for two tasks to exchange
information via a shared memory location (that is, a global). Even as you add a preemptive
scheduler to your code, your compiler has no idea what a context switch is or when one might
occur. Thus, another task modifying a shared global is conceptually identical to the problem of
interrupt service routines discussed previously. So all shared global variables should be
declared volatile. For example, this is asking for trouble:

int cntr;

void task1(void)

cntr = 0;

while (cntr == 0)

sleep(1);

...

}
C_Lang_Ref Raja 15

void task2(void)

...

cntr++;

sleep(10);

...

This code will likely fail once the compiler's optimizer is enabled. Declaring cntr to be volatile is
the proper way to solve the problem.

Final thoughts

Some compilers allow you to implicitly declare all variables as volatile. Resist this temptation,
since it is essentially a substitute for thought. It also leads to potentially less efficient code.

Also, resist the temptation to blame the optimizer or turn it off. Modern optimizers are so good
that I cannot remember the last time I came across an optimization bug. In contrast, I come
across failures by programmers to use volatile with depressing frequency.

If you are given a piece of flaky code to "fix," perform a grep for volatile. If grep comes up
empty, the examples given here are probably good places to start looking for problems.

Storage Class Specifiers:

C supports four storage class specifiers:


extern
static
register
auto

These specifiers tell the compiler how to store the subsequent variable. The general form of a
variable declaration that uses one is shown here:
storage_specifier type var_name;

Extern

C defines three categories of linkage:


External -> functions and global variables have external linkage. This means they are
available to all files that constitute a program.
internal -> File scope objects declared as static have internal linkage. These are known
only within the file in which they are declared.
None -> variables have no linkage and are therefore known only within their own block.
C_Lang_Ref Raja 16

 A declaration declares the name and type of an object.


 A definition causes storage to be allocated for the object.
 The same object may have many declarations, but there can be only one
definition.

In most cases, variable declarations are also definitions. However, by preceding a variable
name with the extern specifier, you can declare a variable without defining it. Thus, when you
need to refer to a variable that is defined in another part of your program, you can declare
that variable using extern.

• extern can also be applied to a function declaration, but doing so is redundant.

Static

Static variable:
Variables declared as static are permanent variables within their own function or file.

 They are stored in memory. Default initial value is zero.


 Their scope is local to the block in which the variable is defined &
 Value of the variable persists between different function calls.
 Avoid using static unless we need it. Because their values are kept in memory when
the variables are not active.

Static Local variables


If static variables were not allowed, globals would have to be used, opening the door to
possible side effects. An example of a function that benefits from a static local variable is a
number -series generator that produces a new value based on the previous one. You could use
a global variable to hold this value. However, each time the function is used in a program, you
would have to declare that global variable and make sure it did not conflict with any other
global variables already in place. The better solution is it declares the variable that holds the
generated number to be static.

Static Global Variable


Applying the specifier static to a global variable instructs the compiler to create a global
variable known only to the file in which it is declared. Thus, a static global variable has internal
linkage (as described under the extern statement). This means those even though the
variable is global, routines in other files have no knowledge of it and cannot alter its contents
directly, keeping it free from side effects. For the few situations where a local static cannot do
the job, you can create a small file that contains only the functions that need the global static
variable, separately compile that file, and use it without fear of side effects.

To review:

The names of local static variables are known only to the block of code in which they are
declared; the names of global static variables are known only to the file in which they reside.
In fact, you can even declare and use another variable with same name in your program (in
another file, of course). In essence, the static modifier permits variables that are known only
to the functions that need them, without unwanted side effects.
C_Lang_Ref Raja 17

By using static variables, you can hide portions of your program from other portions. This can
be a tremendous advantage when you are trying to manage a very large and complex
program.

Register

Originally, the register specifier requested that the compiler keep the value of a variable in a
register of the CPU rather than in memory, where normal variables are stored. This meant that
operations on a register variable could occur much faster than on a normal variable because
the register variable was actually held in the CPU and did not require a memory access to
determine or modify its value.

In fact, it is technically permissible for a compiler to ignore the register specifier altogether
and treat variables modified by it as if they were ''normal" variables, but this is seldom done in
practice.

• You can only apply the register specifier to local variables and to the formal
parameters in a function. Global register variables are not allowed.
• register variables are optimized for speed makes them ideal for control of or use
in loops.
• We can declare any number of variables as being of type register, but not all will
receive the same access speed optimization.
• The number of register variables optimized for speed allowed within any one code
block is determined by both the environment and the specific implementation of C.
• In C, you cannot obtain the address of a register variable by using the & operator
this makes sense because a register variable may be stored in a register of the CPU,
which is not usually addressable.

Variable Initialization

• Global and static local variables are initialized only at the start of the program.
• Local variables (excluding static local variables) are initialized each time the block in
which they are declared is entered.
• Local variables that are not initialized have unknown values before the first assignment
is made to them.
• Uninitialized global and static local variables are automatically set to zero.

Constants

Character constants are enclosed between single quotes. For example, 'a' and '%' are both
character constants. C defines both multibyte characters, which consist of one or more bytes,
and wide characters (which are usually 16 bits long). Multibyte and wide characters are used
primarily to represent languages that have large character sets. To specify a multibyte
character, enclose the characters within single quotes, for example, 'xy'. To specify a wide
character constant, precede the character with an L.

Operators
C_Lang_Ref Raja 18

There are four main classes of operators: arithmetic, relational , logical, and bitwise.

Assignment

Frequently in literature on C and in compiler error messages you will see these two terms:
lvalue and rvalue. Simply put, an lvalue is an object. If that object can occur on the left side
of an assignment statement, it is called a modifiable lvalue. Thus, for all practical purposes, a
modifiable lvalue means ''variable." The term rvalue refers to expressions on the right side of
an assignment and simply means the value of an expression.

Increment and Decrement Operators

Operators on the same level of precedence are evaluated by the compiler from left to right.

Relational and logical Operators

In the term relational operator, relational refers to the relationships that values can have with
one another. In the term logical operator, logical refers to the ways these relationships can be
connected. Both the relational and logical operators are lower in precedence than the
arithmetic operators.
C_Lang_Ref Raja 19

The Compile-Time Operator sizeof

sizeof is a unary compile-time operator that returns the length, in bytes, of the variable or
parenthesized type specifier that it precedes.

Bitwise Operators

We cannot use bitwise operations on float , double, long double, void , or other more complex
types.

Uses:

 Bitwise operations(&, |, ^) most often find application in device drivers— such as


modem programs, disk file routines, and printer routines— because the bitwise
operations can be used to mask off certain bits, such as parity. (The parity bit confirms
that the rest of the bits in the byte are unchanged. It is often the high-order bit in
each byte.)
 Bit-shift operations(<<, >>) can be very useful when you are decoding input from an
external device, such as a D/A converter, and reading status information. The bitwise
shift operators can also quickly multiply and divide integers.
 A shift right effectively divides a number by 2 and a shift left multiplies it by 2.
 The bitwise operators(~) are often used in cipher routines. If you want to make a disk
file appear unreadable, perform some bitwise manipulations.

Operator Precedence
C_Lang_Ref Raja 20

Some of the Operators Have the Wrong Precedence


C_Lang_Ref Raja 21

Associativity

The only use of associativity is to disambiguate an expression of two or more equalprecedence


operators.
• All assignment-operators have right associativity.
• operators with left associativity (such as the bitwise and's and or 's), the operands are
grouped from left to right.

Typedef
Don't bother with typedefs for structs. All they do is save you writing the word "struct", which
is a clue that you probably shouldn't be hiding anyway.
Use typedefs for:
• types that combine arrays, structs, pointers, or functions.
• portable types. When you need a type that's at least (say) 20-bits, make it a typedef.
Then when you port the code to different platforms, select the right type, short,
int, long, making the change in just the typedef, rather than in every
declaration.
C_Lang_Ref Raja 22

• casts. A typedef can provide a simple name for a complicated type cast. E.g.
typedef int (*ptr_to_int_fun)(void);
char * p;
= (ptr_to_int_fun) p;
Always use a tag in a structure definition, even if it's not needed. It will be later.

Function Pointers
Function Pointers are pointers, i.e. variables, which point to the address of a function.

 Function Pointers provide some extremely interesting, efficient and elegant


programming techniques. You can use them to replace switch/if-statements, to realize
your own late-binding or to implement callbacks.
 They are less error prone than normal pointers cause you will never allocate or de-
allocate memory with them.
 One aspect in the case of late-binding is runtime: If you call a virtual function, your
program has got to determine which one has got to be called. It does this using a V-
Table containing all the possible functions. This costs some time each call and maybe
you can save some time using function pointers instead of virtual functions.

The const function attribute

The const function attribute allows you to tell the compiler that the function can safely be
called fewer times than indicated in the source code. The language feature provides you with
an explicit way to help the compiler optimize code by indicating that the function does not
examine any values except its arguments and has no effects except for its return value.

const function attribute syntax

>>-__attribute__--((--+-const-----+--))------------------------><
'-__const__-'

The following kinds of functions should not be declared const:

• A function with pointer arguments which examines the data pointed to.
• A function that calls a non-const function.

Sorting and Searching Algorithms

Comparison sort:

A comparison sort is a type of sorting algorithm that only reads the list elements through a
single abstract comparison operation (often a "less than or equal to" operator) that determines
which of two elements should occur first in the final sorted list. The only requirement is that
the operator obey the three defining properties of a total order:
C_Lang_Ref Raja 23

1. if a ≤ b and b ≤ a then a = b (antisymmetry)


2. if a ≤ b and b ≤ c then a ≤ c (transitivity)
3. a ≤ b or b ≤ a (totalness or trichotomy)

Some of the most well-known comparison sorts include:

Quicksort, Heapsort, Merge sort, Introsort, Insertion sort, Selection sort, Bubble sort

Some examples of sorts which are not comparison sorts include:

• Radix sort (examines individual bits of keys)


• Counting sort (indexes using key values)
• Bucket sort (examines bits of keys)

Sorting Algorithms:

Exchange sorts Bubble , Quick , odd-even , comb , Gnome


Selection sorts Selection, Heap , smooth
Insertion sorts Insertion, Shell, Tree , Library, Patience
Merge sorts Merge , Strand
Non-Comparison sort Radix , Bucket , counting , Burst , pigeonhole
Others Topological, sorting network, Bitonic

Comparision:

Sorting Class Data Time Space Optimal


structure Complexity Complexity
Insertion Sorting Array О(n²) О(n) total, Not usually
O(1) auxiliary
Selection Sorting Array О(n²) О(n) total, Not usually
O(1) auxiliary
Bubble Sorting Array О(n²) О(n) total, No
O(1) auxiliary
Heap Sorting Array O(n log n) О(n) total, sometimes
O(1) auxiliary
Merge Sorting Array O(n log n) O(n) sometimes
Quick Sorting Varies O(n log n) Varies by sometimes
Implementation
Tree (fast Sorting Binary O(n log n)
sort) Search Tree
Shell Sorting

Binary Searching Array O(log n) O(1) yes


Hash Table Searching
Linear Searching
C_Lang_Ref Raja 24

Fibonacci Searching

Orders of common functions

Here is a list of classes of functions that are commonly encountered when analyzing
algorithms. All of these are as n increases to infinity. The slower-growing functions are listed
first. c is an arbitrary constant.

Notation Name Example


constant Determining if a number is even or odd
Amortized time per operation when using
inverse Ackermann
a disjoint-set (union-find) data structure
The find algorithm of Hopcroft and
iterated logarithmic
Ullman on a disjoint set
log log n
Finding an item in a sorted array with
logarithmic
the binary search algorithm
Deciding if n is prime with the AKS
polylogarithmic primality test
fractional power Searching in a kd-tree
Finding an item in an unsorted list,
linear
adding two n-digit numbers.
linearithmic,
Sorting a list with heapsort, performing
loglinear, or
an FFT
quasilinear
Sorting a list with insertion sort,
multiplying two n-digit numbers by
quadratic
simple algorithm, adding of two n×n
matrices.
Multiplying two n×n matrices by simple
cubic
algorithm.
polynomial, Finding the shortest path on a weighted
sometimes called digraph with the Floyd-War shall
algebraic algorithm
Ln[α,c],0 < α < 1 Factoring a number using the special or
L-notation
general number field sieve
Determining if two logical statements are
exponential, equivalent using brute force; finding the
sometimes called (exact) solution to the traveling
geometric salesman problem using dynamic
programming.
Determining if two logical statements are
factorial, equivalent; solving the traveling
sometimes called salesman problem via brute-force
combinatorial search; finding the determinant of a
matrix with expansion by minors.

Often used instead of to derive


n to the n simpler formulas for asymptotic
complexity.
C_Lang_Ref Raja 25

Finding a complete set of associative-


double exponential
commutative unifiers

C Questions:

1. Write a C program to find biggest of 4 numbers without using relational


operators?
main() {
int a, b, c, d, max;
printf("Enter numbersn");
scanf("%d %d %d %d", &a, &b, &c, &d);

max=a;
if(b/max)
max=b;
if(c/max)
max=c;
if(d/max)
max=d;

printf("%dn",max);
}

2. What is the benefit of using an enum rather than a #define constant?


The use of an enumeration constant (enum) has many advantages over using the traditional
symbolic constant style of #define.

Advantages:
1. lower maintenance requirement
2. improved program readability
3. better debugging capability.

1) The first advantage is that enumerated constants are generated automatically by the compiler.
Conversely, symbolic constants must be manually assigned values by the programmer. For
instance, if you had an enumerated constant type for error codes that could occur in your
program, your enum definition could look something like this:
enum Error_Code
{
OUT_OF_MEMORY,
INSUFFICIENT_DISK_SPACE,
LOGIC_ERROR,
FILE_NOT_FOUND
C_Lang_Ref Raja 26

};
In the preceding example, OUT_OF_MEMORY is automatically assigned the value of 0 (zero) by
the compiler because it appears first in the definition. The compiler then continues to
automatically assign numbers to the enumerated constants, making INSUFFICIENT_DISK_SPACE
equal to 1, LOGIC_ERROR equal to 2, and FILE_NOT_FOUND equal to 3, so on. If you were to
approach the same example by using symbolic constants, your code would look something like
this:
#define OUT_OF_MEMORY 0
#define INSUFFICIENT_DISK_SPACE 1
#define LOGIC_ERROR 2
#define FILE_NOT_FOUND 3
values by the programmer. Each of the two methods arrives at the same result: four constants
assigned numeric values to represent error codes. Consider the maintenance required, however,
if you were to add two constants to represent the error codes DRIVE_NOT_READY and
CORRUPT_FILE. Using the enumeration constant method, you simply would put these two
constants anywhere in the enum definition. The compiler would generate two unique values for
these constants. Using the symbolic constant method, you would have to manually assign two
new numbers to these constants. Additionally, you would want to ensure that the numbers you
assign to these constants are unique.
2) Another advantage of using the enumeration constant method is that your programs are more
readable and
thus can be understood better by others who might have to update your program later.
3) A third advantage to using enumeration constants is that some symbolic debuggers can print
the value of an enumeration constant. Conversely, most symbolic debuggers cannot
print the value of a symbolic constant. This can be an enormous help in debugging your
program, because if your program is stopped at a line that uses an enum, you can
simply inspect that constant and instantly know its value. On the other hand, because
most debuggers cannot print #define values, you would most likely have to search for
that value by manually looking it up in a header file.

3. Difference between reference and pointer?


In C there is no concept of a reference variable.
Only pointers exist. When you pass address of any variable to a function, it is termed as pass by
reference. Coz the formal parameter in function is the pointer that stores this address.

In C++, reference variables exist. A reference variable does not get any memory of its own. It
must be initialized when it is declared and it acts as an alias or synonym for the variable which
was used to initialize it.

There are two ways to pass the variable by reference in C++

1) Traditional pass by reference approach.

someFunction (&a, &b);

void someFunction (int *x, int *y)


{
}

2) using reference variables

someFunction (a, b);

void someFunction (int &x, int &y)


{
}

In this case no memory is allocated to x & y.


They act as alias for the memory locations to which a & b are referring.
any changes made to the values of x & y will cause values of a & b in the calling function
to change respectively.

Both approaches 1) and 2) are called pass by reference. But first approach uses pointers
and second approach uses reference variables.
C_Lang_Ref Raja 27

4. Printf function processing order R->L


main()
{
int a=6;
int *x=&a;
printf("%d %d %d " , a, ++a, a++);
}
o/p: 8 8 6

5. Pointer Definition ... Refer “Expert C Programming” book for better


understanding

int const *p; const int x;


const int *p; p=&x
pointer to a constant integer. P can be point to another variable but the
value of x can’t be changed.

int *const p; int x;


Constant pointer to a integer. P can’t be point to another integer, but
the value of the integer can be changed.

Char * const * (*f)();


f is a pointer to a function returning pointer to a read only
pointer-to-char

char * (*c[10])(int **p)


C is a array [10] of pointer to a function returning a pointer-to-char

The Precedence Rule


C_Lang_Ref Raja 28

solving a declaration using the Precedence Rule: char* const * (*next) ( ) ;

Then put it all together to read:


"next is a pointer to a function returning a pointer to a const pointer-to-char"
C_Lang_Ref Raja 29

6. Can we return an array from a function, not pointer to an array ?


We can’t directly return an array from a function. But we can do this by putting the
array in the structure and returning the structure.
Anyway return array from the function is a very rare case.

7. Difference between typedef and #define

• You can extend a macro typename with other type specifiers, but not a typedef 'd
typename.

#define X int
C_Lang_Ref Raja 30

Unsigned X abc; /*Works fine*/


typedef int X;
uns igned X abc; /*Illegal*/

• typedef 'd name provides the type for every declarator in a declaration.

#define X int*
X a,b; // a is pointer to a interger and b is just an int

typedef X int*
X a, b; //Both a and b are pointers to an int

8.

A 'C' Test: The 0x10 Best Questions for Would-be Embedded Programmers
Nigel Jones
Pencils up, everyone. Here's a test to identify potential embedded programmers or
embedded programmers with potential

A n obligatory and significant part of the recruitment process for embedded systems
programmers seems to be the "C test." Over the years, I have had to both take and prepare
such tests and, in doing so, have realized that these tests can be informative for both the
interviewer and interviewee. Furthermore, when given outside the pressure of an
interview situation, these tests can also be quite entertaining.
From the interviewee's perspective, you can learn a lot about the person who has written
or administered the test. Is the test designed to show off the writer's knowledge of the
minutiae of the ANSI standard rather than to test practical know-how? Does it test
ludicrous knowledge, such as the ASCII values of certain characters? Are the questions
heavily slanted towards your knowledge of system calls and memory allocation
strategies, indicating that the writer may spend his time programming computers instead
of embedded systems? If any of these are true, then I know I would seriously doubt
whether I want the job in question.
From the interviewer's perspective, a test can reveal several things about the candidate.
Primarily, you can determine the level of the candidate's knowledge of C. However, it's
also interesting to see how the person responds to questions to which they don't know the
answers. Do they make intelligent choices backed up with good intuition, or do they just
guess? Are they defensive when they are stumped, or do they exhibit a real curiosity
about the problem and see it as an opportunity to learn something? I find this information
as useful as their raw performance on the test.
With these ideas in mind, I have attempted to construct a test that is heavily slanted
towards the requirements of embedded systems. This is a lousy test to give to someone
seeking a job writing compilers! The questions are almost all drawn from situations I
have encountered over the years. Some of them are tough; however, they should all be
informative.
C_Lang_Ref Raja 31

This test may be given to a wide range of candidates. Most entry-level applicants will do
poorly on this test, while seasoned veterans should do very well. Points are not assigned
to each question, as this tends to arbitrarily weight certain questions. However, if you
choose to adapt this test for your own uses, feel free to assign scores.
Preprocessor
1. Using the #define statement, how would you declare a manifest constant that returns
the number of seconds in a year? Disregard leap years in your answer.
#define SECONDS_PER_YEAR
(60 * 60 * 24 * 365)UL

I'm looking for several things here:


• Basic knowledge of the #define syntax (for example, no semi-colon at the end, the
need to parenthesize, and so on)
• An understanding that the pre-processor will evaluate constant expressions for
you. Thus, it is clearer, and penalty-free, to spell out how you are calculating the
number of seconds in a year, rather than actually doing the calculation yourself
• A realization that the expression will overflow an integer argument on a 16-bit
machine-hence the need for the L, telling the compiler to treat the variable as a
Long
• As a bonus, if you modified the expression with a UL (indicating unsigned long),
then you are off to a great start. And remember, first impressions count!
2. Write the "standard" MIN macro-that is, a macro that takes two arguments and returns
the smaller of the two arguments.
#define MIN(A,B)
((A)
<
= (B) ? (A) : (B))

The purpose of this question is to test the following:


• Basic knowledge of the #define directive as used in macros. This is important
because until the inline operator becomes part of standard C, macros are the only
portable way of generating inline code. Inline code is often necessary in
embedded systems in order to achieve the required performance level
• Knowledge of the ternary conditional operator. This operator exists in C because
it allows the compiler to produce more optimal code than an if-then-else
sequence. Given that performance is normally an issue in embedded systems,
knowledge and use of this construct is important
• Understanding of the need to very carefully parenthesize arguments to macros
• I also use this question to start a discussion on the side effects of macros, for
example, what happens when you write code such as:
least = MIN(*p++, b);

3. What is the purpose of the preprocessor directive #error?


C_Lang_Ref Raja 32

Either you know the answer to this, or you don't. If you don't, see Reference 1. This
question is useful for differentiating between normal folks and the nerds. Only the nerds
actually read the appendices of C textbooks to find out about such things. Of course, if
you aren't looking for a nerd, the candidate better hope she doesn't know the answer.
Infinite loops
4. Infinite loops often arise in embedded systems. How does you code an infinite loop in
C?
There are several solutions to this question. My preferred solution is:

while(1)
{
É
}

Many programmers seem to prefer:

for(;;)
{
É
}

This construct puzzles me because the syntax doesn't exactly spell out what's going on.
Thus, if a candidate gives this as a solution, I'll use it as an opportunity to explore their
rationale for doing so. If their answer is basically, "I was taught to do it this way and I
haven't thought about it since," it tells me something (bad) about them.
A third solution is to use a goto :

Loop:
...
goto Loop;

Candidates who propose this are either assembly language programmers (which is
probably good), or else they are closet BASIC/FORTRAN programmers looking to get
into a new field.
Data declarations
5. Using the variable a, give definitions for the following:
a) An integer
b) A pointer to an integer
c) A pointer to a pointer to an integer
d) An array of 10 integers
e) An array of 10 pointers to integers
f) A pointer to an array of 10 integers
g) A pointer to a function that takes an integer as an argument and returns an integer
C_Lang_Ref Raja 33

h) An array of ten pointers to functions that take an integer argument and return an
integer
The answers are:
a) int a; // An integer
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an
integer
h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument
and return an integer
People often claim that a couple of these are the sorts of thing that one looks up in
textbooks-and I agree. While writing this article, I consulted textbooks to ensure the
syntax was correct. However, I expect to be asked this question (or something close to it)
when I'm being interviewed. Consequently, I make sure I know the answers, at least for
the few hours of the interview. Candidates who don't know all the answers (or at least
most of them) are simply unprepared for the interview. If they can't be prepared for the
interview, what will they be prepared for?
Static
6. What are the uses of the keyword static?
This simple question is rarely answered completely. Static has three distinct uses in C:
• A variable declared static within the body of a function maintains its value
between function invocations
• A variable declared static within a module, (but outside the body of a function) is
accessible by all functions within that module. It is not accessible by functions
within any other module. That is, it is a localized global
• Functions declared static within a module may only be called by other functions
within that module. That is, the scope of the function is localized to the module
within which it is declared
Most candidates get the first part correct. A reasonable number get the second part
correct, while a pitiful number understand the third answer. This is a serious weakness in
a candidate, since he obviously doesn't understand the importance and benefits of
localizing the scope of both data and code.
Const
7. What does the keyword const mean?
As soon as the interviewee says "const means constant," I know I'm dealing with an
amateur. Dan Saks has exhaustively covered const in the last year, such that every reader
of ESP should be extremely familiar with what const can and cannot do for you. If you
haven't been reading that column, suffice it to say that const means "read-only." Although
this answer doesn't really do the subject justice, I'd accept it as a correct answer. (If you
want the detailed answer, read Saks' columns-carefully!)
If the candidate gets the answer correct, I'll ask him these supplemental questions:
What do the following declarations mean?
C_Lang_Ref Raja 34

const int a;
int const a;
const int *a;
int * const a;
int const * a const;

The first two mean the same thing, namely a is a const (read-only) integer. The third
means a is a pointer to a const integer (that is, the integer isn't modifiable, but the pointer
is). The fourth declares a to be a const pointer to an integer (that is, the integer pointed to
by a is modifiable, but the pointer is not). The final declaration declares a to be a const
pointer to a const integer (that is, neither the integer pointed to by a, nor the pointer itself
may be modified). If the candidate correctly answers these questions, I'll be impressed.
Incidentally, you might wonder why I put so much emphasis on const, since it is easy to
write a correctly functioning program without ever using it. I have several reasons:
• The use of const conveys some very useful information to someone reading your
code. In effect, declaring a parameter const tells the user about its intended usage.
If you spend a lot of time cleaning up the mess left by other people, you'll quickly
learn to appreciate this extra piece of information. (Of course, programmers who
use const , rarely leave a mess for others to clean up.)
• const has the potential for generating tighter code by giving the optimizer some
additional information
• Code that uses const liberally is inherently protected by the compiler against
inadvertent coding constructs that result in parameters being changed that should
not be. In short, they tend to have fewer bugs
Volatile
8. What does the keyword volatile mean? Give three different examples of its use.
A volatile variable is one that can change unexpectedly. Consequently, the compiler can
make no assumptions about the value of the variable. In particular, the optimizer must be
careful to reload the variable every time it is used instead of holding a copy in a register.
Examples of volatile variables are:
• Hardware registers in peripherals (for example, status registers)
• Non-automatic variables referenced within an interrupt service routine
• Variables shared by multiple tasks in a multi-threaded application
Candidates who don't know the answer to this question aren't hired. I consider this the
most fundamental question that distinguishes between a C programmer and an embedded
systems programmer. Embedded folks deal with hardware, interrupts, RTOSes, and the
like. All of these require volatile variables. Failure to understand the concept of volatile
will lead to disaster.
On the (dubious) assumption that the interviewee gets this question correct, I like to
probe a little deeper to see if they really understand the full significance of volatile . In
particular, I'll ask them the following additional questions:
• Can a parameter be both const and volatile ? Explain.
• Can a pointer be volatile ? Explain.
• What's wrong with the following function?:
C_Lang_Ref Raja 35

int square(volatile int *ptr)


{
return *ptr * *ptr;
}

The answers are as follows:


• Yes. An example is a read-only status register. It is volatile because it can change
unexpectedly. It is const because the program should not attempt to modify it
• Yes, although this is not very common. An example is when an interrupt service
routine modifies a pointer to a buffer
• This one is wicked. The intent of the code is to return the square of the value
pointed to by *ptr . However, since *ptr points to a volatile parameter, the
compiler will generate code that looks something like this:

int square(volatile int *ptr)


{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}

Because it's possible for the value of *ptr to change unexpectedly, it is possible for a and
b to be different. Consequently, this code could return a number that is not a square! The
correct way to code this is:

long square(volatile int *ptr)


{
int a;
a = *ptr;
return a * a;
}

Bit manipulation
9. Embedded systems always require the user to manipulate bits in registers or variables.
Given an integer variable a, write two code fragments. The first should set bit 3 of a. The
second should clear bit 3 of a. In both cases, the remaining bits should be unmodified.
These are the three basic responses to this question:
• No idea. The interviewee cannot have done any embedded systems work
• Use bit fields. Bit fields are right up there with trigraphs as the most brain-dead
portion of C. Bit fields are inherently non-portable across compilers, and as such
guarantee that your code is not reusable. I recently had the misfortune to look at a
driver written by Infineon for one of their more complex communications chips. It
used bit fields and was completely useless because my compiler implemented the
bit fields the other way around. The moral: never let a non-embedded person
anywhere near a real piece of hardware!
• Use #defines and bit masks. This is a highly portable method and is the one that
should be used. My optimal solution to this problem would be:
C_Lang_Ref Raja 36

#define BIT3 (0x1


<
<
3)
static int a;

void set_bit3(void) {
a |= BIT3;
}
void clear_bit3(void) {
a &= ~BIT3;
}

Some people prefer to define a mask together with manifest constants for the set and
clear values. This is also acceptable. The element that I'm looking for is the use of
manifest constants, together with the |= and &= ~ constructs
Accessing fixed memory locations
10. Embedded systems are often characterized by requiring the programmer to access a
specific memory location. On a certain project it is required to set an integer variable at
the absolute address 0x67a9 to the value 0xaa55. The compiler is a pure ANSI compiler.
Write code to accomplish this task.
This problem tests whether you know that it is legal to typecast an integer to a pointer in
order to access an absolute location. The exact syntax varies depending upon one's style.
However, I would typically be looking for something like this:

int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;

A more obscure approach is:

*(int * const)(0x67a9) = 0xaa55;

Even if your taste runs more to the second solution, I suggest the first solution when you
are in an interview situation.
Interrupts
11. Interrupts are an important part of embedded systems. Consequently, many compiler
vendors offer an extension to standard C to support interrupts. Typically, this new
keyword is __interrupt. The following code uses __interrupt to define an interrupt service
routine (ISR). Comment on the code.
C_Lang_Ref Raja 37

__interrupt double compute_area


(double
radius)
{
double area = PI * radius *
radius;
printf("\nArea = %f", area);
return area;
}

This function has so much wrong with it, it's hard to know where to start:
• ISRs cannot return a value. If you don't understand this, you aren't hired
• ISRs cannot be passed parameters. See the first item for your employment
prospects if you missed this
• On many processors/compilers, floating-point operations are not necessarily re-
entrant. In some cases one needs to stack additional registers. In other cases, one
simply cannot do floating point in an ISR. Furthermore, given that a general rule
of thumb is that ISRs should be short and sweet, one wonders about the wisdom
of doing floating-point math here
• In a vein similar to the third point, printf() often has problems with reentrancy and
performance. If you missed points three and four, I wouldn't be too hard on you.
Needless to say, if you got these last two points, your employment prospects are
looking better and better
Code examples
12. What does the following code output and why?

void foo(void)
{
unsigned int a = 6;
int b = -20;
(a+b > 6) ? puts("> 6") :
puts("
<
= 6");
}

This question tests whether you understand the integer promotion rules in C-an area that I
find is very poorly understood by many developers. Anyway, the answer is that this
outputs "> 6." The reason for this is that expressions involving signed and unsigned types
have all operands promoted to unsigned types. Thus Ð20 becomes a very large positive
integer and the expression evaluates to greater than 6. This is a very important point in
embedded systems where unsigned data types should be used frequently (see Reference
2). If you get this one wrong, you are perilously close to not getting the job.
13. Comment on the following code fragment.

unsigned int zero = 0;


unsigned int compzero = 0xFFFF;
/*1's complement of zero */
C_Lang_Ref Raja 38

On machines where an int is not 16 bits, this will be incorrect. It should be coded:

unsigned int compzero = ~0;

This question really gets to whether the candidate understands the importance of word
length on a computer. In my experience, good embedded programmers are critically
aware of the underlying hardware and its limitations, whereas computer programmers
tend to dismiss the hardware as a necessary annoyance.
By this stage, candidates are either completely demoralized-or they're on a roll and
having a good time. If it's obvious that the candidate isn't very good, then the test is
terminated at this point. However, if the candidate is doing well, then I throw in these
supplemental questions. These questions are hard, and I expect that only the very best
candidates will do well on them. In posing these questions, I'm looking more at the way
the candidate tackles the problems, rather than the answers. Anyway, have fun...
Dynamic memory allocation
14. Although not as common as in non-embedded computers, embedded systems do still
dynamically allocate memory from the heap. What are the problems with dynamic
memory allocation in embedded systems?
Here, I expect the user to mention memory fragmentation, problems with garbage
collection, variable execution time, and so on. This topic has been covered extensively in
ESP , mainly by P.J. Plauger. His explanations are far more insightful than anything I
could offer here, so go and read those back issues! Having lulled the candidate into a
sense of false security, I then offer up this tidbit:
What does the following code fragment output and why?

char *ptr;
if ((ptr = (char *)malloc(0)) ==
NULL)
else
puts("Got a null pointer");
puts("Got a valid pointer");

This is a fun question. I stumbled across this only recently when a colleague of mine
inadvertently passed a value of 0 to malloc and got back a valid pointer! That is, the
above code will output "Got a valid pointer." I use this to start a discussion on whether
the interviewee thinks this is the correct thing for the library routine to do. Getting the
right answer here is not nearly as important as the way you approach the problem and the
rationale for your decision.
Typedef
15. Typedef is frequently used in C to declare synonyms for pre-existing data types. It is
also possible to use the preprocessor to do something similar. For instance, consider the
following code fragment:

#define dPS struct s *


typedef struct s * tPS;

The intent in both cases is to define dPS and tPS to be pointers to structure s. Which
method, if any, is preferred and why?
C_Lang_Ref Raja 39

This is a very subtle question, and anyone who gets it right (for the right reason) is to be
congratulated or condemned ("get a life" springs to mind). The answer is the typedef is
preferred. Consider the declarations:

dPS p1,p2;
tPS p3,p4;

The first expands to:

struct s * p1, p2;

which defines p1 to be a pointer to the structure and p2 to be an actual structure, which is


probably not what you wanted. The second example correctly defines p3 and p4 to be
pointers.
Obscure syntax
16. C allows some appalling constructs. Is this construct legal, and if so what does this
code do?

int a = 5, b = 7, c;
c = a+++b;

This question is intended to be a lighthearted end to the quiz, as, believe it or not, this is
perfectly legal syntax. The question is how does the compiler treat it? Those poor
compiler writers actually debated this issue, and came up with the "maximum munch"
rule, which stipulates that the compiler should bite off as big (and legal) a chunk as it can.
Hence, this code is treated as:

c = a++ + b;

Thus, after this code is executed, a = 6, b = 7, and c = 12.


If you knew the answer, or guessed correctly, well done. If you didn't know the answer
then I wouldn't consider this to be a problem. I find the greatest benefit of this question is
that it is good for stimulating questions on coding styles, the value of code reviews, and
the benefits of using lint.
Well folks, there you have it. That was my version of the C test. I hope you had as much
fun taking it as I had writing it. If you think the test is a good test, then by all means use it
in your recruitment. Who knows, I may get lucky in a year or two and end up being on
the receiving end of my own work.

Reference:

• The Complete Reference, 4th Ed


• Expert C programming - Deep C secrets
• www.Wikipedia.org

Potrebbero piacerti anche