Friday, May 8, 2009

C Interview Questions

C Interview Questions

Types of Functions/Sub-routines

Functions/Sub-routines

  • A function or subroutine or sub-program is a portion of code with in a larger program which performs a specific task and is independent of the remaining code.
  • A function is often coded so that it can be executed (called) several times and/or from several places during a single execution of the program.
  • Once the execution of the function got completed it branch back to the next instruction from where it is called.
  • Advantages of functions:
    • By using functions we can reduce the duplication of code in the program.
    • Enables the reuse of code from multiple programs.
    • Improves the Readability, Maintainability, Extensibility of the program.
    • Dividing the large programming task among various programmers.
    • Hiding Implementation details from the users.
    • Decomposing the complex programming task into simpler tasks.
  • Each function call creates a new entry called a stack frame, at the top of the stack; when function returns, its stack frame gets deleted from the stack. Each stack frame contains the private data of the corresponding call, which typically includes the procedure's parameters, internal variables and return address.
  • Advantage of storing private data on stack is that it allows recursive function calls, since each nested call will get separate instance of the stack frame on stack for private data.
  • Disadvantage of using this stack mechanism is that it includes incrementing and decrementing of stack pointer, and accessing local variables and parameters by frame-relative addresses, instead of absolute addresses.

Callback functions

  • A callback is a executable code that is passed as a argument to a function.
  • Normally callback functions are used to allow the low-level software layer to call a function defined in high-level software layer.
  • Form of callback varies among programming languages
    • C and C++ allow function pointers as arguments to other functions.
    • In Interpreted languages, routine name is passed as a argument to the called routine and routine will be executed using 'eval'
  • Callbacks are also frequently used as a means to handle exceptions arising within the low level function.

Re-entrant functions

  • A Re-entrant function is a function that can be re-entered while it is already running.
  • For a function to be a re-entrant
    • Should not Use any Static or global variables. (But can have Static Constant Data variables).
    • Should not call any Non-Re-entrant functions.
    • Should only use the local variables and the parameters passed.
    • Should not return address to Static or global Non constant data.
    • Should not modify its own code.
  • Recursive functions need to be Re-entrant.
  • Functions which are directly/indirectly called from Interrupt handler typically need to be re-entrant.

Recursive functions

  • A Recursive function is a function which calls by itself.
  • Using recursion, we split a complex problem into its single simplest case.
  • Recursive function should be a re-entrant function.

Interrupt Service Routine


Re-entrant Interrupt Handler

  • Is an Interrupt Handler which re-enables the interrupts early in the interrupt handler.
  • It reduces the interrupt latency.

Inline functions

  • An inline function is one for which the compiler copies the code from the function definition directly into the code of the calling function rather than creating a separate set of instructions in memory. Instead of transferring control to and from the function code segment, a modified copy of the function body may be substituted directly for the function call. In this way, the performance overhead of a function call is avoided. 
  • Using the inline specifier is only a suggestion to the compiler that an inline expansion can be performed; the compiler is free to ignore the suggestion.
  • An Inline function is a programming language construct to tell the compiler it should perform in-line expansion on a particular function.
  • Inline function is used to eliminate the time overhead when a function is called.
  • Static local variables are not allowed to be defined within the body of an inline function.
  • The most efficient way to code an inline function is to place the inline function definition in a header file, and then include the header in any file containing a call to the function which you would like to inline. Otherwise while linking "Unresolved Symbols" error may observe.
  • If both extern and inline keywords are used with function definition then the function will not be inlined.
  • Inline functions can include arguments, local variables, loops, call local functions, call built-in functions, call another inline functions, can return statements but not recursive functions.

Comparisons

Inline functions Vs Macros

  • MACRO invocations don't perform type checking, while inline functions do.
  • Expressions passed as arguments to inline functions are evaluated once. But In some cases, expressions passed as arguments to MACRO's can be evaluated more than once.
  • MACRO can't return something like function would do.
  • MACRO's are textual substitution. So they may result un-intended side effects and inefficiency due to evaluation of arguments and order of operations.
  • Compiler errors with MACRO's are often difficult to understand. 
  • Debugging information for inline code is usually more helpful than that of macro-expanded code.

Compiler Vs Interpreter

Compiler -- spends some time evaluating the entire program and then translates all the programming statements of a program into a machine language program, which is then executed at once. 

A compiler reads the whole source code and translates it into a complete machine code program to perform the required tasks which is output as a new file. This completely separates the source code from the executable file. The biggest advantage of this is that the translation is done once only and as a separate process. The program that is run is already translated into machine code so is much faster in execution. The disadvantage is that you cannot change the program without going back to the original source code, editing that and recompiling.

Interpreter -- translates interactively each programming statement into an immediately usable machine language instruction. Although an interpreter slows down the execution speed of a program somewhat, it does not require extra steps to compile and link like a compiler. 

An interpreter reads the source code one instruction or line at a time, converts this line into machine code and executes it. The machine code is then discarded and the next line is read. The advantage of this is it's simple and you can interrupt it while it is running, change the program and either continue or start again. The disadvantage is that every line has to be translated every time it is executed, even if it is executed many times as the program runs. Because of this interpreters tend to be slow.


Compiler characteristics:

  • spends a lot of time analyzing and processing the program
  • the resulting executable is some form of machine- specific binary code
  • the computer hardware interprets (executes) the resulting code
  • program execution is fast

Interpreter characteristics:

  • relatively little time is spent analyzing and processing the program
  • the resulting code is some sort of intermediate code
  • the resulting code is interpreted by another program
  • program execution is relatively slow


Monday, June 30, 2008

Into, but not out of, the void
C and C++ both use void * as the generic data pointer type, but they treat it differently. Here's some insight into why.
Embedded.com

The early dialect of C described by Kernighan and Ritchie (Kernighan, Brian W. and Ritchie, Dennis M., The C Programming Language. Prentice Hall, 1978.) (often called "Classic C") didn't include the keyword void, and therefore had no type void *. Lacking void *, Classic C programs tended to use char * as the type for a "generic" data pointer--a pointer that can point to any data object. Unfortunately, this increased the need to use cast expressions.

For example, Classic C didn't include the now standard memory management functions malloc and free. On page 175 of their book, Kernighan and Ritchie implemented their own allocation function, alloc, which they defined as:

char *alloc(bytes)
unsigned bytes;
{
...
}

This function definition is in the old, non-prototyped style of Classic C. In Standard C, the equivalent declaration would be:

char *alloc(unsigned bytes);

This alloc function returns the address of the allocated storage as a pointer of type char *. However, they designed alloc to allocate storage for any type of object, not just char or array of char. When allocating storage for something other than a char (or array thereof), the program must convert the char * result into a pointer to the intended type of the allocated object--a conversion that requires a cast. Kernighan and Ritchie used casts to convert the result of calling alloc to something other than char *, in functions such as:

struct tnode *talloc()
{
return ((struct tnode *)alloc(sizeof(struct tnode)));
}

The Classic C implementation of free posed a similar problem. Kernighan and Ritchie defined their version of free as:

free(ap)
char *ap
{
...
}

Absent the keyword void, they omitted the return type to indicate that the function returns nothing. In truth, the return type defaulted to int. In Standard C, the equivalent declaration would be:

int free(char *ap);

Calling free to deallocate anything other than a char or array of chars required a cast, as in:

int *ip;
...
free((char *)ip);

Standard C eliminated the need for these casts by embracing void * as the generic data pointer type. For any object type T, a C program can convert a T * to or from a void * without using a cast.

For example, Standard C declares malloc and free as:

void *malloc(size_t size);
void free(void *p);

so that for any object type T:

T *p;
...
p = malloc(sizeof(T));
...
free(p);

should compile in C without complaint. No casting needed.

An unfortunate consequence of the C's pointer conversion rules is that they permit accidental conversions between unrelated pointer types. For example:

double *pd;
long *pl;
void *pv;
...
pv = pd; // compiles in C
...
pl = pv; // compiles in C

Although pl is declared to point to a long, it now points to a double. Accessing *pl would produce undefined behavior.

C++ reduces the chances of such mishaps by applying slightly more restrictive conversion rules: for any object type T, a C++ program can convert a T * to a void *, but not a void * to a T *. Here's the rationale.

Converting from T * to void * is generally safe because the program can do little harm with a void * object. For example, if pv has type void *, compilers reject attempts to access the object to which pv points. Expressions such as *pv, pv[i], ++pv, and --pv simply don't compile.

Although converting a pointer such as pv from void * to T * (for any non-void T) is itself safe, it throws the door wide open for future unsafe operations, such as accidentally converting an object into something that it really isn't.

Thus, for any (non-void) object type T, C++ requires a cast when converting from void * to T *, as in:

pv = pd;            // compiles in C and C++
...
pl = pv; // compiles in C, but not in C++
pl = (long *)pv; // compiles in C and C++

Using a cast enables the code to compile, but doesn't eliminate the undefined behavior. In this case, the cast really is an indicator that the operation is unsafe.

Thanks to Steve Adamczyk of Edison Design Group (www.edg.com) for helping me identify parts of the standard relevant to this discussion.

Dan Saks is president of Saks & Associates, a C/C++ training and consulting company. For more information about Dan Saks, visit his website at www.dansaks.com. Dan also welcomes your feedback: e-mail him at dsaks@wittenberg.edu. For more information about Dan click here .


The yin and yang of dynamic allocation
Despite its risks, dynamic memory allocation is a valuable facility that you shouldn't blindly disregard.
Embedded.com

A few months back, I explained that each object in C and C++ has one of the following three storage durations: static, automatic, and dynamic:1

• An object with static storage duration has storage that is allocated at program startup and remains allocated at the same location until the program terminates.

• An object with automatic storage duration has storage that's allocated upon entry into a block (typically a function body) and deallocated upon exit from that block.

• An object with dynamic storage duration has storage that's allocated when the program calls an allocation function, such as malloc in C or an operator new in C++. The object's storage lasts until the program passes the address of that object to a corresponding deallocation function, such as free in C or an operator delete in C++.

While it's nearly impossible to write a useful program without using both static and automatic allocation, programs can--and do--manage to get by without any dynamic allocation. This month, I'll look at the case for and against using dynamic allocation.

Quick takes
Static storage allocation typically has no run-time cost. The initialization of statically allocated objects may take place at run time, but the allocation itself usually takes no time. (Of course, the allocation still takes up memory space.) Unfortunately, in applications that juggle diverse collections of objects, big and small, using static storage squanders memory and imposes rather arbitrary restrictions on program behavior.

For example, suppose your application uses two different-sized regions of memory, say, buffers and blocks. Your target system might have enough memory to statically allocate 100 buffers or 250 blocks. Should you carve up space for 50 buffers and 125 blocks, or is a typical application more likely to need up to 60 buffers but only 100 blocks? And what about those occasions when program needs 80 buffers but only 50 blocks? Tough luck?

The run-time cost of automatic storage allocation is usually pretty low--often only an instruction or two on each function entry and exit. On microcontrollers with limited stack space, the cost can be higher. Automatic storage uses memory very efficiently, but it's useless for objects with lifetimes that persist beyond function calls.

By comparison, dynamic storage allocation is much slower than either static or automatic allocation. A call to malloc (in C or C++) or operator new (in C++) may execute tens and occasionally hundreds of instructions in a quest for storage of the requested size. Deallocating storage by calling free (in C or C++) or operator delete (in C++) often requires comparable effort.

You do get something for that effort: dynamic storage allocation uses memory much more flexibly than static storage allocation. Using dynamic storage may add a little overhead to the storage for each object, so instead of 100 statically allocated buffers, you might have room for only 99 or 98 dynamically allocated buffers. But now any run of the program can have up to 98 buffers and no blocks, or up to 123 blocks and no buffers, or any balance of buffers and blocks within those limits that doesn't exceed total available memory.

Risky business
Dynamic memory allocation brings with it a number of risks. Some developers, especially for embedded systems, find these risks too great to accept and follow the advice of the MISRA-C guidelines. (MISRA is the Motor Industry Software Reliability Association in the UK). According to their guidelines:2

Rule 20.4 (required): Dynamic heap memory allocation shall not be used.

This precludes the use of the functions calloc, malloc, realloc, and free.

There is a whole range of unspecified, undefined and implementation-defined behaviour associated with dynamic memory allocation, as well as a number of other potential pitfalls. Dynamic heap memory allocation may lead to memory leaks, data inconsistency, memory exhaustion, non-deterministic behaviour.

Although I have some quibbles with the wording, it covers most of the major concerns. These concerns also apply to operator new and operator delete in C++. Let's look at them (and my quibbles) in some detail.

Undefined behavior is what a program exhibits when it does something erroneous that the compiler and run-time system need not detect (often because they just can't). For example, freeing a pointer to storage that's already been freed, or that never was allocated, produces undefined behavior. The undefined behavior typically corrupts the heap--the data structures that support the dynamic memory. The corruption in the heap usually spills over into other data and run-time structures, and the program eventually crashes.

Implementation-defined and unspecified behaviors are behaviors of correct programs that may vary with different platforms. The difference between implementation-defined behavior and unspecified behavior is simply that each compiler must document its implementation-defined behaviors but not its unspecified behaviors. (You can find more about undefined, unspecified, and implementation-behaviors in a column I wrote several years ago.)3 For example, when a program calls malloc to request zero bytes, the return value of the call is implementation-defined. It might be a null pointer, or it might actually point to allocated storage. If it does point to storage, the amount of that storage is unspecified.

In a sense, memory leaks are not really a problem. Memory exhaustion is. A leak here or there doesn't really cause any harm. The accumulation of many leaks over time becomes a problem when it exhausts available memory and deprives the program of resources it can't survive without. Listing memory leaks and memory exhaustion as separate ills may be redundant.

I'm not sure what the MISRA guideline means by data inconsistency. I looked it up online and found no consistent usage. Some articles use it as another term for heap corruption. Others use it to describe the corruption of values in individual dynamically allocated objects. Whatever data inconsistency is, it's probably just another manifestation of undefined behavior, and I'm not sure it deserves special mention.

Common implementations of malloc and operator new have nondeterministic behavior. That is, the execution time of an allocation request can vary, sometimes markedly, with the size of the request and the current state of the heap. This nondeterministic behavior poses serious problems in real-time systems that require reliable upper bounds on the duration of certain events.

If you've never seen how malloc and free are implemented, take a look at Chapter 8 of Kernighan and Ritchie's classic introduction to C.4 They present a complete, working implementation of malloc and free. It's less than five pages of code and prose, and well worth the time to read.

Many real-time operating systems (RTOSes) offer alternatives to the standard malloc and free that allocate and free fixed-sized blocks from a larger, user-provided memory region, often called a pool. Writing pool-based allocation functions with short, deterministic execution times is a fairly easy thing to do.

The MISRA guideline failed to mention fragmentation, and maybe it should have. Fragmentation is what you get after many allocations and deallocations for different-sized chunks of storage leave many odd-sized chunks in the heap that the program can't seem to use. Depending on the allocation scheme, fragmentation might increase allocation times and thus degrade run-time performance. It can also lead to memory exhaustion. Using pool-based allocation schemes often reduces fragmentation.

Still worth a look
Despite these potential hazards, some problems are just much easier to solve with dynamic memory allocation rather than static allocation, so you shouldn't dismiss dynamic allocation out of hand. Some systems manage to use dynamic memory in limited ways that accrue some benefits without taking on unacceptable risks. One approach is to use dynamic allocation to acquire resources only during program startup. This allows the application greater freedom to allocate memory to objects as needed, but avoids problems with fragmentation, leaks, and nondeterminism during steady-state execution.

Understanding the ins and outs of dynamic memory allocation is especially worthwhile for C++ programmers. C++ offers many facilities--most notably classes with constructors and destructors--that dramatically diminish the incidence of memory leaks. C++ also lets you define an operator new for each class, providing a convenient and efficient packaging for deterministic, fixed-sized allocation schemes. You can even use operator new to place device registers in memory-mapped locations, as I did in a recent project.

I'll delve more into dynamic memory allocation in the coming months.

A correction
In my March column on linkage, Table 2 summarized how C and C++ determine linkage and storage duration for an object from the storage class specifier and the scope of the object's declaration.5 The entry for objects declared at class scope with the storage class specifier static (usually known as static data members) erroneously indicated they have internal linkage. They actually have external linkage.

Endnotes:
1. Saks, Dan, "Storage class specifiers and storage duration," Embedded Systems Design, January, 2008, p. 9.

2. MISRA-C 2004: Guidelines for the use of the C language in critical systems. Motor Industry Research Association, October 2004.

3. Saks, Dan, "As Precise as Possible," Embedded Systems Programming, April, 2002, p. 43 .

4. Kernighan, Brian W. and Ritchie, Dennis M., The C Programming Language, 2nd. ed. Prentice Hall, 1988.

5. Saks, Dan, "Linkage in C and C++," Embedded Systems Design, March, 2008, p. 9.


Wednesday, June 13, 2007

Customizing your shell prompt : tcsh and zsh shells

Shell prompts are extremely customizable. You can customize them in two ways: (1) by using command characters that print out special things and (2) by using colors. Also, customizing the prompt is different in different shells; I'm going to cover tcsh and zsh here. bash is the other popular shell, but I think it sucks, and I never use it, so I'm not going to waste any time on it.

tcsh

I'll start with tcsh, since it's the default BSD and Mac OS X shell. Here's a very simple prompt:

hostname%

That prompt is created with the following command:

setenv PROMPT '%m%# '

The %m is called a formatting sequence, and it is expanded to the hostname of your computer when tcsh outputs your prompt. Similarly, %# equals '>' (or the first character of the promptchars shell variable) if you're a normal user, or '#' (or the second character of promptchars) if you're root. Any letter with a % before it will be treated as a formatting sequence, so if you want to print a % sign, use %%. (Quick side note: you want the extra space at the end, or else the input will be squashed up against the prompt, and it's ugly and hard to read.) A popular prompt is the following:

Formatted:
[user@hostname:/current/path]%


Code
[%n@%m:%c]%#

%n is the username and %c is the current path. Instead of going through millions of examples illustrating all the different kinds of prompts you can have, I'm just going to include the complete list of formatting sequences from the tcsh(1) manpage:

%/  The current working directory.
%~ The current working directory, but with one's
home directory represented by `~' and other
users' home directories represented by `~user'
as per Filename substitution. `~user' substi-
tution happens only if the shell has already
used `~user' in a pathname in the current ses-
sion.
%c[[0]n], %.[[0]n]
The trailing component of the current working
directory, or n trailing components if a digit
n is given. If n begins with `0', the number
of skipped components precede the trailing
component(s) in the format `/<skipped>trail-
ing'. If the ellipsis shell variable is set,
skipped components are represented by an
ellipsis so the whole becomes `...trailing'.
`~' substitution is done as in `%~' above, but
the `~' component is ignored when counting
trailing components.
%C Like %c, but without `~' substitution.
%h, %!, !
The current history event number.
%M The full hostname (e.g. jaguar.apple.com).
%m The hostname up to the first `.' (e.g. jaguar).
%S (%s)
Start (stop) standout (reverse) mode.
%B (%b)
Start (stop) boldfacing mode.
%U (%u)
Start (stop) underline mode.
%t, %@
The time of day in 12-hour AM/PM format.
%T Like `%t', but in 24-hour format (but see the
ampm shell variable).
%p The `precise' time of day in 12-hour AM/PM
format, with seconds.
%P Like `%p', but in 24-hour format (but see the
ampm shell variable).
c c is parsed as in bindkey.
^c c is parsed as in bindkey.
%% A single `%'.
%n The user name.
%d The weekday in `Day' format.
%D The day in `dd' format.
%w The month in `Mon' format.
%W The month in `mm' format.
%y The year in `yy' format.
%Y The year in `yyyy' format.
%l The shell's tty.
%L Clears from the end of the prompt to end of
the display or the end of the line.
%$ Expands the shell or environment variable name
immediately after the `$'.
%# `>' (or the first character of the promptchars
shell variable) for normal users, `#' (or the
second character of promptchars) for the supe-
ruser.
%{string%}
Includes string as a literal escape sequence.
It should be used only to change terminal
attributes and should not move the cursor
location. This cannot be the last sequence in
prompt.
%? The return code of the command executed just
before the prompt.
%R In prompt2, the status of the parser. In
prompt3, the corrected string. In history,
the history string.

Next, on to color. This directly builds on the previous section by adding color escape sequences to the formatting sequences you can use. The following code colors the hostname red:

%{033[31m%}%m%{033[0m%}

The '31' and the %m have been bolded above because those are the only things you change. The 31 is the color code, and the %m is obviously where you put whatever you want to color. The rest of it is the same for every color coding; the beginning starts coloring, and the stuff afterwards stops coloring ('0' switches it back to default text color). You can use the following color codes:

30 - black
31 - red
32 - green
33 - yellow
34 - blue
35 - magenta
36 - cyan
37 - white

Not quite the same as a full Photoshop palette, but you can make a pretty nice prompt with it. Also, you can modify it further by including another control char:

%{033[1;31m%}%m%{033[0m%}

In this case, the '1' will make the following color bold. You can use the following modifiers:

0 - normal
1 - bold
2 - normal again
3 - background color
4 - underline the text
5 - blinking

You can also specify both a foreground and a background color. Use the following syntax to get (fairly hideous looking) Christmas colors:

%{033[2;41;32m%}%m%{033[0m%}

The '41' is the background color, and the '31' is the foreground color. The background color codes are the same as the foreground color codes, except they're 40-47 instead of 30-37.

Finally, you can also have a right-justified prompt. This is stored in the RPROMPT variable, and formatted in exactly the same way as PROMPT. People often like putting the time (%p) in RPROMPT.


zsh

zsh is customized in an extremely similar way. You still use formatting sequences, although some of them are a little different. The color codes are the same, although the color escape sequence is a little different. Other than that, it's pretty easy to move back and forth between a zsh and a tcsh prompt. The formatting sequences are the following (from zshmisc(1)):

%%     A `%'.

%) A `)'.

%d
%/ Present working directory ($PWD). If an integer
follows the `%', it specifies a number of trailing
components of $PWD to show; zero means the whole
path. A negative integer specifies leading compo-
nents, i.e. %-1d specifies the first component.

%~ As %d and %/, but if $PWD has a named directory as
its prefix, that part is replaced by a `~' followed
by the name of the directory. If it starts with
$HOME, that part is replaced by a `~'.

%h
%! Current history event number.

%L The current value of $SHLVL.

%M The full machine hostname.

%m The hostname up to the first `.'. An integer may
follow the `%' to specify how many components of
the hostname are desired. With a negative integer,
trailing components of the hostname are shown.

%S (%s)
Start (stop) standout mode.

%U (%u)
Start (stop) underline mode.

%B (%b)
Start (stop) boldface mode.

%t
%@ Current time of day, in 12-hour, am/pm format.

%T Current time of day, in 24-hour format.

%* Current time of day in 24-hour format, with sec-
onds.

%n $USERNAME.

%N The name of the script, sourced file, or shell
function that zsh is currently executing, whichever
was started most recently. If there is none, this
is equivalent to the parameter $0. An integer may
follow the `%' to specify a number of trailing path
components to show; zero means the full path. A
negative integer specifies leading components.

%i The line number currently being executed in the
script, sourced file, or shell function given by
%N. This is most useful for debugging as part of
$PS4.

%w The date in day-dd format.

%W The date in mm/dd/yy format.

%D The date in yy-mm-dd format.

%D{string}
string is formatted using the strftime function.
See strftime(3) for more details. Three additional
codes are available: %f prints the day of the
month, like %e but without any preceding space if
the day is a single digit, and %K/%L correspond to
%k/%l for the hour of the day (24/12 hour clock) in
the same way.

%l The line (tty) the user is logged in on without
/dev/ prefix. If name starts with /dev/tty this is
stripped.

%y The line (tty) the user is logged in on without
/dev/ prefix. It does not treat /dev/tty* spe-
cially.

%? The return code of the last command executed just
before the prompt.

%_ The status of the parser, i.e. the shell constructs
(like `if' and `for') that have been started on the
command line. If given an integer number that many
strings will be printed; zero or negative or no
integer means print as many as there are. This is
most useful in prompts PS2 for continuation lines
and PS4 for debugging with the XTRACE option; in
the latter case it will also work non-interac-
tively.

%E Clears to end of line.

%# A `#' if the shell is running with privileges, a
`%' if not. Equivalent to `%(!.#.%%)'. The defi-
nition of `privileged', for these purposes, is that
either the effective user ID is zero, or, if
POSIX.1e capabilities are supported, that at least
one capability is raised in either the Effective or
Inheritable capability vectors.

%v The value of the first element of the psvar array
parameter. Following the `%' with an integer gives
that element of the array. Negative integers count
from the end of the array.

%{...%}
Include a string as a literal escape sequence. The
string within the braces should not change the cur-
sor position. Brace pairs can nest.

%(x.true-text.false-text)
Specifies a ternary expression. The character fol-
lowing the x is arbitrary; the same character is
used to separate the text for the `true' result
from that for the `false' result. This separator
may not appear in the true-text, except as part of
a %-escape sequence. A `)' may appear in the
false-text as `%)'. true-text and false-text may
both contain arbitrarily-nested escape sequences,
including further ternary expressions.

The left parenthesis may be preceded or followed by
a positive integer n, which defaults to zero. A
negative integer will be multiplied by -1. The
test character x may be any of the following:

c
.
~ True if the current path, with prefix
replacement, has at least n elements.
/
C True if the current absolute path has at
least n elements.
t True if the time in minutes is equal to n.
T True if the time in hours is equal to n.
d True if the day of the month is equal to n.
D True if the month is equal to n (January =
0).
w True if the day of the week is equal to n
(Sunday = 0).
? True if the exit status of the last command
was n.
# True if the effective uid of the current
process is n.
g True if the effective gid of the current
process is n.
l True if at least n characters have already
been printed on the current line.
L True if the SHLVL parameter is at least n.
S True if the SECONDS parameter is at least n.
v True if the array psvar has at least n ele-
ments.
_ True if at least n shell constructs were
started.
! True if the shell is running with privi-
leges.

%<string<
%>string>
%[xstring]
Specifies truncation behaviour for the remainder of
the prompt string. The third, deprecated, form is
equivalent to `%xstringx', i.e. x may be `<' or
`>'. The numeric argument, which in the third form
may appear immediately after the `[', specifies the
maximum permitted length of the various strings
that can be displayed in the prompt. The string
will be displayed in place of the truncated portion
of any string; note this does not undergo prompt
expansion.

The forms with `<' truncate at the left of the
string, and the forms with `>' truncate at the
right of the string. For example, if the current
directory is `/home/pike', the prompt `%8<..<%/'
will expand to `..e/pike'. In this string, the
terminating character (`<', `>' or `]'), or in fact
any character, may be quoted by a preceding `';
note when using print -P, however, that this must
be doubled as the string is also subject to stan-
dard print processing, in addition to any back-
slashes removed by a double quoted string: the
worst case is therefore `print -P "%<\\<<..."'.

If the string is longer than the specified trunca-
tion length, it will appear in full, completely
replacing the truncated string.

The part of the prompt string to be truncated runs
to the end of the string, or to the end of the next
enclosing group of the `%(' construct, or to the
next truncation encountered at the same grouping
level (i.e. truncations inside a `%(' are sepa-
rate), which ever comes first. In particular, a
truncation with argument zero (e.g. `%<<') marks
the end of the range of the string to be truncated
while turning off truncation from there on. For
example, the prompt '%10<...<%~%<<%# ' will print a
truncated representation of the current directory,
followed by a `%' or `#', followed by a space.
Without the `%<<', those two characters would be
included in the string to be truncated.

%c
%.
%C Trailing component of $PWD. An integer may follow
the `%' to get more than one component. Unless
`%C' is used, tilde contraction is performed first.
These are deprecated as %c and %C are equivalent to
%1~ and %1/, respectively, while explicit positive
integers have the same effect as for the latter two
sequences.

As you can likely tell, zsh has some absurdly powerful prompt characters, but reasonably simple prompts are extremely similar to their tcsh counterparts:

tcsh:
[%n@%m:%c]%#

zsh:
[%n@%m:%/]%#

Not such a huge difference. The color sequence is slightly different, so use this kind of formatting:

%{e[0;31m%}%m%{e[0m%}

Again, the bold parts are the parts you edit. Also, you can customize RPROMPT in the same way.

Thursday, February 22, 2007

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.

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?

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
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?

 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?:

 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:

 #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.

 __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 */  

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?

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.

Nigel Jones is a consultant living in Maryland. When not underwater, he can be found slaving away on a diverse range of embedded projects. He enjoys hearing from readers and can be reached at NAJones@compuserve.com .

References

  • Jones, Nigel, "In Praise of the #error directive," Embedded Systems Programming, September 1999, p. 114.
  • Jones, Nigel, " Efficient C Code for Eight-bit MCUs ," Embedded Systems Programming, November 1998, p. 66.

Byte Alignment and Ordering

Realtime systems consist of multiple processors communicating with each other via messages. For message communication to work correctly, the message formats should be defined unambiguously. In many systems this is achieved simply by defining C/C++ structures to implement the message format. Using C/C++ structures is a simple approach, but it has its own pitfalls. The problem is that different processors/compilers might define the same structure differently, thus causing incompatibility in the interface definition.

There are two reasons for these incompatibilities:

  • Byte Alignment Restrictions
  • Byte Ordering

Byte Alignment Restrictions

Most 16-bit and 32-bit processors do not allow words and long words to be stored at any offset. For example, the Motorola 68000 does not allow a 16 bit word to be stored at an odd address. Attempting to write a 16 bit number at an odd address results in an exception.

Why Restrict Byte Alignment?

32 bit microprocessors typically organize memory as shown below. Memory is accessed by performing 32 bit bus cycles. 32 bit bus cycles can however be performed at addresses that are divisible by 4. (32 bit microprocessors do not use the address lines A1 and A0 for addressing memory).

The reasons for not permitting misaligned long word reads and writes are not difficult to see. For example, an aligned long word X would be written as X0, X1, X2 and X3. Thus the microprocessor can read the complete long word in a single bus cycle. If the same microprocessor now attempts to access a long word at address 0x000D, it will have to read bytes Y0, Y1, Y2 and Y3. Notice that this read cannot be performed in a single 32 bit bus cycle. The microprocessor will have to issue two different reads at address 0x100C and 0x1010 to read the complete long word. Thus it takes twice the time to read a misaligned long word.

Byte 0 Byte 1 Byte 2 Byte 3
0x1000
0x1004 X0 X1 X2 X3
0x1008
0x100C Y0 Y1 Y2
0x1010 Y3

Compiler Byte Padding

Compilers have to follow the byte alignment restrictions defined by the target microprocessors. This means that compilers have to add pad bytes into user defined structures so that the structure does not violate any restrictions imposed by the target microprocessor.

The compiler padding is illustrated in the following example. Here a char is assumed to be one byte, a short is two bytes and a long is four bytes.

User Defined Structure

struct Message
{
short opcode;
char subfield;
long message_length;
char version;
short destination_processor;
};

Actual Structure Definition Used By the Compiler

struct Message
{
short opcode;
char subfield;
char pad1; // Pad to start the long word at a 4 byte boundary
long message_length;
char version;
char pad2; // Pad to start a short at a 2 byte boundary
short destination_processor;
char pad3[4]; // Pad to align the complete structure to a 16 byte boundary
};

In the above example, the compiler has added pad bytes to enforce byte alignment rules of the target processor. If the above message structure was used in a different compiler/microprocessor combination, the pads inserted by that compiler might be different. Thus two applications using the same structure definition header file might be incompatible with each other.

Thus it is a good practice to insert pad bytes explicitly in all C-structures that are shared in a interface between machines differing in either the compiler and/or microprocessor.

General Byte Alignment Rules

The following byte padding rules will generally work with most 32 bit processor. You should consult your compiler and microprocessor manuals to see if you can relax any of these rules.

  • Single byte numbers can be aligned at any address
  • Two byte numbers should be aligned to a two byte boundary
  • Four byte numbers should be aligned to a four byte boundary
  • Structures between 1 and 4 bytes of data should be padded so that the total structure is 4 bytes.
  • Structures between 5 and 8 bytes of data should be padded so that the total structure is 8 bytes.
  • Structures between 9 and 16 bytes of data should be padded so that the total structure is 16 bytes.
  • Structures greater than 16 bytes should be padded to 16 byte boundary.

Structure Alignment for Efficiency

Sometimes array indexing efficiency can also determine the pad bytes in the structure. Note that compilers index into arrays by calculating the address of the indexed entry by the multiplying the index with the size of the structure. This number is then added to the array base address to obtain the final address. Since this operation involves a multiply, indexing into arrays can be expensive. The array indexing can be considerably speeded up by just making sure that the structure size is a power of 2. The compiler can then replace the multiply with a simple shift operation.


Byte Ordering

Microprocessors support big-endian and little-endian byte ordering. Big-endian is an order in which the "big end" (most significant byte) is stored first (at the lowest address). Little-endian is an order in which the "little end" (least significant byte) is stored first.

The table below shows the representation of the hexadecimal number 0x0AC0FFEE on a big-endian and little-endian machine. The contents of memory locations 0x1000 to 0x1003 are shown.

0x1000 0x1001 0x1002 0x1003
Big-endian 0x0A 0xC0 0xFF 0xEE
Little-endian 0xEE 0xFF 0xC0 0x0A

Why Different Byte Ordering?

This is a difficult question. There is no logical reason why different microprocessor vendors decided to use different ordering schemes. Most of the reasons are historical. For example, Intel processors have traditionally been little-endian. Motorola processors have always been big-endian.

The situation is actually quite similar to that of Lilliputians in Gulliver's Travels. Lilliputians were divided into two groups based on the end from which the egg should be broken. The big-endians preferred to break their eggs from the larger end. The little-endians broke their eggs from the smaller end.

Conversion Routines

Routines to convert between big-endian and little-endian formats are actually quite straight forward. The routines shown below will convert from both ways, i.e. big-endian to little-endian and back.

Big-endian to Little-endian conversion and back

short convert_short(short in)
{
short out;
char *p_in = (char *) &in;
char *p_out = (char *) &out;
p_out[0] = p_in[1];
p_out[1] = p_in[0];
return out;
}

long convert_long(long in)
{
long out;
char *p_in = (char *) &in;
char *p_out = (char *) &out;
p_out[0] = p_in[3];
p_out[1] = p_in[2];
p_out[2] = p_in[1];
p_out[3] = p_in[0];
return out;
}