Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
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
Pointer
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.
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.
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.
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:
Volatile Article
Have you experienced any of the following in your C or C++ embedded code?
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.
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:
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 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:
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.
A variable should be declared volatile whenever its value could change unexpectedly. In
practice, only three types of variables could change:
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:
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:
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:
loop:
C_Lang_Ref Raja 13
mov a, @ptr
bz loop
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 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:
void main()
...
while (!ext_rcvd)
// Wait
...
...
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
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.
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
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.
Static
Static variable:
Variables declared as static are permanent variables within their own function or file.
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.
Operators on the same level of precedence are evaluated by the compiler from left to right.
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
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:
Operator Precedence
C_Lang_Ref Raja 20
Associativity
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.
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.
>>-__attribute__--((--+-const-----+--))------------------------><
'-__const__-'
• A function with pointer arguments which examines the data pointed to.
• A function that calls a non-const function.
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
Quicksort, Heapsort, Merge sort, Introsort, Insertion sort, Selection sort, Bubble sort
Sorting Algorithms:
Comparision:
Fibonacci Searching
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.
C Questions:
max=a;
if(b/max)
max=b;
if(c/max)
max=c;
if(d/max)
max=d;
printf("%dn",max);
}
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.
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.
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
• You can extend a macro typename with other type specifiers, but not a typedef 'd
typename.
#define X int
C_Lang_Ref Raja 30
• 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
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)
{
É
}
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
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:
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
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;
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
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.
On machines where an int is not 16 bits, this will be incorrect. It should be coded:
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:
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;
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;
Reference: