Showing posts with label JOB/INTERVIEW SKILLS. Show all posts
Showing posts with label JOB/INTERVIEW SKILLS. Show all posts

Sunday, March 22, 2009

C Traps and Pitfalls

C Traps and Pitfalls*
Andrew Koenig
AT&T Bell Laboratories
Murray Hill, New Jersey 07974
ABSTRACT
The C language is like a carving knife: simple, sharp, and extremely useful in
skilled hands. Like any sharp tool, C can injure people who don’t know how to handle it.
This paper shows some of the ways C can injure the unwary, and how to avoid injury.
0. Introduction
The C language and its typical implementations are designed to be used easily by experts. The language
is terse and expressive. There are few restrictions to keep the user from blundering. A user who has
blundered is often rewarded by an effect that is not obviously related to the cause.
In this paper, we will look at some of these unexpected rewards. Because they are unexpected, it
may well be impossible to classify them completely. Nevertheless, we have made a rough effort to do so
by looking at what has to happen in order to run a C program. We assume the reader has at least a passing
acquaintance with the C language.
Section 1 looks at problems that occur while the program is being broken into tokens. Section 2 follows
the program as the compiler groups its tokens into declarations, expressions, and statements. Section
3 recognizes that a C program is often made out of several parts that are compiled separately and bound
together. Section 4 deals with misconceptions of meaning: things that happen while the program is actually
running. Section 5 examines the relationship between our programs and the library routines they use. In
section 6 we note that the program we write is not really the program we run; the preprocessor has gotten at
it first. Finally, section 7 discusses portability problems: reasons a program might run on one implementation
and not another.
1. Lexical Pitfalls
The first part of a compiler is usually called a lexical analyzer. This looks at the sequence of characters
that make up the program and breaks them up into tokens. A token is a sequence of one or more characters
that have a (relatively) uniform meaning in the language being compiled. In C, for instance, the
token -> has a meaning that is quite distinct from that of either of the characters that make it up, and that is
independent of the context in which the -> appears.
For another example, consider the statement:
if (x > big) big = x;
Each non-blank character in this statement is a separate token, except for the keyword if and the two
instances of the identifier big.
In fact, C programs are broken into tokens twice. First the preprocessor reads the program. It must
tokenize the program so that it can find the identifiers, some of which may represent macros. It must then
replace each macro invocation by the result of evaluating that macro. Finally, the result of the macro
replacement is reassembled into a character stream which is given to the compiler proper. The compiler
then breaks the stream into tokens a second time.
__________________
* This paper, greatly expanded, is the basis for the book C Traps and Pitfalls (Addison-Wesley, 1989, ISBN
0–201–17928–8); interested readers may wish to refer there as well.
In this section, we will explore some common misunderstandings about the meanings of tokens and
the relationship between tokens and the characters that make them up. We will talk about the preprocessor
later.
1.1. = is not ==
Programming languages derived from Algol, such as Pascal and Ada, use := for assignment and =
for comparison. C, on the other hand, uses = for assignment and == for comparison. This is because
assignment is more frequent than comparison, so the more common meaning is given to the shorter symbol.
Moreover, C treats assignment as an operator, so that multiple assignments (such as a=b=c) can be
written easily and assignments can be embedded in larger expressions.
This convenience causes a potential problem: one can inadvertently write an assignment where one
intended a comparison. Thus, this statement, which looks like it is checking whether x is equal to y:
if (x = y)
foo();
actually sets x to the value of y and then checks whether that value is nonzero. Or consider the following
loop that is intended to skip blanks, tabs, and newlines in a file:
while (c == ’ ’ || c = ’\t’ || c == ’\n’)
c = getc (f);
The programmer mistakenly used = instead of == in the comparison with ’\t’. This ‘‘comparison’’ actually
assigns ’\t’ to c and compares the (new) value of c to zero. Since ’\t’ is not zero, the ‘‘comparison’’
will always be true, so the loop will eat the entire file. What it does after that depends on whether the
particular implementation allows a program to keep reading after it has reached end of file. If it does, the
loop will run forever.
Some C compilers try to help the user by giving a warning message for conditions of the form e1 =
e2. To avoid warning messages from such compilers, when you want to assign a value to a variable and
then check whether the variable is zero, consider making the comparison explicit. In other words, instead
of:
if (x = y)
foo();
write:
if ((x = y) != 0)
foo();
This will also help make your intentions plain.
1.2. & and | are not && or ||
It is easy to miss an inadvertent substitution of = for == because so many other languages use = for
comparison. It is also easy to interchange & and &&, or | and ||, especially because the & and | operators
in C are different from their counterparts in some other languages. We will look at these operators more
closely in section 4.
1.3. Multi-character Tokens
Some C tokens, such as /, *, and =, are only one character long. Other C tokens, such as /* and ==,
and identifiers, are several characters long. When the C compiler encounters a / followed by an *, it must
be able to decide whether to treat these two characters as two separate tokens or as one single token. The C
reference manual tells how to decide: ‘‘If the input stream has been parsed into tokens up to a given character,
the next token is taken to include the longest string of characters which could possibly constitute a
token.’’ Thus, if a / is the first character of a token, and the / is immediately followed by a *, the two
characters begin a comment, regardless of any other context.
The following statement looks like it sets y to the value of x divided by the value pointed to by p:
y = x/*p /* p points at the divisor */;
In fact, /* begins a comment, so the compiler will simply gobble up the program text until the */ appears.
In other words, the statement just sets y to the value of x and doesn’t even look at p. Rewriting this statement
as
y = x / *p /* p points at the divisor */;
or even
y = x/(*p) /* p points at the divisor */;
would cause it to do the division the comment suggests.
This sort of near-ambiguity can cause trouble in other contexts. For example, older versions of C use
=+ to mean what present versions mean by +=. Such a compiler will treat
a=-1;
as meaning the same thing as
a =- 1;
or
a = a - 1;
This will surprise a programmer who intended
a = -1;
On the other hand, compilers for these older versions of C would interpret
a=/*b;
as
a =/ * b ;
even though the /* looks like a comment.
1.4. Exceptions
Compound assignment operators such as += are really multiple tokens. Thus,
a + /* strange */ = 1
means the same as
a += 1
These operators are the only cases in which things that look like single tokens are really multiple tokens. In
particular,
p - > a
is illegal. It is not a synonym for
p -> a
As another example, the >> operator is a single token, so >>= is made up of two tokens, not three.
On the other hand, those older compilers that still accept =+ as a synonym for += treat =+ as a single
token.
- 2 -
1.5. Strings and Characters
Single and double quotes mean very different things in C, and there are some contexts in which confusing
them will result in surprises rather than error messages.
A character enclosed in single quotes is just another way of writing an integer. The integer is the one
that corresponds to the given character in the implementation’s collating sequence. Thus, in an ASCII
implementation, ’a’ means exactly the same thing as 0141 or 97. A string enclosed in double quotes, on
the other hand, is a short-hand way of writing a pointer to a nameless array that has been initialized with the
characters between the quotes and an extra character whose binary value is zero.
The following two program fragments are equivalent:
printf ("Hello world\n");
char hello[] = {’H’, ’e’, ’l’, ’l’, ’o’, ’ ’,
’w’, ’o’, ’r’, ’l’, ’d’, ’\n’, 0};
printf (hello);
Using a pointer instead of an integer (or vice versa) will often cause a warning message, so using
double quotes instead of single quotes (or vice versa) is usually caught. The major exception is in function
calls, where most compilers do not check argument types. Thus, saying
printf(’\n’);
instead of
printf ("\n");
will usually result in a surprise at run time.
Because an integer is usually large enough to hold several characters, some C compilers permit multiple
characters in a character constant. This means that writing ’yes’ instead of "yes" may well go
undetected. The latter means ‘‘the address of the first of four consecutive memory locations containing y,
e, s, and a null character, respectively.’’ The former means ‘‘an integer that is composed of the values of
the characters y, e, and s in some implementation-defined manner.’’ Any similarity between these two
quantities is purely coincidental.
2. Syntactic Pitfalls
To understand a C program, it is not enough to understand the tokens that make it up. One must also
understand how the tokens combine to form declarations, expressions, statements, and programs. While
these combinations are usually well-defined, the definitions are sometimes counter-intuitive or confusing.
In this section, we look at some syntactic constructions that are less than obvious.
2.1. Understanding Declarations
I once talked to someone who was writing a C program that was going to run stand-alone in a small
microprocessor. When this machine was switched on, the hardware would call the subroutine whose
address was stored in location 0.
In order to simulate turning power on, we had to devise a C statement that would call this subroutine
explicitly. After some thought, we came up with the following:
(*(void(*)())0)();
Expressions like these strike terror into the hearts of C programmers. They needn’t, though, because
they can usually be constructed quite easily with the help of a single, simple rule: declare it the way you use
it.
Every C variable declaration has two parts: a type and a list of stylized expressions that are expected
to evaluate to that type. The simplest such expression is a variable:
- 3 -
float f, g;
indicates that the expressions f and g, when evaluated, will be of type float. Because the thing declared
is an expression, parentheses may be used freely:
float ((f));
means that ((f)) evaluates to a float and therefore, by inference, that f is also a float.
Similar logic applies to function and pointer types. For example,
float ff();
means that the expression ff() is a float, and therefore that ff is a function that returns a float.
Analogously,
float *pf;
means that *pf is a float and therefore that pf is a pointer to a float.
These forms combine in declarations the same way they do in expressions. Thus
float *g(), (*h)();
says that *g() and (*h)() are float expressions. Since () binds more tightly than *, *g() means
the same thing as *(g()): g is a function that returns a pointer to a float, and h is a pointer to a function
that returns a float.
Once we know how to declare a variable of a given type, it is easy to write a cast for that type: just
remove the variable name and the semicolon from the declaration and enclose the whole thing in parentheses.
Thus, since
float *g();
declares g to be a function returning a pointer to a float, (float *()) is a cast to that type.
Armed with this knowledge, we are now prepared to tackle (*(void(*)())0)(). We can analyze
this statement in two parts. First, suppose that we have a variable fp that contains a function pointer
and we want to call the function to which fp points. That is done this way:
(*fp)();
If fp is a pointer to a function, *fp is the function itself, so (*fp)() is the way to invoke it. The parentheses
in (*fp) are essential because the expression would otherwise be interpreted as *(fp()). We
have now reduced the problem to that of finding an appropriate expression to replace fp.
This problem is the second part of our analysis. If C could read our mind about types, we could
write:
(*0)();
This doesn’t work because the * operator insists on having a pointer as its operand. Furthermore, the
operand must be a pointer to a function so that the result of * can be called. Thus, we need to cast 0 into a
type loosely described as ‘‘pointer to function returning void.’’
If fp is a pointer to a function returning void, then (*fp)() is a void value, and its declaration
would look like this:
void (*fp)();
Thus, we could write:
void (*fp)();
(*fp)();
at the cost of declaring a dummy variable. But once we know how to declare the variable, we know how to
cast a constant to that type: just drop the name from the variable declaration. Thus, we cast 0 to a ‘‘pointer
to function returning void’’ by saying:
- 4 -
(void(*)())0
and we can now replace fp by (void(*)())0:
(*(void(*)())0)();
The semicolon on the end turns the expression into a statement.
At the time we tackled this problem, there was no such thing as a typedef declaration. Using it,
we could have solved the problem more clearly:
typedef void (*funcptr)();
(* (funcptr) 0)();
2.2. Operators Don’t Always Have the Precedence You Want
Suppose that the manifest constant FLAG is an integer with exactly one bit turned on in its binary
representation (in other words, a power of two), and you want to test whether the integer variable flags
has that bit turned on. The usual way to write this is:
if (flags & FLAG) ...
The meaning of this is plain to most C programmers: an if statement tests whether the expression in the
parentheses evaluates to 0 or not. It might be nice to make this test more explicit for documentation purposes:
if (flags & FLAG != 0) ...
The statement is now easier to understand. It is also wrong, because != binds more tightly than &, so the
interpretation is now:
if (flags & (FLAG != 0)) ...
This will work (by coincidence) if FLAG is 1 or 0 (!), but not for any other power of two.*
Suppose you have two integer variables, h and l, whose values are between 0 and 15 inclusive, and
you want to set r to an 8-bit value whose low-order bits are those of l and whose high-order bits are those
of h. The natural way to do this is to write:
r = h<<4 + l;
Unfortunately, this is wrong. Addition binds more tightly than shifting, so this example is equivalent to
r = h << (4 + l);
Here are two ways to get it right:
r = (h << 4) + l;
r = h << 4 | l;
One way to avoid these problems is to parenthesize everything, but expressions with too many parentheses
are hard to understand, so it is probably useful to try to remember the precedence levels in C.
Unfortunately, there are fifteen of them, so this is not always easy to do. It can be made easier,
though, by classifying them into groups.
The operators that bind the most tightly are the ones that aren’t really operators: subscripting, function
calls, and structure selection. These all associate to the left.
Next come the unary operators. These have the highest precedence of any of the true operators.
Because function calls bind more tightly than unary operators, you must write (*p)() to call a function
pointed to by p; *p() implies that p is a function that returns a pointer. Casts are unary operators and
have the same precedence as any other unary operator. Unary operators are right-associative, so *p++ is
__________________
* Recall that the result of != is always either 1 or 0.
- 5 -
interpreted as *(p++) and not as (*p)++.
Next come the true binary operators. The arithmetic operators have the highest precedence, then the
shift operators, the relational operators, the logical operators, the assignment operators, and finally the conditional
operator. The two most important things to keep in mind are:
1. Every logical operator has lower precedence than every relational operator.
2. The shift operators bind more tightly than the relational operators but less tightly than the arithmetic
operators.
Within the various operator classes, there are few surprises. Multiplication, division, and remainder
have the same precedence, addition and subtraction have the same precedence, and the two shift operators
have the same precedence.
One small surprise is that the six relational operators do not all have the same precedence: == and !=
bind less tightly than the other relational operators. This allows us, for instance, to see if a and b are in the
same relative order as c and d by the expression
a < b == c < d
Within the logical operators, no two have the same precedence. The bitwise operators all bind more
tightly than the sequential operators, each and operator binds more tightly than the corresponding or operator,
and the bitwise exclusive or operator (ˆ) falls between bitwise and and bitwise or.
The ternary conditional operator has lower precedence than any we have mentioned so far. This permits
the selection expression to contain logical combinations of relational operators, as in
z = a < b && b < c ? d : e
This example also shows that it makes sense for assignment to have a lower precedence than the conditional
operator. Moreover, all the compound assignment operators have the same precedence and they all
group right to left, so that
a = b = c
means the same as
b = c; a = b;
Lowest of all is the comma operator. This is easy to remember because the comma is often used as a
substitute for the semicolon when an expression is required instead of a statement.
Assignment is another operator often involved in precedence mixups. Consider, for example, the following
loop intended to copy one file to another:
while (c=getc(in) != EOF)
putc(c,out);
The way the expression in the while statement is written makes it look like c should be assigned the value
of getc(in) and then compared with EOF to terminate the loop. Unhappily, assignment has lower precedence
than any comparison operator, so the value of c will be the result of comparing getc(in), the
value of which is then discarded, and EOF. Thus, the ‘‘copy’’ of the file will consist of a stream of bytes
whose value is 1.
It is not too hard to see that the example above should be written:
while ((c=getc(in)) != EOF)
putc(c,out);
However, errors of this sort can be hard to spot in more complicated expressions. For example, several versions
of the lint program distributed with the UNIXÒ system have the following erroneous line:
if( (t=BTYPE(pt1->aty)==STRTY) || t==UNIONTY ){
This was intended to assign a value to t and then see if t is equal to STRTY or UNIONTY. The actual
- 6 -
effect is quite different.*
The precedence of the C logical operators comes about for historical reasons. B, the predecessor of
C, had logical operators that corresponded rougly to C’s & and | operators. Although they were defined to
act on bits, the compiler would treat them as && and || if they were in a conditional context. When the
two usages were split apart in C, it was deemed too dangerous to change the precedence much.**
2.3. Watch Those Semicolons!
An extra semicolon in a C program usually makes little difference: either it is a null statement, which
has no effect, or it elicits a diagnostic message from the compiler, which makes it easy to remove. One
important exception is after an if or while clause, which must be followed by exactly one statement.
Consider this example:
if (x[i] > big);
big = x[i];
The semicolon on the first line will not upset the compiler, but this program fragment means something
quite different from:
if (x[i] > big)
big = x[i];
The first one is equivalent to:
if (x[i] > big) { }
big = x[i];
which is, of course, equivalent to:
big = x[i];
(unless x, i, or big is a macro with side effects).
Another place that a semicolon can make a big difference is at the end of a declaration just before a
function definition. Consider the following fragment:
struct foo {
int x;
}
f()
{
. . .
}
There is a semicolon missing between the first } and the f that immediately follows it. The effect of this is
to declare that the function f returns a struct foo, which is defined as part of this declaration. If the
semicolon were present, f would be defined by default as returning an integer.†
2.4. The Switch Statement
C is unusual in that the cases in its switch statement can flow into each other. Consider, for example,
the following program fragments in C and Pascal:
__________________
* Thanks to Guy Harris for pointing this out to me.
** Dennis Ritchie and Steve Johnson both pointed this out to me.
† Thanks to an anonymous benefactor for this one.
- 7 -
switch (color) {
case 1: printf ("red");
break;
case 2: printf ("yellow");
break;
case 3: printf ("blue");
break;
}
case color of
1: write (’red’);
2: write (’yellow’);
3: write (’blue’)
end
Both these program fragments do the same thing: print red, yellow, or blue (without starting a
new line), depending on whether the variable color is 1, 2, or 3. The program fragments are exactly analogous,
with one exception: the Pascal program does not have any part that corresponds to the C break
statement. The reason for that is that case labels in C behave as true labels: control can flow unimpeded
right through a case label.
Looking at it another way, suppose the C fragment looked more like the Pascal fragment:
switch (color) {
case 1: printf ("red");
case 2: printf ("yellow");
case 3: printf ("blue");
}
and suppose further that color were equal to 2. Then, the program would print yellowblue, because
control would pass naturally from the second printf call to the statement after it.
This is both a strength and a weakness of C switch statements. It is a weakness because leaving
out a break statement is easy to do, and often gives rise to obscure program misbehavior. It is a strength
because by leaving out a break statement deliberately, one can readily express a control structure that is
inconvenient to implement otherwise. Specifically, in large switch statements, one often finds that the
processing for one of the cases reduces to some other case after a relatively small amount of special handling.
For example, consider a program that is an interpreter for some kind of imaginary machine. Such a
program might contain a switch statement to handle each of the various operation codes. On such a
machine, it is often true that a subtract operation is identical to an add operation after the sign of the second
operand has been inverted. Thus, it is nice to be able to write something like this:
case SUBTRACT:
opnd2 = -opnd2;
/* no break */
case ADD:
. . .
As another example, consider the part of a compiler that skips white space while looking for a token.
Here, one would want to treat spaces, tabs, and newlines identically except that a newline should cause a
line counter to be incremented:
- 8 -
case ’\n’:
linecount++;
/* no break */
case ’\t’:
case ’ ’:
. . .
2.5. Calling Functions
Unlike some other programming languages, C requires a function call to have an argument list, even
if there are no arguments. Thus, if f is a function,
f();
is a statement that calls the function, but
f;
does nothing at all. More precisely, it evaluates the address of the function, but does not call it.*
2.6. The Dangling else Problem
We would be remiss in leaving any discussion of syntactic pitfalls without mentioning this one.
Although it is not unique to C, it has bitten C programmers with many years of experience.
Consider the following program fragment:
if (x == 0)
if (y == 0) error();
else {
z = x + y;
f (&z);
}
The programmer’s intention for this fragment is that there should be two main cases: x = 0 and x¹0.
In the first case, the fragment should do nothing at all unless y = 0, in which case it should call error. In
the second case, the program should set z = x + y and then call f with the address of z as its argument.
However, the program fragment actually does something quite different. The reason is the rule that
an else is always associated with the closest unmatched if. If we were to indent this fragment the way it
is actually executed, it would look like this:
if (x == 0) {
if (y == 0)
error();
else {
z = x + y;
f (&z);
}
}
In other words, nothing at all will happen if x¹0. To get the effect implied by the indentation of the original
example, write:
__________________
* Thanks to Richard Stevens for pointing this out.
- 9 -
if (x == 0) {
if (y == 0)
error();
} else {
z = x + y;
f (&z);
}
3. Linkage
A C program may consist of several parts that are compiled separately and then bound together by a
program usually called a linker, linkage editor, or loader. Because the compiler normally sees only one file
at a time, it cannot detect errors whose recognition would require knowledge of several source program
files at once.
In this section, we look at some errors of that type. Some C implementations, but not all, have a program
called lint that catches many of these errors. It is impossible to overemphasize the importance of
using such a program if it is available.
3.1. You Must Check External Types Yourself
Suppose you have a C program divided into two files. One file contains the declaration:
int n;
and the other contains the declaration:
long n;
This is not a valid C program, because the same external name is declared with two different types in the
two files. However, many implementations will not detect this error, because the compiler does not know
about the contents of either of the two files while it is compiling the other. Thus, the job of checking type
consistency can only be done by the linker (or some utility program like lint); if the operating system has a
linker that doesn’t know about data types, there is little the C compiler can do to force it.
What actually happens when this program is run? There are many possibilities:
1. The implementation is clever enough to detect the type clash. One would then expect to see a diagnostic
message explaining that the type of n was given differently in two different files.
2. You are using an implementation in which int and long are really the same type. This is typically
true of machines in which 32-bit arithmetic comes most naturally. In this case, your program will
probably work as if you had said long (or int) in both declarations. This would be a good example
of a program that works only by coincidence.
3. The two instances of n require different amounts of storage, but they happen to share storage in such
a way that the values assigned to one are valid for the other. This might happen, for example, if the
linker arranged for the int to share storage with the low-order part of the long. Whether or not this
happens is obviously machine- and system-dependent. This is an even better example of a program
that works only by coincidence.
4. The two instances of n share storage in such a way that assigning a value to one has the effect of
apparently assigning a different value to the other. In this case, the program will probably fail.
Another example of this sort of thing happens surprisingly often. One file of a program will contain
a declaration like:
char filename[] = "/etc/passwd";
and another will contain this declaration:
char *filename;
- 10 -
Although arrays and pointers behave very similarly in some contexts, they are not the same. In the
first declaration, filename is the name of an array of characters. Although using the name will generate
a pointer to the first element of that array, that pointer is generated as needed and not actually kept around.
In the second declaration, filename is the name of a pointer. That pointer points wherever the
programmer makes it point. If the programmer doesn’t give it a value, it will have a zero (null) value by
default.
The two declarations of filename use storage in different ways; they cannot coexist.
One way to avoid type clashes of this sort is to use a tool like lint if it is available. In order to be able
to check for type clashes between separately compiled parts of a program, some program must be able to
see all the parts at once. The typical compiler does not do this, but lint does.
Another way to avoid these problems is to put external declarations into include files. That way,
the type of an external object only appears once.*
4. Semantic Pitfalls
A sentence can be perfectly spelled and written with impeccable grammar and still be meaningless.
In this section, we will look at ways of writing programs that look like they mean one thing but actually
mean something quite different.
We will also discuss contexts in which things that look reasonable on the surface actually give undefined
results. We will limit ourselves here to things that are not guaranteed to work on any C implementation.
We will leave those that might work on some implementations but not others until section 7, which
looks at portability problems.
4.1. Expression Evaluation Sequence
Some C operators always evaluate their operands in a known, specified order. Others don’t. Consider,
for instance, the following expression:
a < b && c < d
The language definition states that aevaluated to determine the value of the whole expression. On the other hand, if a is greater than or equal to
b, then cTo evaluate amachines, it may even evaluate them in parallel.
Only the four C operators &&, ||, ?:, and , specify an order of evaluation. && and || evaluate the
left operand first, and the right operand only if necessary. The ?: operator takes three operands: a?b:c
evaluates a first, and then evaluates either b or c, depending on the value of a. The , operator evaluates its
left operand and discards its value, then evaluates its right operand.†
All other C operators evaluate their operands in undefined order. In particular, the assignment operators
do not make any guarantees about evaluation order.
For this reason, the following way of copying the first n elements of array x to array y doesn’t work:
i = 0;
while (i < n)
y[i] = x[i++];
The trouble is that there is no guarantee that the address of y[i] will be evaluated before i is incremented.
__________________
* Some C compilers insist that there must be exactly one definition of an external object, although there may be many declarations.
When using such a compiler, it may be easiest to put a declaration in an include file and a definition in some
other place. This means that the type of each external object appears twice, but that is better than having it appear more
than two times.
† Commas that separate function arguments are not comma operators. For example, x and y are fetched in undefined order
in f(x,y), but not in g((x,y)). In the latter example, g has one argument. The value of that argument is determined by
evaluating x, discarding its value, and then evaluating y.
- 11 -
On some implementations, it will; on others, it won’t. This similar version fails for the same reason:
i = 0;
while (i < n)
y[i++] = x[i];
On the other hand, this one will work fine:
i = 0;
while (i < n) {
y[i] = x[i];
i++;
}
This can, of course, be abbreviated:
for (i = 0; i < n; i++)
y[i] = x[i];
4.2. The &&, ||, and ! Operators
C has two classes of logical operators that are occasionally interchangeable: the bitwise operators &,
|, and ˜, and the logical operators &&, ||, and !. A programmer who substitutes one of these operators for
the corresponding operator from the other class may be in for a surprise: the program may appear to work
correctly after such an interchange but may actually be working only by coincidence.
The &, |, and ˜ operators treat their operands as a sequence of bits and work on each bit separately.
For example, 10&12 is 8 (1000), because & looks at the binary representations of 10 (1010) and 12
(1100) and produces a result that has a bit turned on for each bit that is on in the same position in both
operands. Similarly, 10|12 is 14 (1110) and ˜10 is –11 (11...110101), at least on a 2’s complement
machine.
The &&, ||, and ! operators, on the other hand, treat their arguments as if they are either ‘‘true’’ or
‘‘false,’’ with the convention that 0 represents ‘‘false’’ and any other value represents ‘‘true.’’ These operators
return 1 for ‘‘true’’ and 0 for ‘‘false,’’ and the && and || operators do not even evaluate their righthand
operands if their results can be determined from their left-hand operands.
Thus !10 is zero, because 10 is nonzero, 10&&12 is 1, because both 10 and 12 are nonzero, and
10||12 is also 1, because 10 is nonzero. Moreover, 12 is not even evaluated in the latter expression, nor
is f() in 10||f().
Consider the following program fragment to look for a particular element in a table:
i = 0;
while (i < tabsize && tab[i] != x)
i++;
The idea behind this loop is that if i is equal to tabsize when the loop terminates, then the element
sought was not found. Otherwise, i contains the element’s index.
Suppose that the && were inadvertently replaced by & in this example. Then the loop would probably
still appear to work, but would do so only because of two lucky breaks.
The first is that both comparisons in this example are of a sort that yield 0 if the condition is false and
1 if the condition is true. As long as x and y are both 1 or 0, x&y and x&&y will always have the same
value. However, if one of the comparisons were to be replaced by one that uses some non-zero value other
than 1 to represent ‘‘true,’’ then the loop would stop working.
The second lucky break is that looking just one element off the end of an array is usually harmless,
provided that the program doesn’t change that element. The modified program looks past the end of the
array because &, unlike &&, must always evaluate both of its operands. Thus in the last iteration of the
loop, the value of tab[i] will be fetched even though i is equal to tabsize. If tabsize is the number
of elements in tab, this will fetch a non-existent element of tab.
- 12 -
4.3. Subscripts Start from Zero
In most languages, an array with n elements normally has those elements numbered with subscripts
ranging from 1 to n inclusive. Not so in C.
A C array with n elements does not have an element with a subscript of n, as the elements are numbered
from 0 through n-1. Because of this, programmers coming from other languages must be especially
careful when using arrays:
int i, a[10];
for (i=1; i<=10; i++)
a[i] = 0;
This example, intended to set the elements of a to zero, had an unexpected side effect. Because the comparison
in the for statement was i<=10 instead of i<10, the non-existent element number 10 of a was
set to zero, thus clobbering the word that followed a in memory. The compiler on which this program was
run allocates memory for users’ variables in decreasing memory locations, so the word after a turned out to
be i. Setting i to zero made the loop into an infinite loop.
4.4. C Doesn’t Always Cast Actual Parameters
The following simple program fragment fails for two reasons:
double s;
s = sqrt (2);
printf ("%g\n", s);
The first reason is that sqrt expects a double value as its argument and it isn’t getting one. The
second is that it returns a double result but isn’t declared as such. One way to correct it is:
double s, sqrt();
s = sqrt (2.0);
printf ("%g\n", s);
C has two simple rules that control conversion of function arguments: (1) integer values shorter than
an int are converted to int; (2) floating-point values shorter than a double are converted to double.
All other values are left unconverted. It is the programmer’s responsibility to ensure that the arguments to
a function are of the right type.
Therefore, a programmer who uses a function like sqrt, whose parameter is a double, must be
careful to pass arguments that are of float or double type only. The constant 2 is an int and is therefore
of the wrong type.
When the value of a function is used in an expression, that value is automatically cast to an appropriate
type. However, the compiler must know the actual type returned by the function in order to be able to
do this. Functions used without further declaration are assumed to return an int, so declarations for such
functions are unnecessary. However, sqrt returns a double, so it must be declared as such before it can
be used successfully.
In practice, C implementations generally provide a file that can be brought in with an include
statement that contains declarations for library functions like sqrt, but writing declarations is still necessary
for programmers who write their own functions – in other words, for anyone who writes non-trivial C
programs.
Here is a more spectacular example:
- 13 -
main()
{
int i;
char c;
for (i=0; i<5; i++) {
scanf ("%d", &c);
printf ("%d ", i);
}
printf ("\n");
}
Ostensibly, this program reads five numbers from its standard input and writes 0 1 2 3 4 on its
standard output. In fact, it doesn’t always do that. On one compiler, for example, its output is 0 0 0 0
0 1 2 3 4.
Why? The key is the declaration of c as a char rather than as an int. When you ask scanf to read
an integer, it expects a pointer to an integer. What it gets in this case is a pointer to a character. Scanf has
no way to tell that it didn’t get what it expected: it treats its input as an integer pointer and stores an integer
there. Since an integer takes up more memory than a character, this steps on some of the memory near c.
Exactly what is near c is the compiler’s business; in this case it turned out to be the low-order part of
i. Therefore, each time a value was read for c, it reset i to zero. When the program finally reached end of
file, scanf stopped trying to put new values into c, so i could be incremented normally to end the loop.
4.5. Pointers are not Arrays
C programs often adopt the convention that a character string is stored as an array of characters, followed
by a null character. Suppose we have two such strings s and t, and we want to concatenate them
into a single string r. To do this, we have the usual library functions strcpy and strcat. The following
obvious method doesn’t work:
char *r;
strcpy (r, s);
strcat (r, t);
The reason it doesn’t work is that r is not initialized to point anywhere. Although r is potentially capable
of identifying an area of memory, that area doesn’t exist until you allocate it.
Let’s try again, allocating some memory for r:
char r[100];
strcpy (r, s);
strcat (r, t);
This now works as long as the strings pointed to by s and t aren’t too big. Unfortunately, C requires us to
state the size of an array as a constant, so there is no way to be certain that r will be big enough. However,
most C implementations have a library function called malloc that takes a number and allocates enough
memory for that many characters. There is also usually a function called strlen that tells how many
characters are in a string. It might seem, therefore, that we could write:
char *r, *malloc();
r = malloc (strlen(s) + strlen(t));
strcpy (r, s);
strcat (r, t);
This example, however, fails for two reasons. First, malloc might run out of memory, an event that
it generally signals by quietly returning a null pointer.
Second, and much more important, is that the call to malloc doesn’t allocate quite enough memory.
Recall the convention that a string is terminated by a null character. The strlen function returns the
- 14 -
number of characters in the argument string, excluding the null character at the end. Therefore, if
strlen(s) is n, s really requires n+1 characters to contain it. We must therefore allocate one extra character
for r. After doing this and checking that malloc worked, we get:
char *r, *malloc();
r = malloc (strlen(s) + strlen(t) + 1);
if (!r) {
complain();
exit (1);
}
strcpy (r, s);
strcat (r, t);
4.6. Eschew Synecdoche
A synecdoche (sin-ECK-duh-key) is a literary device, somewhat like a simile or a metaphor, in which,
according to the Oxford English Dictionary, ‘‘a more comprehensive term is used for a less comprehensive
or vice versa; as whole for part or part for whole, genus for species or species for genus, etc.’’
This exactly describes the common C pitfall of confusing a pointer with the data to which it points.
This is most common for character strings. For instance:
char *p, *q;
p = "xyz";
It is important to understand that while it is sometimes useful to think of the value of p as the string xyz
after the assignment, this is not really true. Instead, the value of p is a pointer to the 0th element of an array
of four characters, whose values are ’x’, ’y’, ’z’, and ’\0’. Thus, if we now execute
q = p;
p and q are now two pointers to the same part of memory. The characters in that memory did not get
copied by the assignment. The situation now looks like this:
x y z \0
p q
The thing to remember is that copying a pointer does not copy the thing it points to.
Thus, if after this we were to execute
q[1] = ’Y’;
q would point to memory containing the string xYz. So would p, because p and q point to the same memory.
4.7. The Null Pointer is Not the Null String
The result of converting an integer to a pointer is implementation-dependent, with one important
exception. That exception is the constant 0, which is guaranteed to be converted to a pointer that is unequal
to any valid pointer. For documentation, this value is often given symbolically:
#define NULL 0
but the effect is the same. The important thing to remember about 0 when used as a pointer is that it must
never be dereferenced. In other words, when you have assigned 0 to a pointer variable, you must not ask
- 15 -
what is in the memory it points to. It is valid to write:
if (p == (char *) 0) ...
but it is not valid to write:
if (strcmp (p, (char *) 0) == 0) ...
because strcmp always looks at the memory addressed by its arguments.
If p is a null pointer, it is not even valid to say:
printf (p);
or
printf ("%s", p);
4.8. Integer Overflow
The C language definition is very specific about what happens when an integer operation overflows
or underflows.
If either operand is unsigned, the result is unsigned, and is defined to be modulo 2n, where n is the
word size. If both operands are signed, the result is undefined.
Suppose, for example, that a and b are two integer variables, known to be non-negative, and you
want to test whether a+b might overflow. One obvious way to do it looks something like this:
if (a + b < 0)
complain();
In general, this does not work.
The point is that once a+b has overflowed, all bets are off as to what the result will be. For example,
on some machines, an addition operation sets an internal register to one of four states: positive, negative,
zero, or overflow. On such a machine, the compiler would have every right to implement the example
given above by adding a and b and checking whether this internal register was in negative state afterwards.
If the operation overflowed, the register would be in overflow state, and the test would fail.
One correct way of doing this particular test relies on the fact that unsigned arithmetic is well-defined
for all values, as are the conversions between signed and unsigned values:
if ((int) ((unsigned) a + (unsigned) b) < 0)
complain();
4.9. Shift Operators
Two questions seem to cause trouble for people who use shift operators:
1. In a right shift, are vacated bits filled with zeroes or copies of the sign bit?
2. What values are permitted for the shift count?
The answer to the first question is simple but sometimes implementation-dependent. If the item
being shifted is unsigned, zeroes are shifted in. If the item is signed, the implementation is permitted to fill
vacated bit positions either with zeroes or with copies of the sign bit. If you care about vacated bits in a
right shift, declare the variable in question as unsigned. You are then entitled to assume that vacated bits
will be set to zero.
The answer to the second question is also simple: if the item being shifted is n bits long, then the shift
count must be greater than or equal to zero and strictly less than n. Thus, it is not possible to shift all the
bits out of a value in a single operation.
For example, if an int is 32 bits, and n is an int, it is legal to write n<<31 and n<<0 but not
n<<32 or n<<-1.
Note that a right shift of a signed integer is generally not equivalent to division by a power of two,
- 16 -
even if the implementation copies the sign into vacated bits. To prove this, consider that the value of (-
1)>>1 cannot possibly be zero.
5. Library Functions
Every useful C program must use library functions, because there is no way of doing input or output
built into the language. In this section, we look at some cases where some widely-available library functions
behave in ways that the programmer might not expect.
5.1. Getc Returns an Integer
Consider the following program:
#include
main()
{
char c;
while ((c = getchar()) != EOF)
putchar (c);
}
This program looks like it should copy its standard input to its standard output. In fact, it doesn’t
quite do this.
The reason is that c is declared as a character rather than as an integer. This means that it is impossible
for c to hold every possible character as well as EOF.
Thus there are two possibilities. Either some legitimate input character will cause c to take on the
same value as EOF, or it will be impossible for c to have the value EOF at all. In the former case, the program
will stop copying in the middle of certain files. In the latter case, the program will go into an infinite
loop.
Actually, there is a third case: the program may work by coincidence. The C Reference Manual
defines the result of the expression
((c = getchar()) != EOF)
quite rigorously. Section 6.1 states:
When a longer integer is converted to a shorter or to a char, it is truncated on the left; excess bits
are simply discarded.
Section 7.14 states:
There are a number of assignment operators, all of which group right-to-left. All require an lvalue as
their left operand, and the type of an assignment expression is that of its left operand. The value is
the value stored in the left operand after the assignment has taken place.
The combined effect of these two sections is to require that the result of getchar be truncated to a character
value by discarding the high-order bits, and that this truncated value then be compared with EOF. As part of
this comparison, the value of c must be extended to an integer, either by padding on the left with zero bits
or by sign extension, as appropriate.
However, some compilers do not implement this expression correctly. They properly assign the
low-order bits of the value of getchar to c. However, instead of then comparing c to EOF, they compare the
entire value of getchar! A compiler that does this will make the sample program shown above appear to
work ‘‘correctly.’’
- 17 -
5.2. Buffered Output and Memory Allocation
When a program produces output, how important is it that a human be able to see that output immediately?
It depends on the program.
For example, if the output is going to a terminal and is asking the person sitting at that terminal to
answer a question, it is crucial that the person see the output in order to be able to know what to type. On
the other hand, if the output is going to a file, from where it will eventually be sent to a line printer, it is
only important that all the output get there eventually.
It is often more expensive to arrange for output to appear immediately than it is to save it up for a
while and write it later on in a large chunk. For this reason, C implementations typically afford the programmer
some control over how much output is to be produced before it is actually written.
That control is often vested in a library function called setbuf. If buf is a character array of appropriate
size, then
setbuf (stdout, buf);
tells the I/O library that all output written to stdout should henceforth use buf as an output buffer, and that
output directed to stdout should not actually be written until buf becomes full or until the programmer
directs it to be written by calling fflush. The appropriate size for such a buffer is defined as BUFSIZ in
.
Thus, the following program illustrates the obvious way to use setbuf in a program that copies its
standard input to its standard output:
#include
main()
{
int c;
char buf[BUFSIZ];
setbuf (stdout, buf);
while ((c = getchar()) != EOF)
putchar (c);
}
Unfortunately, this program is wrong, for a subtle reason.
To see where the trouble lies, ask when the buffer is flushed for the last time. Answer: after the main
program has finished, as part of the cleaning up that the library does before handing control back to the
operating system. But by that time, the buffer has already been freed!
There are two ways to prevent this sort of trouble.
First, make the buffer static, either by declaring it explicitly as static:
static char buf[BUFSIZ];
or by moving the declaration outside the main program entirely.
Another possibility is to allocate the buffer dynamically and never free it:
char *malloc();
setbuf (stdout, malloc (BUFSIZ));
Note that in this latter case, it is unnecessary to check if malloc was successful, because if it fails it will
return a null pointer. A null pointer is an acceptable second argument to setbuf; it requests that stdout be
unbuffered. This will work slowly, but it will work.
- 18 -
6. The Preprocessor
The programs we run are not the programs we write: they are first transformed by the C preprocessor.
The preprocessor gives us a way of abbreviating things that is important for two major reasons (and several
minor ones).
First, we may want to be able to change all instances of a particular quantity, such as the size of a
table, by changing one number and recompiling the program.*
Second, we may want to define things that appear to be functions but do not have the execution overhead
normally associated with a function call. For example, getchar and putchar are usually implemented
as macros to avoid having to call a function for each character of input or output.
6.1. Macros are not Functions
Because macros can be made to appear almost as if they were functions, programmers are sometimes
tempted to regard them as truly equivalent. Thus, one sees things like this:
#define max(a,b) ((a)>(b)?(a):(b))
Notice all the parentheses in the macro body. They defend against the possibility that a or b might be
expressions that contain operators of lower precedence than >.
The main problem, though, with defining things like max as macros is that an operand that is used
twice may be evaluated twice. Thus, in this example, if a is greater than b, a will be evaluated twice: once
during the comparison, and again to calculate the value yielded by max.
Not only can this be inefficient, it can also be wrong:
biggest = x[0];
i = 1;
while (i < n)
biggest = max (biggest, x[i++]);
This would work fine if max were a true function, but fails with max a macro. Suppose, for example, that
x[0] is 2, x[1] is 3, and x[2] is 1. Look at what happens during the first iteration of the loop. The
assignment statement expands into:
biggest = ((biggest)>(x[i++])?(biggest):(x[i++]));
First, biggest is compared to x[i++]. Since i is 1 and x[1] is 3, the relation is false. As a side effect,
i is incremented to 2.
Because the relation is false, the value of x[i++] is now assigned to biggest. However, i is now
2, so the value assigned to biggest is the value of x[2], which is 1.
One way around these worries is to ensure that the arguments to the max macro do not have any side
effects:
biggest = x[0];
for (i = 1; i < n; i++)
biggest = max (biggest, x[i]);
Here is another example of the hazards of mixing side effects and macros. This is the definition of
the putc macro from in the Eighth Edition of the Unix system:
#define putc(x,p) (--(p)->_cnt>=0?(*(p)->_ptr++=(x)):_flsbuf(x,p))
The first argument to putc is a character to be written to a file; the second argument is a pointer to an
internal data structure that describes the file. Notice that the first argument, which could easily be something
like *z++, is carefully evaluated only once, even though it appears in two separate places in the
macro body, while the second argument is evaluated twice (in the macro body, x appears twice, but since
__________________
* The preprocessor also makes it easy to group such manifest constants together to make them easier to find.
- 19 -
the two occurrences are on opposite sides of a : operator, exactly one of them will be evaluated in any single
instance of putc). Since it is unusual for the file argument to putc to have side effects, this rarely
causes trouble. Nevertheless, it is documented in the user’s manual: ‘‘Because it is implemented as a
macro, putc treats a stream argument with side effects improperly. In particular, putc(c,*f++) doesn’t
work sensibly.’’ Notice that putc(*c++,f) works fine in this implementation.
Some C implementations are less careful. For instance, not everyone handles putc(*c++,f) correctly.
As another example, consider the toupper function that appears in many C libraries. It translates a
lower-case letter to the corresponding upper-case letter while leaving other characters unchanged. If we
assume that all the lower-case letters and all the upper-case letters are contiguous (with a possible gap
between the cases), we get the following function:
toupper(c)
{
if (c >= ’a’ && c <= ’z’)
c += ’A’ - ’a’;
return c;
}
In most C implementations, the subroutine call overhead is much longer than the actual calculations, so the
implementor is tempted to make it a macro:
#define toupper(c) ((c)>=’a’ && (c)<=’z’? (c)+(’A’-’a’): (c))
This is indeed faster than the function in many cases. However, it will cause a surprise for anyone who
tries to use toupper(*p++).
Another thing to watch out for when using macros is that they may generate very large expressions
indeed. For example, look again at the definition of max:
#define max(a,b) ((a)>(b)?(a):(b))
Suppose we want to use this definition to find the largest of a, b, c, and d. If we write the obvious:
max(a,max(b,max(c,d)))
this expands to:
((a)>(((b)>(((c)>(d)?(c):(d)))?(b):(((c)>(d)?(c):(d)))))?
(a):(((b)>(((c)>(d)?(c):(d)))?(b):(((c)>(d)?(c):(d))))))
which is surprisingly large. We can make it a little less large by balancing the operands:
max(max(a,b),max(c,d))
which gives:
((((a)>(b)?(a):(b)))>(((c)>(d)?(c):(d)))?
(((a)>(b)?(a):(b))):(((c)>(d)?(c):(d))))
Somehow, though, it seems easier to write:
biggest = a;
if (biggest < b) biggest = b;
if (biggest < c) biggest = c;
if (biggest < d) biggest = d;
6.2. Macros are not Type Definitions
One common use of macros is to permit several things in diverse places to be the same type:
- 20 -
#define FOOTYPE struct foo
FOOTYPE a;
FOOTYPE b, c;
This lets the programmer change the types of a, b, and c just by changing one line of the program, even if
a, b, and c are declared in widely different places.
Using a macro definition for this has the advantage of portability – any C compiler supports it. Most
C compilers also support another way of doing this:
typedef struct foo FOOTYPE;
This defines FOOTYPE as a new type that is equivalent to struct foo.
These two ways of naming a type may appear to be equivalent, but the typedef is more flexible.
Consider, for example, the following:
#define T1 struct foo *
typedef struct foo *T2;
These definitions make T1 and T2 conceptually equivalent to a pointer to a struct foo. But look what
happens when we try to use them with more than one variable:
T1 a, b;
T2 c, d;
The first declaration gets expanded to
struct foo * a, b;
This defines a to be a pointer to a structure, but defines b to be a structure (not a pointer). The second declaration,
in contrast, defines both c and d as pointers to structures, because T2 behaves as a true type.
7. Portability Pitfalls
C has been implemented by many people to run on many machines. Indeed, one of the reasons to
write programs in C in the first place is that it is easy to move them from one programming environment to
another.
However, because there are so many implementors, they do not all talk to each other. Moreover, different
systems have different requirements, so it is reasonable to expect C implementations to differ slightly
between one machine and another.
Because so many of the early C implementations were associated with the UNIX operating system,
the nature of many of these functions was shaped by that system. When people started implementing C
under other systems, they tried to make the library behave in ways that would be familiar to programmers
used to the UNIX system.
They did not always succeed. What is more, as more people in different parts of the world started
working on different versions of the UNIX system, the exact nature of some of the library functions
inevitably diverged. Today, a C programmer who wishes to write programs useful in someone else’s environment
must know about many of these subtle differences.
7.1. What’s in a Name?
Some C compilers treat all the characters of an identifier as being significant. Others ignore characters
past some limit when storing identifiers. C compilers usually produce object programs that must then
be processed by loaders in order to be able to access library subroutines. Loaders, in turn, often impose
their own restrictions on the kinds of names they can handle.
One common loader restriction is that letters in external names must be in upper case only. When
faced with such a restriction, it is reasonable for a C implementor to force all external names to upper case.
Restrictions of this sort are blessed by section 2.1 the C reference manual:
An identifier is a sequence of letters and digits; the first character must be a letter. The underscore _
- 21 -
counts as as a letter. Upper and lower case letters are different. No more than the first eight characters
are significant, although more may be used. External identifiers, which are used by various
assemblers and loaders, are more restricted:
Here, the reference manual goes on to give examples of various implementations that restrict external
identifiers to a single case, or to fewer than eight characters, or both.
Because of all this, it is important to be careful when choosing identifiers in programs intended to be
portable. Having two subroutines named, say print_fields and print_float would not be a very
good idea.
As a striking example, consider the following function:
char *
Malloc (n)
unsigned n;
{
char *p, *malloc();
p = malloc (n);
if (p == NULL)
panic ("out of memory");
return p;
}
This function is a simple way of ensuring that running out of memory will not go undetected. The
idea is for the program to allocate memory by calling Malloc instead of malloc. If malloc ever fails,
the result will be to call panic which will presumably terminate the program with an appropriate error
message.
Consider, however, what happens when this function is used on a system that ignores case distinctions
in external identifiers. In effect, the names malloc and Malloc become equivalent. In other
words, the library function malloc is effectively replaced by the Malloc function above, which when it
calls malloc is really calling itself. The result, of course, is that the first attempt to allocate memory
results in a recursion loop and consequent mayhem, even though the function will work on an implementation
that preserves case distinctions.
7.2. How Big is an Integer?
C provides the programmer with three sizes of integers: ordinary, short, and long, and with characters,
which behave as if they were small integers. The language definition does not guarantee much about
the relative sizes of the various kinds of integer:
1. The four sizes of integers are non-decreasing.
2. An ordinary integer is large enough to contain any array subscript.
3. The size of a character is natural for the particular hardware.
Most modern machines have 8-bit characters, though a few have 7- or 9-bit characters, so characters
are usually 7, 8, or 9 bits.
Long integers are usually at least 32 bits long, so that a long integer can be used to represent the size
of a file.
Ordinary integers are usually at least 16 bits long, because shorter integers would impose too much of
a restriction on the maximum size of an array.
Short integers are almost always exactly 16 bits long.
What does this all mean in practice? The most important thing is that one cannot count on having
any particular precision available. Informally, one can probably expect 16 bits for a short or an ordinary
integer, and 32 bits for a long integer, but not even those sizes are guaranteed. One can certainly use ordinary
integers to express table sizes and subscripts, but what about a variable that must be able to hold values
up to ten million?
- 22 -
The most portable way to do that is probably to define a ‘‘new’’ type:
typedef long tenmil;
Now one can use this type to declare a variable of that width and know that, at worst, one will have to
change a single type definition to get all those variables to be the right type.
7.3. Are Characters Signed or Unsigned?
Most modern computers support 8-bit characters, so most modern C compilers implement characters
as 8-bit integers. However, not all compilers interpret those 8-bit quantities the same way.
The issue becomes important only when converting a char quantity to a larger integer. Going the
other way, the results are well-defined: excess bits are simply discarded. But a compiler converting a char
to an int has a choice: should it treat the char as a signed or an unsigned quantity? If the former, it
should expand the char to an int by replicating the sign bit; if the latter, it should fill the extra bit positions
with zeroes.
The results of this decision are important to virtually anyone who deals with characters with their
high-order bits turned on. It determines whether 8-bit characters are going to be considered to range from
–128 through 127 or from 0 through 255. This, in turn, affects the way the programmer will design things
like hash tables and translate tables.
If you care whether a character value with the high-order bit on is treated as a negative number, you
should probably declare it as unsigned char. Such values are guaranteed to be zero-extended when
converted to integer, whereas ordinary char variables may be signed in one implementation and unsigned
in another.
Incidentally, it is a common misconception that if c is a character variable, one can obtain the
unsigned integer equivalent of c by writing (unsigned) c. This fails because a char quantity is converted
to int before any operator is applied to it, even a cast. Thus c is converted first to a signed integer
and then to an unsigned integer, with possibly unexpected results.
The right way to do it is (unsigned char) c.
7.4. Are Right Shifts Signed or Unsigned?
This bears repeating: a program that cares how shifts are done had better declare the quantities being
shifted as unsigned.
7.5. How Does Division Truncate?
Suppose we divide a by b to give a quotient q and remainder r:
q = a / b;
r = a % b;
For the moment, suppose also that b>0.
What relationships might we want to hold between a, b, p, and q?
1. Most important, we want q*b + r == a, because this is the relation that defines the remainder.
2. If we change the sign of a, we want that to change the sign of q, but not the absolute value.
3. We want to ensure that r>=0 and rhash table, it is important to be able to know that it will always be a valid index.
These three properties are clearly desirable for integer division and remainder operations. Unfortunately,
they cannot all be true at once.
Consider 3/2, giving a quotient of 1 and a remainder of 1. This satisfies property 1. What should be
the value of - 3/2? Property 2 suggests that it should be - 1, but if that is so, the remainder must also be
- 1, which violates property 3. Alternatively, we can satisfy property 3 by making the remainder 1, in
which case property 1 demands that the quotient be - 2. This violates property 2.
Thus C, and any language that implements truncating integer division, must give up at least one of
- 23 -
these three principles.
Most programming languages give up number 3, saying instead that the remainder has the same sign
as the dividend. This makes it possible to preserve properties 1 and 2. Most C implementations do this in
practice, also.
However, the C language definition only guarantees property 1, along with the property that ïrï<ïbï
and that r³0 whenever a³0 and b > 0. This property is less restrictive than either property 2 or property 3,
and actually permits some rather strange implementations that would be unlikely to occur in practice (such
as an implementation that always truncates the quotient away from zero).
Despite its sometimes unwanted flexibility, the C definition is enough that we can usually make integer
division do what we want, provided that we know what we want. Suppose, for example, that we have a
number n that represents some function of the characters in an identifier, and we want to use division to
obtain a hash table entry h such that 0£h < HASHSIZE. If we know that n is never negative, we simply
write
h = n % HASHSIZE;
However, if n might be negative, this is not good enough, because h might also be negative. However, we
know that h > - HASHSIZE, so we can write:
h = n % HASHSIZE;
if (h < 0)
h += HASHSIZE;
Better yet, declare n as unsigned.
7.6. How Big is a Random Number?
This size ambiguity has affected library design as well. When the only C implementation ran on the
PDP-11‡ computer, there was a function called rand that returned a (pseudo-) random non-negative integer.
PDP-11 integers were 16 bits long, including the sign, so rand would return an integer between 0 and
215 - 1.
When C was implemented on the VAX-11, integers were 32 bits long. What was the range of the
rand function on the VAX-11?
For their system, the people at the University of California took the view that rand should return a
value that ranges over all possible non-negative integers, so their version of rand returns an integer between
0 and 231 - 1.
The people at AT&T, on the other hand, decided that a PDP-11 program that expected the result of
rand to be less than 215 would be easier to transport to a VAX-11 if the rand function returned a value
between 0 and 215 there, too.
As a result, it is now difficult to write a program that uses rand without tailoring it to the implementation.
7.7. Case Conversion
The toupper and tolower functions have a similar history. They were originally written as macros:
#define toupper(c) ((c)+’A’-’a’)
#define tolower(c) ((c)+’a’-’A’)
When given a lower-case letter as input toupper yields the corresponding upper-case letter. Tolower does
the opposite. Both these macros depend on the implementation’s character set to the extent that they
demand that the difference between an upper-case letter and the corresponding lower-case letter be the
same constant for all letters. This assumption is valid for both the ASCII and EBCDIC character sets, and
probably isn’t too dangerous, because the non-portability of these macro definitions can be encapsulated in
__________________
‡ PDP-11 and VAX-11 are Trademarks of Digital Equipment Corporation.
- 24 -
the single file that contains them.
These macros do have one disadvantage, though: when given something that is not a letter of the
appropriate case, they return garbage. Thus, the following innocent program fragment to convert a file to
lower case doesn’t work with these macros:
int c;
while ((c = getchar()) != EOF)
putchar (tolower (c));
Instead, one must write:
int c;
while ((c = getchar()) != EOF)
putchar (isupper (c)? tolower (c): c);
At one point, some enterprising soul in the UNIX development organization at AT&T noticed that
most uses of toupper and tolower were preceded by tests to ensure that their arguments were appropriate.
He considered rewriting the macros this way:
#define toupper(c) ((c) >= ’a’ && (c) <= ’z’? (c) + ’A’ - ’a’: (c))
#define tolower(c) ((c) >= ’A’ && (c) <= ’Z’? (c) + ’a’ - ’A’: (c))
but realized that this would cause c to be evaluated anywhere between one and three times for each call,
which would play havoc with expressions like toupper(*p++). Instead, he decided to rewrite toupper
and tolower as functions. Toupper now looked something like this:
int toupper (c)
int c;
{
if (c >= ’a’ && c <= ’z’)
return c + ’A’ - ’a’;
return c;
}
and tolower looked similar.
This change had the advantage of robustness, at the cost of introducing function call overhead into
each use of these functions. Our hero realized that some people might not be willing to pay the cost of this
overhead, so he re-introduced the macros with new names:
#define _toupper(c) ((c)+’A’-’a’)
#define _tolower(c) ((c)+’a’-’A’)
This gave users a choice of convenience or speed.
There was just one problem in all this: the people at Berkeley never followed suit, nor did some other
C implementors. This means that a program written on an AT&T system that uses toupper or tolower, and
assumes that it will be able to pass an argument that is not a letter of the appropriate case, may stop working
on some other C implementation.
This sort of failure is very hard to trace for someone who does not know this bit of history.
7.8. Free First, then Reallocate
Most C implementations provide users with three memory allocation functions called malloc, realloc,
and free. Calling malloc(n) returns a pointer to n characters of newly-allocated memory that the programmer
can use. Giving free a pointer to memory previously returned by malloc makes that memory
available for re-use. Calling realloc with a pointer to an allocated area and a new size stretches or shrinks
the memory to the new size, possibly copying it in the process.
Or so one might think. The truth is actually somewhat more subtle. Here is an excerpt from the
description of realloc that appears in the System V Interface Definition:
- 25 -
Realloc changes the size of the block pointed to by ptr to size bytes and returns a pointer to the (possibly
moved) block. The contents will be unchanged up to the lesser of the new and old sizes.
The Seventh Edition of the reference manual for the UNIX system contains a copy of the same paragraph.
In addition, it contains a second paragraph describing realloc:
Realloc also works if ptr points to a block freed since the last call of malloc, realloc, or calloc; thus
sequences of free, malloc and realloc can exploit the search strategy of malloc to do storage compaction.
Thus, the following is legal under the Seventh Edition:
free (p);
p = realloc (p, newsize);
This idiosyncrasy remains in systems derived from the Seventh Edition: it is possible to free a storage
area and then reallocate it. By implication, freeing memory on these systems is guaranteed not to change
its contents until the next time memory is allocated. Thus, on these systems, one can free all the elements
of a list by the following curious means:
for (p = head; p != NULL; p = p->next)
free ((char *) p);
without worrying that the call to free might invalidate p->next.
Needless to say, this technique is not recommended, if only because not all C implementations preserve
memory long enough after it has been freed. However, the Seventh Edition manual leaves one thing
unstated: the original implementation of realloc actually required that the area given to it for reallocation be
free first. For this reason, there are many C programs floating around that free memory first and then reallocate
it, and this is something to watch out for when moving a C program to another implementation.
7.9. An Example of Portability Problems
Let’s take a look at a problem that has been solved many times by many people. The following program
takes two arguments: a long integer and a (pointer to a) function. It converts the integer to decimal
and calls the given function with each character of the decimal representation.
void
printnum (n, p)
long n;
void (*p)();
{
if (n < 0) {
(*p) (’-’);
n = -n;
}
if (n >= 10)
printnum (n/10, p);
(*p) (n % 10 + ’0’);
}
This program is fairly straightforward. First we check if n is negative; if so, we print a sign and
make n positive. Next, we test if n³10. If so, its decimal representation has two or more digits, so we call
printnum recursively to print all but the last digit. Finally, we print the last digit.
This program, for all its simplicity, has several portability problems. The first is the method it uses to
convert the low-order decimal digit of n to character form. Using n%10 to get the value of the low-order
digit is fine, but adding ’0’ to it to get the corresponding character representation is not. This addition
assumes that the machine collating sequence has all the digits in sequence with no gaps, so that ’0’+5 has
the same value as ’5’, and so on. This assumption, while true of the ASCII and EBCDIC character sets,
might not be true for some machines. The way to avoid that problem is to use a table:
- 26 -
void
printnum (n, p)
long n;
void (*p)();
{
if (n < 0) {
(*p) (’-’);
n = -n;
}
if (n >= 10)
printnum (n/10, p);
(*p) ("0123456789"[n % 10]);
}
The next problem involves what happens if n < 0. The program prints a negative sign and sets n to
-n. This assignment might overflow, because 2’s complement machines generally allow more negative values
than positive values to be represented. In particular, if a (long) integer is k bits plus one extra bit for the
sign, - 2k can be represented but 2k cannot.
There are several ways around this problem. The most obvious one is to assign n to an unsigned
long value and be done with it. However, some C compilers do not implement unsigned long, so let
us see how we can get along without it.
In both 1’s complement and 2’s complement machines, changing the sign of a positive integer is
guaranteed not to overflow. The only trouble comes when changing the sign of a negative value. Therefore,
we can avoid trouble by making sure we do not attempt to make n positive.
Of course, once we have printed the sign of a negative value, we would like to be able to treat negative
and positive numbers the same way. The way to do that is to force n to be negative after printing the
sign, and to do all our arithmetic with negative values. If we do this, we will have to ensure that the part of
the program that prints the sign is executed only once; the easiest way to do that is to split the program into
two functions:
void
printnum (n, p)
long n;
void (*p)();
{
void printneg();
if (n < 0) {
(*p) (’-’);
printneg (n, p);
} else
printneg (-n, p);
}
void
printneg (n, p)
long n;
void (*p)();
{
if (n <= -10)
printneg (n/10, p);
(*p) ("0123456789"[-(n % 10)]);
}
Printnum now just checks if the number being printed is negative; if so it prints a negative sign. In
- 27 -
either case, it calls printneg with the negative absolute value of n. We have also modified the body of
printneg to cater to the fact that n will always be a negative number or zero.
Or have we? We have used n/10 and n%10 to represent the leading digits and the trailing digit of n
(with suitable sign changes). Recall that integer division behaves in a somewhat implementation-dependent
way when one of the operands is negative. For that reason, it might actually be that n%10 is positive! In
that case, -(n%10) would be negative, and we would run off the end of our digit array.
We cater to this problem by creating two temporary variables to hold the quotient and remainder.
After we do the division, we check that the remainder is in range and adjust both variables if not. Printnum
has not changed, so we show only printneg:
void
printneg (n, p)
long n;
void (*p)();
{
long q;
int r;
q = n / 10;
r = n % 10;
if (r > 0) {
r -= 10;
q++;
}
if (n <= -10)
printneg (q, p);
(*p) ("0123456789"[-r]);
}
8. This Space Available
There are many ways for C programmers to go astray that have not been mentioned in this paper. If
you find one, please contact the author. It may well be included, with an acknowledging footnote, in a
future revision.
References
The C Programming Language (Kernighan and Ritchie, Prentice-Hall 1978) is the definitive work on
C. It contains both an excellent tutorial, aimed at people who are already familiar with other high-level languages,
and a reference manual that describes the entire language succinctly. While the language has
expanded slightly since 1978, this book is still the last word on most subjects. This book also contains the
‘‘C Reference Manual’’ we have mentioned several times in this paper.
The C Puzzle Book (Feuer, Prentice-Hall, 1982) is an unusual way to hone one’s syntactic skills. The
book is a collection of puzzles (and answers) whose solutions test the reader’s knowledge of C’s fine
points.
C: A Reference Manual (Harbison and Steele, Prentice Hall 1984) is mostly intended as a reference
source for implementors. Other users may also find it useful, particularly because of its meticulous cross
references.

C Interview Questions

http://www.exforsys.com Page 1 6/26/2004
1
C Interview Questions
Note : All the programs are tested under Turbo C/C++ compilers.
It is assumed that,
 Programs run under DOS environment,
 The underlying machine is an x86 system,
 Program is compiled using Turbo C/C++ compiler.
The program output may depend on the information based on this
assumptions (for example sizeof(int) == 2 may be assumed).
Predict the output or error(s) for the following:
1. void main()
{
int const * p=5;
printf("%d",++(*p));
}
Answer:
Compiler error: Cannot modify a constant value.
Explanation:
p is a pointer to a "constant integer". But we tried to change the
value of the "constant integer".
2. main()
{
char s[ ]="man";
int i;
for(i=0;s[ i ];i++)
printf("\n%c%c%c%c",s[ i ],*(s+i),*(i+s),i[s]);
}
Answer:
mmmm
aaaa
nnnn
Explanation:
s[i], *(i+s), *(s+i), i[s] are all different ways of expressing the
same idea. Generally array name is the base address for that array. Here s is
the base address. i is the index number/displacement from the base address. So,
indirecting it with * is same as s[i]. i[s] may be surprising. But in the case of C
it is same as s[i].
3. main()
{
float me = 1.1;
double you = 1.1;
if(me==you)
printf("I love U");
else
printf("I hate U");
}
Answer:
I hate U
http://www.exforsys.com Page 2 6/26/2004
2
Explanation:
For floating point numbers (float, double, long double) the values
cannot be predicted exactly. Depending on the number of bytes, the precession
with of the value represented varies. Float takes 4 bytes and long double takes
10 bytes. So float stores 0.9 with less precision than long double.
Rule of Thumb:
Never compare or at-least be cautious when using floating point
numbers with relational operators (== , >, <, <=, >=,!= ) .
4. main()
{
static int var = 5;
printf("%d ",var--);
if(var)
main();
}
Answer:
5 4 3 2 1
Explanation:
When static storage class is given, it is initialized once. The change
in the value of a static variable is retained even between the function calls. Main
is also treated like any other ordinary function, which can be called recursively.
5. main()
{
int c[ ]={2.8,3.4,4,6.7,5};
int j,*p=c,*q=c;
for(j=0;j<5;j++)>
printf(" %d ",*c);
++q; }
for(j=0;j<5;j++){
printf(" %d ",*p);
++p; }
}
Answer:
2 2 2 2 2 2 3 4 6 5
Explanation:
Initially pointer c is assigned to both p and q. In the first loop,
since only q is incremented and not c , the value 2 will be printed 5 times. In
second loop p itself is incremented. So the values 2 3 4 6 5 will be printed.
6. main()
{
extern int i;
i=20;
printf("%d",i);
}
Answer:
Linker Error : Undefined symbol '_i'
Explanation:
extern storage class in the following declaration,
http://www.exforsys.com Page 3 6/26/2004
3
extern int i;
specifies to the compiler that the memory for i is allocated in some other
program and that address will be given to the current program at the time of
linking. But linker finds that no other variable of name i is available in any other
program with memory space allocated for it. Hence a linker error has occurred .
7. main()
{
int i=-1,j=-1,k=0,l=2,m;
m=i++&&j++&&k++||l++;
printf("%d %d %d %d %d",i,j,k,l,m);
}
Answer:
0 0 1 3 1
Explanation :
Logical operations always give a result of 1 or 0 . And also the
logical AND (&&) operator has higher priority over the logical OR (||) operator. So
the expression ‘i++ && j++ && k++’ is executed first. The result of this
expression is 0 (-1 && -1 && 0 = 0). Now the expression is 0 || 2 which
evaluates to 1 (because OR operator always gives 1 except for ‘0 || 0’
combination- for which it gives 0). So the value of m is 1. The values of other
variables are also incremented by 1.
8. main()
{
char *p;
printf("%d %d ",sizeof(*p),sizeof(p));
}
Answer:
1 2
Explanation:
The sizeof() operator gives the number of bytes taken by its
operand. P is a character pointer, which needs one byte for storing its value (a
character). Hence sizeof(*p) gives a value of 1. Since it needs two bytes to store
the address of the character pointer sizeof(p) gives 2.
9. main()
{
int i=3;
switch(i)
{
default:printf("zero");
case 1: printf("one");
break;
case 2:printf("two");
break;
case 3: printf("three");
break;
}
}
Answer :
three
http://www.exforsys.com Page 4 6/26/2004
4
Explanation :
The default case can be placed anywhere inside the loop. It is
executed only when all other cases doesn't match.
10. main()
{
printf("%x",-1<<4);
}
Answer:
fff0
Explanation :
-1 is internally represented as all 1's. When left shifted four times
the least significant 4 bits are filled with 0's.The %x format specifier specifies that
the integer value be printed as a hexadecimal value.
11. main()
{
char string[]="Hello World";
display(string);
}
void display(char *string)
{
printf("%s",string);
}
Answer:
Compiler Error : Type mismatch in redeclaration of function display
Explanation :
In third line, when the function display is encountered, the
compiler doesn't know anything about the function display. It assumes the
arguments and return types to be integers, (which is the default type). When it
sees the actual function display, the arguments and type contradicts with what it
has assumed previously. Hence a compile time error occurs.
12. main()
{
int c=- -2;
printf("c=%d",c);
}
Answer:
c=2;
Explanation:
Here unary minus (or negation) operator is used twice. Same
maths rules applies, ie. minus * minus= plus.
Note:
However you cannot give like --2. Because -- operator can only be
applied to variables as a decrement operator (eg., i--). 2 is a constant and not a
variable.
13. #define int char
main()
{
int i=65;
printf("sizeof(i)=%d",sizeof(i));
http://www.exforsys.com Page 5 6/26/2004
5
}
Answer:
sizeof(i)=1
Explanation:
Since the #define replaces the string int by the macro char
14. main()
{
int i=10;
i=!i>14;
Printf ("i=%d",i);
}
Answer:
i=0
Explanation:
In the expression !i>14 , NOT (!) operator has more precedence
than ‘ >’ symbol. ! is a unary logical operator. !i (!10) is 0 (not of true is false).
0>14 is false (zero).
15. #include
main()
{
char s[]={'a','b','c','\n','c','\0'};
char *p,*str,*str1;
p=&s[3];
str=p;
str1=s;
printf("%d",++*p + ++*str1-32);
}
Answer:
77
Explanation:
p is pointing to character '\n'. str1 is pointing to character 'a' ++*p. "p is
pointing to '\n' and that is incremented by one." the ASCII value of '\n' is 10,
which is then incremented to 11. The value of ++*p is 11. ++*str1, str1 is
pointing to 'a' that is incremented by 1 and it becomes 'b'. ASCII value of 'b' is
98.
Now performing (11 + 98 – 32), we get 77("M");
So we get the output 77 :: "M" (Ascii is 77).
16. #include
main()
{
int a[2][2][2] = { {10,2,3,4}, {5,6,7,8} };
int *p,*q;
p=&a[2][2][2];
*q=***a;
printf("%d----%d",*p,*q);
}
Answer:
SomeGarbageValue---1
http://www.exforsys.com Page 6 6/26/2004
6
Explanation:
p=&a[2][2][2] you declare only two 2D arrays, but you are trying
to access the third 2D(which you are not declared) it will print garbage values.
*q=***a starting address of a is assigned integer pointer. Now q is pointing to
starting address of a. If you print *q, it will print first element of 3D array.
17. #include
main()
{
struct xx
{
int x=3;
char name[]="hello";
};
struct xx *s;
printf("%d",s->x);
printf("%s",s->name);
}
Answer:
Compiler Error
Explanation:
You should not initialize variables in declaration
18. #include
main()
{
struct xx
{
int x;
struct yy
{
char s;
struct xx *p;
};
struct yy *q;
};
}
Answer:
Compiler Error
Explanation:
The structure yy is nested within structure xx. Hence, the elements
are of yy are to be accessed through the instance of structure xx, which needs an
instance of yy to be known. If the instance is created after defining the structure
the compiler will not know about the instance relative to xx. Hence for nested
structure yy you have to declare member.
19. main()
{
printf("\nab");
printf("\bsi");
printf("\rha");
}
Answer:
http://www.exforsys.com Page 7 6/26/2004
7
hai
Explanation:
\n - newline
\b - backspace
\r - linefeed
20. main()
{
int i=5;
printf("%d%d%d%d%d%d",i++,i--,++i,--i,i);
}
Answer:
45545
Explanation:
The arguments in a function call are pushed into the stack from left
to right. The evaluation is by popping out from the stack. and the evaluation is
from right to left, hence the result.
21. #define square(x) x*x
main()
{
int i;
i = 64/square(4);
printf("%d",i);
}
Answer:
64
Explanation:
the macro call square(4) will substituted by 4*4 so the expression
becomes i = 64/4*4 . Since / and * has equal priority the expression will be
evaluated as (64/4)*4 i.e. 16*4 = 64
22. main()
{
char *p="hai friends",*p1;
p1=p;
while(*p!='\0') ++*p++;
printf("%s %s",p,p1);
}
Answer:
ibj!gsjfoet
Explanation:
++*p++ will be parse in the given order
 *p that is value at the location currently pointed by p will be taken
 ++*p the retrieved value will be incremented
 when ; is encountered the location will be incremented that is p++ will be
executed
Hence, in the while loop initial value pointed by p is ‘h’, which is changed to ‘i’ by
executing ++*p and pointer moves to point, ‘a’ which is similarly changed to ‘b’
and so on. Similarly blank space is converted to ‘!’. Thus, we obtain value in p
becomes “ibj!gsjfoet” and since p reaches ‘\0’ and p1 points to p thus p1doesnot
print anything.
http://www.exforsys.com Page 8 6/26/2004
8
23. #include
#define a 10
main()
{
#define a 50
printf("%d",a);
}
Answer:
50
Explanation:
The preprocessor directives can be redefined anywhere in the
program. So the most recently assigned value will be taken.
24. #define clrscr() 100
main()
{
clrscr();
printf("%d\n",clrscr());
}
Answer:
100
Explanation:
Preprocessor executes as a seperate pass before the execution of
the compiler. So textual replacement of clrscr() to 100 occurs.The input program
to compiler looks like this :
main()
{
100;
printf("%d\n",100);
}
Note:
100; is an executable statement but with no action. So it doesn't
give any problem
25. main()
{
41printf("%p",main);
}8Answer:
Some address will be printed.
Explanation:
Function names are just addresses (just like array names are
addresses).
main() is also a function. So the address of function main will be printed. %p in
printf specifies that the argument is an address. They are printed as hexadecimal
numbers.
27) main()
{
clrscr();
}
clrscr();
Answer:
http://www.exforsys.com Page 9 6/26/2004
9
No output/error
Explanation:
The first clrscr() occurs inside a function. So it becomes a function
call. In the second clrscr(); is a function declaration (because it is
not inside any function).
28) enum colors {BLACK,BLUE,GREEN}
main()
{
printf("%d..%d..%d",BLACK,BLUE,GREEN);
return(1);
}
Answer:
0..1..2
Explanation:
enum assigns numbers starting from 0, if not explicitly defined.
29) void main()
{
char far *farther,*farthest;
printf("%d..%d",sizeof(farther),sizeof(farthest));
}
Answer:
4..2
Explanation:
the second pointer is of char type and not a far pointer
30) main()
{
int i=400,j=300;
printf("%d..%d");
}
Answer:
400..300
Explanation:
printf takes the values of the first two assignments of the program.
Any number of printf's may be given. All of them take only the first
two values. If more number of assignments given in the
program,then printf will take garbage values.
31) main()
{
char *p;
p="Hello";
printf("%c\n",*&*p);
}
Answer:
H
Explanation:
http://www.exforsys.com Page 10 6/26/2004
10
* is a dereference operator & is a reference operator. They can be
applied any number of times provided it is meaningful. Here p
points to the first character in the string "Hello". *p dereferences it
and so its value is H. Again & references it to an address and *
dereferences it to the value H.
32) main()
{
int i=1;
while (i<=5)
{
printf("%d",i);
if (i>2)
goto here;
i++;
}
}
fun()
{
here:
printf("PP");
}
Answer:
Compiler error: Undefined label 'here' in function main
Explanation:
Labels have functions scope, in other words the scope of the labels
is limited to functions. The label 'here' is available in function fun()
Hence it is not visible in function main.
33) main()
{
static char names[5][20]={"pascal","ada","cobol","fortran","perl"};
int i;
char *t;
t=names[3];
names[3]=names[4];
names[4]=t;
for (i=0;i<=4;i++)
printf("%s",names[i]);
}
Answer:
Compiler error: Lvalue required in function main
Explanation:
Array names are pointer constants. So it cannot be modified.
34) void main()
{
int i=5;
printf("%d",i++ + ++i);
}
Answer:
Output Cannot be predicted exactly.
Explanation:
http://www.exforsys.com Page 11 6/26/2004
11
Side effects are involved in the evaluation of i
35) void main()
{
int i=5;
printf("%d",i+++++i);
}
Answer:
Compiler Error
Explanation:
The expression i+++++i is parsed as i ++ ++ + i which is an
illegal combination of operators.
36) #include
main()
{
int i=1,j=2;
switch(i)
{
case 1: printf("GOOD");
break;
case j: printf("BAD");
break;
}
}
Answer:
Compiler Error: Constant expression required in function main.
Explanation:
The case statement can have only constant expressions (this
implies that we cannot use variable names directly so an error).
Note:
Enumerated types can be used in case statements.
37) main()
{
int i;
printf("%d",scanf("%d",&i)); // value 10 is given as input here
}
Answer:
1
Explanation:
Scanf returns number of items successfully read and not 1/0. Here
10 is given as input which should have been scanned successfully.
So number of items read is 1.
38) #define f(g,g2) g##g2
main()
{
int var12=100;
printf("%d",f(var,12));
}
Answer:
100
http://www.exforsys.com Page 12 6/26/2004
12
39) main()
{
int i=0;
for(;i++;printf("%d",i)) ;
printf("%d",i);
}
Answer:
1
Explanation:
before entering into the for loop the checking condition is
"evaluated". Here it evaluates to 0 (false) and comes out of the
loop, and i is incremented (note the semicolon after the for loop).
40) #include
main()
{
char s[]={'a','b','c','\n','c','\0'};
char *p,*str,*str1;
p=&s[3];
str=p;
str1=s;
printf("%d",++*p + ++*str1-32);
}
Answer:
M
Explanation:
p is pointing to character '\n'.str1 is pointing to character 'a' ++*p
meAnswer:"p is pointing to '\n' and that is incremented by one."
the ASCII value of '\n' is 10. then it is incremented to 11. the value
of ++*p is 11. ++*str1 meAnswer:"str1 is pointing to 'a' that is
incremented by 1 and it becomes 'b'. ASCII value of 'b' is 98. both
11 and 98 is added and result is subtracted from 32.
i.e. (11+98-32)=77("M");
41) #include
main()
{
struct xx
{
int x=3;
char name[]="hello";
};
struct xx *s=malloc(sizeof(struct xx));
printf("%d",s->x);
printf("%s",s->name);
}
Answer:
Compiler Error
Explanation:
Initialization should not be done for structure members inside the
structure declaration
http://www.exforsys.com Page 13 6/26/2004
13
42) #include
main()
{
struct xx
{
int x;
struct yy
{
char s;
struct xx *p;
};
struct yy *q;
};
}
Answer:
Compiler Error
Explanation:
in the end of nested structure yy a member have to be declared.
43) main()
{
extern int i;
i=20;
printf("%d",sizeof(i));
}
Answer:
Linker error: undefined symbol '_i'.
Explanation:
extern declaration specifies that the variable i is defined
somewhere else. The compiler passes the external variable to be
resolved by the linker. So compiler doesn't find an error. During
linking the linker searches for the definition of i. Since it is not
found the linker flags an error.
44) main()
{
printf("%d", out);
}
int out=100;
Answer:
Compiler error: undefined symbol out in function main.
Explanation:
The rule is that a variable is available for use from the point of
declaration. Even though a is a global variable, it is not available
for main. Hence an error.
45) main()
{
extern out;
printf("%d", out);
}
int out=100;
http://www.exforsys.com Page 14 6/26/2004
14
Answer:
100
Explanation:
This is the correct way of writing the previous program.
46) main()
{
show();
}
void show()
{
printf("I'm the greatest");
}
Answer:
Compier error: Type mismatch in redeclaration of show.
Explanation:
When the compiler sees the function show it doesn't know anything
about it. So the default return type (ie, int) is assumed. But when
compiler sees the actual definition of show mismatch occurs since it
is declared as void. Hence the error.
The solutions are as follows:
1. declare void show() in main() .
2. define show() before main().
3. declare extern void show() before the use of show().
47) main( )
{
int a[2][3][2] = {{{2,4},{7,8},{3,4}},{{2,2},{2,3},{3,4}}};
printf(“%u %u %u %d \n”,a,*a,**a,***a);
printf(“%u %u %u %d \n”,a+1,*a+1,**a+1,***a+1);
}
Answer:
100, 100, 100, 2
114, 104, 102, 3
Explanation:
The given array is a 3-D one. It can also be viewed as a 1-D array.
2 4 7 8 3 4 2 2 2 3 3 4
100 102 104 106 108 110 112 114 116 118 120 122
thus, for the first printf statement a, *a, **a give address of first
element . since the indirection ***a gives the value. Hence, the
first line of the output.
for the second printf a+1 increases in the third dimension thus
points to value at 114, *a+1 increments in second dimension thus
points to 104, **a +1 increments the first dimension thus points to
102 and ***a+1 first gets the value at first location and then
increments it by 1. Hence, the output.
48) main( )
{
int a[ ] = {10,20,30,40,50},j,*p;
http://www.exforsys.com Page 15 6/26/2004
15
for(j=0; j<5;>
{
printf(“%d” ,*a);
a++;
}
p = a;
for(j=0; j<5;>
{
printf(“%d ” ,*p);
p++;
}
}
Answer:
Compiler error: lvalue required.
Explanation:
Error is in line with statement a++. The operand must be an lvalue
and may be of any of scalar type for the any operator, array name
only when subscripted is an lvalue. Simply array name is a nonmodifiable
lvalue.
**49) main( )
{
static int a[ ] = {0,1,2,3,4};
int *p[ ] = {a,a+1,a+2,a+3,a+4};
int **ptr = p;
ptr++;
printf(“\n %d %d %d”, ptr-p, *ptr-a, **ptr);
*ptr++;
printf(“\n %d %d %d”, ptr-p, *ptr-a, **ptr);
*++ptr;
printf(“\n %d %d %d”, ptr-p, *ptr-a, **ptr);
++*ptr;
printf(“\n %d %d %d”, ptr-p, *ptr-a, **ptr);
}
Answer:
111
222
333
344
Explanation:
Let us consider the array and the two pointers with some address
a
0 1 2 3 4
100 102 104 106 108
p
100 102 104 106 108
1000 1002 1004 1006 1008
ptr
1000
2000
After execution of the instruction ptr++ value in ptr becomes 1002,
if scaling factor for integer is 2 bytes. Now ptr – p is value in ptr –
http://www.exforsys.com Page 16 6/26/2004
16
starting location of array p, (1002 – 1000) / (scaling factor) = 1,
*ptr – a = value at address pointed by ptr – starting value of array
a, 1002 has a value 102 so the value is (102 – 100)/(scaling
factor) = 1, **ptr is the value stored in the location pointed by
the pointer of ptr = value pointed by value pointed by 1002 =
value pointed by 102 = 1. Hence the output of the firs printf is 1,
1, 1.
After execution of *ptr++ increments value of the value in ptr by
scaling factor, so it becomes1004. Hence, the outputs for the
second printf are ptr – p = 2, *ptr – a = 2, **ptr = 2.
After execution of *++ptr increments value of the value in ptr by
scaling factor, so it becomes1004. Hence, the outputs for the third
printf are ptr – p = 3, *ptr – a = 3, **ptr = 3.
After execution of ++*ptr value in ptr remains the same, the value
pointed by the value is incremented by the scaling factor. So the
value in array p at location 1006 changes from 106 10 108,.
Hence, the outputs for the fourth printf are ptr – p = 1006 – 1000
= 3, *ptr – a = 108 – 100 = 4, **ptr = 4.
50) main( )
{
char *q;
int j;
for (j=0; j<3;>
for (j=0; j<3;>
for (j=0; j<3;>
}
Explanation:
Here we have only one pointer to type char and since we take input
in the same pointer thus we keep writing over in the same location,
each time shifting the pointer value by 1. Suppose the inputs are
MOUSE, TRACK and VIRTUAL. Then for the first input suppose the
pointer starts at location 100 then the input one is stored as
M O U S E \0
When the second input is given the pointer is incremented as j
value becomes 1, so the input is filled in memory starting from
101.
M T R A C K \0
The third input starts filling from the location 102
M T V I R T U A L \0
This is the final value stored .
The first printf prints the values at the position q, q+1 and q+2 =
M T V
The second printf prints three strings starting from locations q,
q+1, q+2
i.e MTVIRTUAL, TVIRTUAL and VIRTUAL.
51) main( )
{
void *vp;
char ch = ‘g’, *cp = “goofy”;
int j = 20;
http://www.exforsys.com Page 17 6/26/2004
17
vp = &ch;
printf(“%c”, *(char *)vp);
vp = &j;
printf(“%d”,*(int *)vp);
vp = cp;
printf(“%s”,(char *)vp + 3);
}
Answer:
g20fy
Explanation:
Since a void pointer is used it can be type casted to any other type
pointer. vp = &ch stores address of char ch and the next
statement prints the value stored in vp after type casting it to the
proper data type pointer. the output is ‘g’. Similarly the output
from second printf is ‘20’. The third printf statement type casts it to
print the string from the 4th value hence the output is ‘fy’.
52) main ( )
{
static char *s[ ] = {“black”, “white”, “yellow”, “violet”};
char **ptr[ ] = {s+3, s+2, s+1, s}, ***p;
p = ptr;
**++p;
printf(“%s”,*--*++p + 3);
}
Answer:
ck
Explanation:
In this problem we have an array of char pointers pointing to start
of 4 strings. Then we have ptr which is a pointer to a pointer of
type char and a variable p which is a pointer to a pointer to a
pointer of type char. p hold the initial value of ptr, i.e. p = s+3.
The next statement increment value in p by 1 , thus now value of p
= s+2. In the printf statement the expression is evaluated *++p
causes gets value s+1 then the pre decrement is executed and we
get s+1 – 1 = s . the indirection operator now gets the value from
the array of s and adds 3 to the starting address. The string is
printed starting from this position. Thus, the output is ‘ck’.
53) main()
{
int i, n;
char *x = “girl”;
n = strlen(x);
*x = x[n];
for(i=0; i
{
printf(“%s\n”,x);
x++;
}
}
Answer:
(blank space)
http://www.exforsys.com Page 18 6/26/2004
18
irl
rl
l
Explanation:
Here a string (a pointer to char) is initialized with a value “girl”.
The strlen function returns the length of the string, thus n has a
value 4. The next statement assigns value at the nth location (‘\0’)
to the first location. Now the string becomes “\0irl” . Now the printf
statement prints the string after each iteration it increments it
starting position. Loop starts from 0 to 4. The first time x[0] = ‘\0’
hence it prints nothing and pointer value is incremented. The
second time it prints from x[1] i.e “irl” and the third time it prints
“rl” and the last time it prints “l” and the loop terminates.
54) int i,j;
for(i=0;i<=10;i++)
{
j+=5;
assert(i<5);
}
Answer:
Runtime error: Abnormal program termination.
assert failed (i<5),>,
Explanation:
asserts are used during debugging to make sure that certain
conditions are satisfied. If assertion fails, the program will
terminate reporting the same. After debugging use,
#undef NDEBUG
and this will disable all the assertions from the source code.
Assertion
is a good debugging tool to make use of.
55) main()
{
int i=-1;
+i;
printf("i = %d, +i = %d \n",i,+i);
}
Answer:
i = -1, +i = -1
Explanation:
Unary + is the only dummy operator in C. Where-ever it comes
you can just ignore it just because it has no effect in the
expressions (hence the name dummy operator).
56) What are the files which are automatically opened when a C file is
executed?
Answer:
stdin, stdout, stderr (standard input,standard output,standard
error).
57) what will be the position of the file marker?
a: fseek(ptr,0,SEEK_SET);
http://www.exforsys.com Page 19 6/26/2004
19
b: fseek(ptr,0,SEEK_CUR);
Answer :
a: The SEEK_SET sets the file position marker to the starting of the
file.
b: The SEEK_CUR sets the file position marker to the current
position
of the file.
58) main()
{
char name[10],s[12];
scanf(" \"%[^\"]\"",s);
}
How scanf will execute?
Answer:
First it checks for the leading white space and discards it.Then it
matches with a quotation mark and then it reads all character upto
another quotation mark.
59) What is the problem with the following code segment?
while ((fgets(receiving array,50,file_ptr)) != EOF)
;
Answer & Explanation:
fgets returns a pointer. So the correct end of file check is checking
for != NULL.
60) main()
{
main();
}
Answer:
Runtime error : Stack overflow.
Explanation:
main function calls itself again and again. Each time the function is
called its return address is stored in the call stack. Since there is
no condition to terminate the function call, the call stack overflows
at runtime. So it terminates the program and results in an error.
61) main()
{
char *cptr,c;
void *vptr,v;
c=10; v=0;
cptr=&c; vptr=&v;
printf("%c%v",c,v);
}
Answer:
Compiler error (at line number 4): size of v is Unknown.
Explanation:
You can create a variable of type void * but not of type void, since
void is an empty type. In the second line you are creating variable
vptr of type void * and v of type void hence an error.
http://www.exforsys.com Page 20 6/26/2004
20
62) main()
{
char *str1="abcd";
char str2[]="abcd";
printf("%d %d %d",sizeof(str1),sizeof(str2),sizeof("abcd"));
}
Answer:
2 5 5
Explanation:
In first sizeof, str1 is a character pointer so it gives you the size of
the pointer variable. In second sizeof the name str2 indicates the
name of the array whose size is 5 (including the '\0' termination
character). The third sizeof is similar to the second one.
63) main()
{
char not;
not=!2;
printf("%d",not);
}
Answer:
0
Explanation:
! is a logical operator. In C the value 0 is considered to be the
boolean value FALSE, and any non-zero value is considered to be
the boolean value TRUE. Here 2 is a non-zero value so TRUE.
!TRUE is FALSE (0) so it prints 0.
64) #define FALSE -1
#define TRUE 1
#define NULL 0
main() {
if(NULL)
puts("NULL");
else if(FALSE)
puts("TRUE");
else
puts("FALSE");
}
Answer:
TRUE
Explanation:
The input program to the compiler after processing by the
preprocessor is,
main(){
if(0)
puts("NULL");
else if(-1)
puts("TRUE");
else
puts("FALSE");
}
http://www.exforsys.com Page 21 6/26/2004
21
Preprocessor doesn't replace the values given inside the double
quotes. The check by if condition is boolean value false so it goes
to else. In second if -1 is boolean value true hence "TRUE" is
printed.
65) main()
{
int k=1;
printf("%d==1 is ""%s",k,k==1?"TRUE":"FALSE");
}
Answer:
1==1 is TRUE
Explanation:
When two strings are placed together (or separated by whitespace)
they are concatenated (this is called as "stringization"
operation). So the string is as if it is given as "%d==1 is %s". The
conditional operator( ?: ) evaluates to "TRUE".
66) main()
{
int y;
scanf("%d",&y); // input given is 2000
if( (y%4==0 && y%100 != 0) || y%100 == 0 )
printf("%d is a leap year");
else
printf("%d is not a leap year");
}
Answer:
2000 is a leap year
Explanation:
An ordinary program to check if leap year or not.
67) #define max 5
#define int arr1[max]
main()
{
typedef char arr2[max];
arr1 list={0,1,2,3,4};
arr2 name="name";
printf("%d %s",list[0],name);
}
Answer:
Compiler error (in the line arr1 list = {0,1,2,3,4})
Explanation:
arr2 is declared of type array of size 5 of characters. So it can be
used to declare the variable name of the type arr2. But it is not the
case of arr1. Hence an error.
Rule of Thumb:
#defines are used for textual replacement whereas typedefs are
used for declaring new types.
68) int i=10;
main()
http://www.exforsys.com Page 22 6/26/2004
22
{
extern int i;
{
int i=20;
{
const volatile unsigned i=30;
printf("%d",i);
}
printf("%d",i);
}
printf("%d",i);
}
Answer:
30,20,10
Explanation:
'{' introduces new block and thus new scope. In the innermost
block i is declared as,
const volatile unsigned
which is a valid declaration. i is assumed of type int. So printf
prints 30. In the next block, i has value 20 and so printf prints 20.
In the outermost block, i is declared as extern, so no storage space
is allocated for it. After compilation is over the linker resolves it to
global variable i (since it is the only variable visible there). So it
prints i's value as 10.
69) main()
{
int *j;
{
int i=10;
j=&i;
}
printf("%d",*j);
}
Answer:
10
Explanation:
The variable i is a block level variable and the visibility is inside
that block only. But the lifetime of i is lifetime of the function so it
lives upto the exit of main function. Since the i is still allocated
space, *j prints the value stored in i since j points i.
70) main()
{
int i=-1;
-i;
printf("i = %d, -i = %d \n",i,-i);
}
Answer:
i = -1, -i = 1
Explanation:
http://www.exforsys.com Page 23 6/26/2004
23
-i is executed and this execution doesn't affect the value of i. In
printf first you just print the value of i. After that the value of the
expression -i = -(-1) is printed.
71) #include
main()
{
const int i=4;
float j;
j = ++i;
printf("%d %f", i,++j);
}
Answer:
Compiler error
Explanation:
i is a constant. you cannot change the value of constant
72) #include
main()
{
int a[2][2][2] = { {10,2,3,4}, {5,6,7,8} };
int *p,*q;
p=&a[2][2][2];
*q=***a;
printf("%d..%d",*p,*q);
}
Answer:
garbagevalue..1
Explanation:
p=&a[2][2][2] you declare only two 2D arrays. but you are trying
to access the third 2D(which you are not declared) it will print
garbage values. *q=***a starting address of a is assigned integer
pointer. now q is pointing to starting address of a.if you print *q
meAnswer:it will print first element of 3D array.
73) #include
main()
{
register i=5;
char j[]= "hello";
printf("%s %d",j,i);
}
Answer:
hello 5
Explanation:
if you declare i as register compiler will treat it as ordinary integer
and it will take integer value. i value may be stored either in
register or in memory.
74) main()
{
int i=5,j=6,z;
printf("%d",i+++j);
http://www.exforsys.com Page 24 6/26/2004
24
}
Answer:
11
Explanation:
the expression i+++j is treated as (i++ + j)
76) struct aaa{
struct aaa *prev;
int i;
struct aaa *next;
};
main()
{
struct aaa abc,def,ghi,jkl;
int x=100;
abc.i=0;abc.prev=&jkl;
abc.next=&def;
def.i=1;def.prev=&abc;def.next=&ghi;
ghi.i=2;ghi.prev=&def;
ghi.next=&jkl;
jkl.i=3;jkl.prev=&ghi;jkl.next=&abc;
x=abc.next->next->prev->next->i;
printf("%d",x);
}
Answer:
2
Explanation:
above all statements form a double circular linked list;
abc.next->next->prev->next->i
this one points to "ghi" node the value of at particular node is 2.
77) struct point
{
int x;
int y;
};
struct point origin,*pp;
main()
{
pp=&origin;
printf("origin is(%d%d)\n",(*pp).x,(*pp).y);
printf("origin is (%d%d)\n",pp->x,pp->y);
}
Answer:
origin is(0,0)
origin is(0,0)
Explanation:
pp is a pointer to structure. we can access the elements of the
structure either with arrow mark or with indirection operator.
Note:
Since structure point is globally declared x & y are initialized as
zeroes
http://www.exforsys.com Page 25 6/26/2004
25
78) main()
{
int i=_l_abc(10);
printf("%d\n",--i);
}
int _l_abc(int i)
{
return(i++);
}
Answer:
9
Explanation:
return(i++) it will first return i and then increments. i.e. 10 will be
returned.
79) main()
{
char *p;
int *q;
long *r;
p=q=r=0;
p++;
q++;
r++;
printf("%p...%p...%p",p,q,r);
}
Answer:
0001...0002...0004
Explanation:
++ operator when applied to pointers increments address
according to their corresponding data-types.
80) main()
{
char c=' ',x,convert(z);
getc(c);
if((c>='a') && (c<='z'))
x=convert(c);
printf("%c",x);
}
convert(z)
{
return z-32;
}
Answer:
Compiler error
Explanation:
declaration of convert and format of getc() are wrong.
81) main(int argc, char **argv)
{
printf("enter the character");
http://www.exforsys.com Page 26 6/26/2004
26
getchar();
sum(argv[1],argv[2]);
}
sum(num1,num2)
int num1,num2;
{
return num1+num2;
}
Answer:
Compiler error.
Explanation:
argv[1] & argv[2] are strings. They are passed to the function sum
without converting it to integer values.
82) # include
int one_d[]={1,2,3};
main()
{
int *ptr;
ptr=one_d;
ptr+=3;
printf("%d",*ptr);
}
Answer:
garbage value
Explanation:
ptr pointer is pointing to out of the array range of one_d.
83) # include
aaa() {
printf("hi");
}
bbb(){
printf("hello");
}
ccc(){
printf("bye");
}
main()
{
int (*ptr[3])();
ptr[0]=aaa;
ptr[1]=bbb;
ptr[2]=ccc;
ptr[2]();
}
Answer:
bye
Explanation:
ptr is array of pointers to functions of return type int.ptr[0] is
assigned to address of the function aaa. Similarly ptr[1] and ptr[2]
for bbb and ccc respectively. ptr[2]() is in effect of writing ccc(),
since ptr[2] points to ccc.
http://www.exforsys.com Page 27 6/26/2004
27
85) #include
main()
{
FILE *ptr;
char i;
ptr=fopen("zzz.c","r");
while((i=fgetch(ptr))!=EOF)
printf("%c",i);
}
Answer:
contents of zzz.c followed by an infinite loop
Explanation:
The condition is checked against EOF, it should be checked against
NULL.
86) main()
{
int i =0;j=0;
if(i && j++)
printf("%d..%d",i++,j);
printf("%d..%d,i,j);
}
Answer:
0..0
Explanation:
The value of i is 0. Since this information is enough to determine
the truth value of the boolean expression. So the statement
following the if statement is not executed. The values of i and j
remain unchanged and get printed.
87) main()
{
int i;
i = abc();
printf("%d",i);
}
abc()
{
_AX = 1000;
}
Answer:
1000
Explanation:
Normally the return value from the function is through the
information from the accumulator. Here _AH is the pseudo global
variable denoting the accumulator. Hence, the value of the
accumulator is set 1000 so the function returns value 1000.
88) int i;
main(){
int t;
for ( t=4;scanf("%d",&i)-t;printf("%d\n",i))
http://www.exforsys.com Page 28 6/26/2004
28
printf("%d--",t--);
}
// If the inputs are 0,1,2,3 find the o/p
Answer:
4--0
3--1
2--2
Explanation:
Let us assume some x= scanf("%d",&i)-t the values during
execution
will be,
t i x
4 0 -4
3 1 -2
2 2 0
89) main(){
int a= 0;int b = 20;char x =1;char y =10;
if(a,b,x,y)
printf("hello");
}
Answer:
hello
Explanation:
The comma operator has associativity from left to right. Only the
rightmost value is returned and the other values are evaluated and
ignored. Thus the value of last variable y is returned to check in if.
Since it is a non zero value if becomes true so, "hello" will be
printed.
90) main(){
unsigned int i;
for(i=1;i>-2;i--)
printf("c aptitude");
}
Explanation:
i is an unsigned integer. It is compared with a signed value. Since
the both types doesn't match, signed is promoted to unsigned
value. The unsigned equivalent of -2 is a huge value so condition
becomes false and control comes out of the loop.
91) In the following pgm add a stmt in the function fun such that the address
of
'a' gets stored in 'j'.
main(){
int * j;
void fun(int **);
fun(&j);
}
void fun(int **k) {
int a =0;
/* add a stmt here*/
}
http://www.exforsys.com Page 29 6/26/2004
29
Answer:
*k = &a
Explanation:
The argument of the function is a pointer to a pointer.
92) What are the following notations of defining functions known as?
i. int abc(int a,float b)
{
/* some code */
}
ii. int abc(a,b)
int a; float b;
{
/* some code*/
}
Answer:
i. ANSI C notation
ii. Kernighan & Ritche notation
93) main()
{
char *p;
p="%d\n";
p++;
p++;
printf(p-2,300);
}
Answer:
300
Explanation:
The pointer points to % since it is incremented twice and again
decremented by 2, it points to '%d\n' and 300 is printed.
94) main(){
char a[100];
a[0]='a';a[1]]='b';a[2]='c';a[4]='d';
abc(a);
}
abc(char a[]){
a++;
printf("%c",*a);
a++;
printf("%c",*a);
}
Explanation:
The base address is modified only in function and as a result a
points to 'b' then after incrementing to 'c' so bc will be printed.
95) func(a,b)
int a,b;
{
return( a= (a==b) );
}
http://www.exforsys.com Page 30 6/26/2004
30
main()
{
int process(),func();
printf("The value of process is %d !\n ",process(func,3,6));
}
process(pf,val1,val2)
int (*pf) ();
int val1,val2;
{
return((*pf) (val1,val2));
}
Answer:
The value if process is 0 !
Explanation:
The function 'process' has 3 parameters - 1, a pointer to another
function 2 and 3, integers. When this function is invoked from
main, the following substitutions for formal parameters take place:
func for pf, 3 for val1 and 6 for val2. This function returns the
result of the operation performed by the function 'func'. The
function func has two integer parameters. The formal parameters
are substituted as 3 for a and 6 for b. since 3 is not equal to 6,
a==b returns 0. therefore the function returns 0 which in turn is
returned by the function 'process'.
96) void main()
{
static int i=5;
if(--i){
main();
printf("%d ",i);
}
}
Answer:
0 0 0 0
Explanation:
The variable "I" is declared as static, hence memory for I will be
allocated for only once, as it encounters the statement. The function
main() will be called recursively unless I becomes equal to 0, and since
main() is recursively called, so the value of static I ie., 0 will be printed
every time the control is returned.
97) void main()
{
int k=ret(sizeof(float));
printf("\n here value is %d",++k);
}
int ret(int ret)
{
ret += 2.5;
return(ret);
}
Answer:
Here value is 7
http://www.exforsys.com Page 31 6/26/2004
31
Explanation:
The int ret(int ret), ie., the function name and the argument name
can be the same.
Firstly, the function ret() is called in which the sizeof(float) ie., 4 is
passed, after the first expression the value in ret will be 6, as ret is
integer hence the value stored in ret will have implicit type conversion
from float to int. The ret is returned in main() it is printed after and
preincrement.
98) void main()
{
char a[]="12345\0";
int i=strlen(a);
printf("here in 3 %d\n",++i);
}
Answer:
here in 3 6
Explanation:
The char array 'a' will hold the initialized string, whose length will
be counted from 0 till the null character. Hence the 'I' will hold the value
equal to 5, after the pre-increment in the printf statement, the 6 will be
printed.
99) void main()
{
unsigned giveit=-1;
int gotit;
printf("%u ",++giveit);
printf("%u \n",gotit=--giveit);
}
Answer:
0 65535
Explanation:
100) void main()
{
int i;
char a[]="\0";
if(printf("%s\n",a))
printf("Ok here \n");
else
printf("Forget it\n");
}
Answer:
Ok here
Explanation:
Printf will return how many characters does it print. Hence
printing a null character returns 1 which makes the if
statement true, thus "Ok here" is printed.
101) void main()
{
void *v;
http://www.exforsys.com Page 32 6/26/2004
32
int integer=2;
int *i=&integer;
v=i;
printf("%d",(int*)*v);
}
Answer:
Compiler Error. We cannot apply indirection on type void*.
Explanation:
Void pointer is a generic pointer type. No pointer arithmetic can
be done on it. Void pointers are normally used for,
1. Passing generic pointers to functions and returning such
pointers.
2. As a intermediate pointer type.
3. Used when the exact pointer type will be known at a later
point of time.
102) void main()
{
int i=i++,j=j++,k=k++;
printf(“%d%d%d”,i,j,k);
}
Answer:
Garbage values.
Explanation:
An identifier is available to use in program code from the point of
its declaration.
So expressions such as i = i++ are valid statements. The i, j and k
are automatic variables and so they contain some garbage value.
Garbage in is garbage out (GIGO).
103) void main()
{
static int i=i++, j=j++, k=k++;
printf(“i = %d j = %d k = %d”, i, j, k);
}
Answer:
i = 1 j = 1 k = 1
Explanation:
Since static variables are initialized to zero by default.
104) void main()
{
while(1){
if(printf("%d",printf("%d")))
break;
else
continue;
}
}
Answer:
Garbage values
Explanation:
http://www.exforsys.com Page 33 6/26/2004
33
The inner printf executes first to print some garbage value. The
printf returns no of characters printed and this value also cannot be
predicted. Still the outer printf prints something and so returns a
non-zero value. So it encounters the break statement and comes
out of the while statement.
104) main()
{
unsigned int i=10;
while(i-->=0)
printf("%u ",i);
}
Answer:
10 9 8 7 6 5 4 3 2 1 0 65535 65534…..
Explanation:
Since i is an unsigned integer it can never become negative. So the
expression i-- >=0 will always be true, leading to an infinite loop.
105) #include
main()
{
int x,y=2,z,a;
if(x=y%2) z=2;
a=2;
printf("%d %d ",z,x);
}
Answer:
Garbage-value 0
Explanation:
The value of y%2 is 0. This value is assigned to x. The condition
reduces to if (x) or in other words if(0) and so z goes uninitialized.
Thumb Rule: Check all control paths to write bug free code.
106) main()
{
int a[10];
printf("%d",*a+1-*a+3);
}
Answer:
4
Explanation:
*a and -*a cancels out. The result is as simple as 1 + 3 = 4 !
107) #define prod(a,b) a*b
main()
{
int x=3,y=4;
printf("%d",prod(x+2,y-1));
}
Answer:
10
http://www.exforsys.com Page 34 6/26/2004
34
Explanation:
The macro expands and evaluates to as:
x+2*y-1 => x+(2*y)-1 => 10
108) main()
{
unsigned int i=65000;
while(i++!=0);
printf("%d",i);
}
Answer:
1
Explanation:
Note the semicolon after the while statement. When the value of i
becomes 0 it comes out of while loop. Due to post-increment on i
the value of i while printing is 1.
109) main()
{
int i=0;
while(+(+i--)!=0)
i-=i++;
printf("%d",i);
}
Answer:
-1
Explanation:
Unary + is the only dummy operator in C. So it has no effect on
the expression and now the while loop is, while(i--!=0) which is
false and so breaks out of while loop. The value –1 is printed due
to the post-decrement operator.
113) main()
{
float f=5,g=10;
enum{i=10,j=20,k=50};
printf("%d\n",++k);
printf("%f\n",f<<2);
printf("%lf\n",f%g);
printf("%lf\n",fmod(f,g));
}
Answer:
Line no 5: Error: Lvalue required
Line no 6: Cannot apply leftshift to float
Line no 7: Cannot apply mod to float
Explanation:
Enumeration constants cannot be modified, so you cannot apply
++.
Bit-wise operators and % operators cannot be applied on float
values.
fmod() is to find the modulus values for floats as % operator is for
ints.
http://www.exforsys.com Page 35 6/26/2004
35
110) main()
{
int i=10;
void pascal f(int,int,int);
f(i++,i++,i++);
printf(" %d",i);
}
void pascal f(integer :i,integer:j,integer :k)
{
write(i,j,k);
}
Answer:
Compiler error: unknown type integer
Compiler error: undeclared function write
Explanation:
Pascal keyword doesn’t mean that pascal code can be used. It
means that the function follows Pascal argument passing mechanism in
calling the functions.
111) void pascal f(int i,int j,int k)
{
printf(“%d %d %d”,i, j, k);
}
void cdecl f(int i,int j,int k)
{
printf(“%d %d %d”,i, j, k);
}
main()
{
int i=10;
f(i++,i++,i++);
printf(" %d\n",i);
i=10;
f(i++,i++,i++);
printf(" %d",i);
}
Answer:
10 11 12 13
12 11 10 13
Explanation:
Pascal argument passing mechanism forces the arguments to be
called from left to right. cdecl is the normal C argument passing
mechanism where the arguments are passed from right to left.
112). What is the output of the program given below
main()
{
signed char i=0;
for(;i>=0;i++) ;
printf("%d\n",i);
}
Answer
http://www.exforsys.com Page 36 6/26/2004
36
-128
Explanation
Notice the semicolon at the end of the for loop. THe initial
value of the i is set to 0. The inner loop executes to
increment the value from 0 to 127 (the positive range of
char) and then it rotates to the negative value of -128. The
condition in the for loop fails and so comes out of the for
loop. It prints the current value of i that is -128.
113) main()
{
unsigned char i=0;
for(;i>=0;i++) ;
printf("%d\n",i);
}
Answer
infinite loop
Explanation
The difference between the previous question and this one is that
the char is declared to be unsigned. So the i++ can never yield negative
value and i>=0 never becomes false so that it can come out of the for
loop.
114) main()
{
char i=0;
for(;i>=0;i++) ;
printf("%d\n",i);
}
Answer:
Behavior is implementation dependent.
Explanation:
The detail if the char is signed/unsigned by default is
implementation dependent. If the implementation treats the char
to be signed by default the program will print –128 and terminate.
On the other hand if it considers char to be unsigned by default, it
goes to infinite loop.
Rule:
You can write programs that have implementation
dependent behavior. But dont write programs that depend on such
behavior.
115) Is the following statement a declaration/definition. Find what does it
mean?
int (*x)[10];
Answer
Definition.
x is a pointer to array of(size 10) integers.
Apply clock-wise rule to find the meaning of this definition.
http://www.exforsys.com Page 37 6/26/2004
37
116). What is the output for the program given below
typedef enum errorType{warning, error, exception,}error;
main()
{
error g1;
g1=1;
printf("%d",g1);
}
Answer
Compiler error: Multiple declaration for error
Explanation
The name error is used in the two meanings. One means
that it is a enumerator constant with value 1. The another use is
that it is a type name (due to typedef) for enum errorType. Given a
situation the compiler cannot distinguish the meaning of error to
know in what sense the error is used:
error g1;
g1=error;
// which error it refers in each case?
When the compiler can distinguish between usages then it
will not issue error (in pure technical terms, names can only be
overloaded in different namespaces).
Note: the extra comma in the declaration,
enum errorType{warning, error, exception,}
is not an error. An extra comma is valid and is provided just for
programmer’s convenience.
117) typedef struct error{int warning, error,
exception;}error;
main()
{
error g1;
g1.error =1;
printf("%d",g1.error);
}
Answer
1
Explanation
The three usages of name errors can be distinguishable by the
compiler at any instance, so valid (they are in different namespaces).
Typedef struct error{int warning, error, exception;}error;
This error can be used only by preceding the error by struct kayword as
in:
struct error someError;
typedef struct error{int warning, error, exception;}error;
This can be used only after . (dot) or -> (arrow) operator preceded by the
variable name as in :
g1.error =1;
printf("%d",g1.error);
typedef struct error{int warning, error, exception;}error;
http://www.exforsys.com Page 38 6/26/2004
38
This can be used to define variables without using the preceding struct
keyword as in:
error g1;
Since the compiler can perfectly distinguish between these three usages, it
is perfectly legal and valid.
Note
This code is given here to just explain the concept behind. In real
programming don’t use such overloading of names. It reduces the
readability of the code. Possible doesn’t mean that we should use it!
118) #ifdef something
int some=0;
#endif
main()
{
int thing = 0;
printf("%d %d\n", some ,thing);
}
Answer:
Compiler error : undefined symbol some
Explanation:
This is a very simple example for conditional compilation.
The name something is not already known to the compiler
making the declaration
int some = 0;
effectively removed from the source code.
119) #if something == 0
int some=0;
#endif
main()
{
int thing = 0;
printf("%d %d\n", some ,thing);
}
Answer
0 0
Explanation
This code is to show that preprocessor expressions are not
the same as the ordinary expressions. If a name is not
known the preprocessor treats it to be equal to zero.
120). What is the output for the following program
main()
{
int arr2D[3][3];
http://www.exforsys.com Page 39 6/26/2004
39
printf("%d\n", ((arr2D==* arr2D)&&(* arr2D == arr2D[0]))
);
}
Answer
1
Explanation
This is due to the close relation between the arrays and
pointers. N dimensional arrays are made up of (N-1)
dimensional arrays.
arr2D is made up of a 3 single arrays that contains 3
integers each .
The name arr2D refers to the beginning of all the 3 arrays.
*arr2D refers to the start of the first 1D array (of 3
integers) that is the same address as arr2D. So the
expression (arr2D == *arr2D) is true (1).
Similarly, *arr2D is nothing but *(arr2D + 0), adding a zero
doesn’t change the value/meaning. Again arr2D[0] is the
another way of telling *(arr2D + 0). So the expression
(*(arr2D + 0) == arr2D[0]) is true (1).
Since both parts of the expression evaluates to true the
result is true(1) and the same is printed.
121) void main()
{
if(~0 == (unsigned int)-1)
printf(“You can answer this if you know how values are represented
in memory”);
}
Answer
You can answer this if you know how values are represented
in memory
Explanation
~ (tilde operator or bit-wise negation operator) operates on
0 to produce all ones to fill the space for an integer. –1 is
represented in unsigned value as all 1’s and so both are
equal.
122) int swap(int *a,int *b)
{
*a=*a+*b;*b=*a-*b;*a=*a-*b;
}
main()
{
int x=10,y=20;
swap(&x,&y);
arr2D
arr2D[1]
arr2D[2]
arr2D[3]
http://www.exforsys.com Page 40 6/26/2004
40
printf("x= %d y = %d\n",x,y);
}
Answer
x = 20 y = 10
Explanation
This is one way of swapping two values. Simple checking will help
understand this.
123) main()
{
char *p = “ayqm”;
printf(“%c”,++*(p++));
}
Answer:
b
124) main()
{
int i=5;
printf("%d",++i++);
}
Answer:
Compiler error: Lvalue required in function main
Explanation:
++i yields an rvalue. For postfix ++ to operate an lvalue is
required.
125) main()
{
char *p = “ayqm”;
char c;
c = ++*p++;
printf(“%c”,c);
}
Answer:
b
Explanation:
There is no difference between the expression ++*(p++)
and ++*p++. Parenthesis just works as a visual clue for the
reader to see which expression is first evaluated.
126)
int aaa() {printf(“Hi”);}
int bbb(){printf(“hello”);}
iny ccc(){printf(“bye”);}
main()
{
int ( * ptr[3]) ();
ptr[0] = aaa;
ptr[1] = bbb;
ptr[2] =ccc;
ptr[2]();
http://www.exforsys.com Page 41 6/26/2004
41
}
Answer:
bye
Explanation:
int (* ptr[3])() says that ptr is an array of pointers to functions
that takes no arguments and returns the type int. By the
assignment ptr[0] = aaa; it means that the first function pointer in
the array is initialized with the address of the function aaa.
Similarly, the other two array elements also get initialized with the
addresses of the functions bbb and ccc. Since ptr[2] contains the
address of the function ccc, the call to the function ptr[2]() is same
as calling ccc(). So it results in printing "bye".
127)
main()
{
int i=5;
printf(“%d”,i=++i ==6);
}
Answer:
1
Explanation:
The expression can be treated as i = (++i==6), because == is of
higher precedence than = operator. In the inner expression, ++i is
equal to 6 yielding true(1). Hence the result.
128) main()
{
char p[ ]="%d\n";
p[1] = 'c';
printf(p,65);
}
Answer:
A
Explanation:
Due to the assignment p[1] = ‘c’ the string becomes, “%c\n”.
Since this string becomes the format string for printf and ASCII
value of 65 is ‘A’, the same gets printed.
129) void ( * abc( int, void ( *def) () ) ) ();
Answer::
abc is a ptr to a function which takes 2 parameters .(a). an
integer variable.(b). a ptrto a funtion which returns void. the
return type of the function is void.
Explanation:
Apply the clock-wise rule to find the result.
130) main()
{
while (strcmp(“some”,”some\0”))
http://www.exforsys.com Page 42 6/26/2004
42
printf(“Strings are not equal\n”);
}
Answer:
No output
Explanation:
Ending the string constant with \0 explicitly makes no difference.
So “some” and “some\0” are equivalent. So, strcmp returns 0
(false) hence breaking out of the while loop.
131) main()
{
char str1[] = {‘s’,’o’,’m’,’e’};
char str2[] = {‘s’,’o’,’m’,’e’,’\0’};
while (strcmp(str1,str2))
printf(“Strings are not equal\n”);
}
Answer:
“Strings are not equal”
“Strings are not equal”
….
Explanation:
If a string constant is initialized explicitly with characters, ‘\0’ is not
appended automatically to the string. Since str1 doesn’t have null
termination, it treats whatever the values that are in the following
positions as part of the string until it randomly reaches a ‘\0’. So
str1 and str2 are not the same, hence the result.
132) main()
{
int i = 3;
for (;i++=0;) printf(“%d”,i);
}
Answer:
Compiler Error: Lvalue required.
Explanation:
As we know that increment operators return rvalues and
hence it cannot appear on the left hand side of an
assignment operation.
133) void main()
{
int *mptr, *cptr;
mptr = (int*)malloc(sizeof(int));
printf(“%d”,*mptr);
int *cptr = (int*)calloc(sizeof(int),1);
printf(“%d”,*cptr);
}
Answer:
garbage-value 0
Explanation:
The memory space allocated by malloc is uninitialized, whereas
calloc returns the allocated memory space initialized to zeros.
http://www.exforsys.com Page 43 6/26/2004
43
134) void main()
{
static int i;
while(i<=10)
(i>2)?i++:i--;
printf(“%d”, i);
}
Answer:
32767
Explanation:
Since i is static it is initialized to 0. Inside the while loop the
conditional operator evaluates to false, executing i--. This
continues till the integer value rotates to positive value (32767).
The while condition becomes false and hence, comes out of the
while loop, printing the i value.
135) main()
{
int i=10,j=20;
j = i, j?(i,j)?i:j:j;
printf("%d %d",i,j);
}
Answer:
10 10
Explanation:
The Ternary operator ( ? : ) is equivalent for if-then-else
statement. So the question can be written as:
if(i,j)
{
if(i,j)
j = i;
else
j = j;
}
else
j = j;
136) 1. const char *a;
2. char* const a;
3. char const *a;
-Differentiate the above declarations.
Answer:
1. 'const' applies to char * rather than 'a' ( pointer to a constant
char )
*a='F' : illegal
a="Hi" : legal
2. 'const' applies to 'a' rather than to the value of a (constant
pointer to char )
http://www.exforsys.com Page 44 6/26/2004
44
*a='F' : legal
a="Hi" : illegal
3. Same as 1.
137) main()
{
int i=5,j=10;
i=i&=j&&10;
printf("%d %d",i,j);
}
Answer:
1 10
Explanation:
The expression can be written as i=(i&=(j&&10)); The inner
expression (j&&10) evaluates to 1 because j==10. i is 5. i = 5&1 is
1. Hence the result.
138) main()
{
int i=4,j=7;
j = j || i++ && printf("YOU CAN");
printf("%d %d", i, j);
}
Answer:
4 1
Explanation:
The boolean expression needs to be evaluated only till the truth
value of the expression is not known. j is not equal to zero itself
means that the expression’s truth value is 1. Because it is followed
by || and true || (anything) => true where (anything) will not be
evaluated. So the remaining expression is not evaluated and so the
value of i remains the same.
Similarly when && operator is involved in an expression, when any
of the operands become false, the whole expression’s truth value
becomes false and hence the remaining expression will not be
evaluated.
false && (anything) => false where (anything) will not be
evaluated.
139) main()
{
register int a=2;
printf("Address of a = %d",&a);
printf("Value of a = %d",a);
}
Answer:
Compier Error: '&' on register variable
Rule to Remember:
& (address of ) operator cannot be applied on register
variables.
http://www.exforsys.com Page 45 6/26/2004
45
140) main()
{
float i=1.5;
switch(i)
{
case 1: printf("1");
case 2: printf("2");
default : printf("0");
}
}
Answer:
Compiler Error: switch expression not integral
Explanation:
Switch statements can be applied only to integral types.
141) main()
{
extern i;
printf("%d\n",i);
{
int i=20;
printf("%d\n",i);
}
}
Answer:
Linker Error : Unresolved external symbol i
Explanation:
The identifier i is available in the inner block and so using extern
has no use in resolving it.
142) main()
{
int a=2,*f1,*f2;
f1=f2=&a;
*f2+=*f2+=a+=2.5;
printf("\n%d %d %d",a,*f1,*f2);
}
Answer:
16 16 16
Explanation:
f1 and f2 both refer to the same memory location a. So changes
through f1 and f2 ultimately affects only the value of a.
143) main()
{
char *p="GOOD";
char a[ ]="GOOD";
printf("\n sizeof(p) = %d, sizeof(*p) = %d, strlen(p) = %d",
sizeof(p), sizeof(*p), strlen(p));
printf("\n sizeof(a) = %d, strlen(a) = %d", sizeof(a), strlen(a));
}
Answer:
http://www.exforsys.com Page 46 6/26/2004
46
sizeof(p) = 2, sizeof(*p) = 1, strlen(p) = 4
sizeof(a) = 5, strlen(a) = 4
Explanation:
sizeof(p) => sizeof(char*) => 2
sizeof(*p) => sizeof(char) => 1
Similarly,
sizeof(a) => size of the character array => 5
When sizeof operator is applied to an array it returns the sizeof the
array and it is not the same as the sizeof the pointer variable. Here
the sizeof(a) where a is the character array and the size of the
array is 5 because the space necessary for the terminating NULL
character should also be taken into account.
144) #define DIM( array, type) sizeof(array)/sizeof(type)
main()
{
int arr[10];
printf(“The dimension of the array is %d”, DIM(arr, int));
}
Answer:
10
Explanation:
The size of integer array of 10 elements is 10 * sizeof(int). The
macro expands to sizeof(arr)/sizeof(int) => 10 * sizeof(int) /
sizeof(int) => 10.
145) int DIM(int array[])
{
return sizeof(array)/sizeof(int );
}
main()
{
int arr[10];
printf(“The dimension of the array is %d”, DIM(arr));
}
Answer:
1
Explanation:
Arrays cannot be passed to functions as arguments and only the
pointers can be passed. So the argument is equivalent to int *
array (this is one of the very few places where [] and * usage are
equivalent). The return statement becomes, sizeof(int *)/
sizeof(int) that happens to be equal in this case.
146) main()
{
static int a[3][3]={1,2,3,4,5,6,7,8,9};
int i,j;
static *p[]={a,a+1,a+2};
for(i=0;i<3;i++)
{
for(j=0;j<3;j++)
printf("%d\t%d\t%d\t%d\n",*(*(p+i)+j),
http://www.exforsys.com Page 47 6/26/2004
47
*(*(j+p)+i),*(*(i+p)+j),*(*(p+j)+i));
}
}
Answer:
1 1 1 1
2 4 2 4
3 7 3 7
4 2 4 2
5 5 5 5
6 8 6 8
7 3 7 3
8 6 8 6
9 9 9 9
Explanation:
*(*(p+i)+j) is equivalent to p[i][j].
147) main()
{
void swap();
int x=10,y=8;
swap(&x,&y);
printf("x=%d y=%d",x,y);
}
void swap(int *a, int *b)
{
*a ^= *b, *b ^= *a, *a ^= *b;
}
Answer:
x=10 y=8
Explanation:
Using ^ like this is a way to swap two variables without using a
temporary variable and that too in a single statement.
Inside main(), void swap(); means that swap is a function that
may take any number of arguments (not no arguments) and
returns nothing. So this doesn’t issue a compiler error by the call
swap(&x,&y); that has two arguments.
This convention is historically due to pre-ANSI style (referred to as
Kernighan and Ritchie style) style of function declaration. In that
style, the swap function will be defined as follows,
void swap()
int *a, int *b
{
*a ^= *b, *b ^= *a, *a ^= *b;
}
where the arguments follow the (). So naturally the declaration for
swap will look like, void swap() which means the swap can take
any number of arguments.
148) main()
{
int i = 257;
int *iPtr = &i;
printf("%d %d", *((char*)iPtr), *((char*)iPtr+1) );
http://www.exforsys.com Page 48 6/26/2004
48
}
Answer:
1 1
Explanation:
The integer value 257 is stored in the memory as, 00000001
00000001, so the individual bytes are taken by casting it to char *
and get printed.
149) main()
{
int i = 258;
int *iPtr = &i;
printf("%d %d", *((char*)iPtr), *((char*)iPtr+1) );
}
Answer:
2 1
Explanation:
The integer value 257 can be represented in binary as, 00000001
00000001. Remember that the INTEL machines are ‘small-endian’
machines. Small-endian means that the lower order bytes are
stored in the higher memory addresses and the higher order bytes
are stored in lower addresses. The integer value 258 is stored in
memory as: 00000001 00000010.
150) main()
{
int i=300;
char *ptr = &i;
*++ptr=2;
printf("%d",i);
}
Answer:
556
Explanation:
The integer value 300 in binary notation is: 00000001 00101100.
It is stored in memory (small-endian) as: 00101100 00000001.
Result of the expression *++ptr = 2 makes the memory
representation as: 00101100 00000010. So the integer
corresponding to it is 00000010 00101100 => 556.
151) #include
main()
{
char * str = "hello";
char * ptr = str;
char least = 127;
while (*ptr++)
least = (*ptr
printf("%d",least);
}
Answer:
0
Explanation:
http://www.exforsys.com Page 49 6/26/2004
49
After ‘ptr’ reaches the end of the string the value pointed by ‘str’ is
‘\0’. So the value of ‘str’ is less than that of ‘least’. So the value of
‘least’ finally is 0.
152) Declare an array of N pointers to functions returning pointers to functions
returning pointers to characters?
Answer:
(char*(*)( )) (*ptr[N])( );
153) main()
{
struct student
{
char name[30];
struct date dob;
}stud;
struct date
{
int day,month,year;
};
scanf("%s%d%d%d", stud.rollno, &student.dob.day,
&student.dob.month, &student.dob.year);
}
Answer:
Compiler Error: Undefined structure date
Explanation:
Inside the struct definition of ‘student’ the member of type struct
date is given. The compiler doesn’t have the definition of date
structure (forward reference is not allowed in C in this case) so it
issues an error.
154) main()
{
struct date;
struct student
{
char name[30];
struct date dob;
}stud;
struct date
{
int day,month,year;
};
scanf("%s%d%d%d", stud.rollno, &student.dob.day,
&student.dob.month, &student.dob.year);
}
Answer:
Compiler Error: Undefined structure date
Explanation:
Only declaration of struct date is available inside the structure
definition of ‘student’ but to have a variable of type struct date the
definition of the structure is required.
http://www.exforsys.com Page 50 6/26/2004
50
155) There were 10 records stored in “somefile.dat” but the following program
printed 11 names. What went wrong?
void main()
{
struct student
{
char name[30], rollno[6];
}stud;
FILE *fp = fopen(“somefile.dat”,”r”);
while(!feof(fp))
{
fread(&stud, sizeof(stud), 1 , fp);
puts(stud.name);
}
}
Explanation:
fread reads 10 records and prints the names successfully. It
will return EOF only when fread tries to read another record
and fails reading EOF (and returning EOF). So it prints the
last record again. After this only the condition feof(fp)
becomes false, hence comes out of the while loop.
156) Is there any difference between the two declarations,
1. int foo(int *arr[]) and
2. int foo(int *arr[2])
Answer:
No
Explanation:
Functions can only pass pointers and not arrays. The numbers that
are allowed inside the [] is just for more readability. So there is no
difference between the two declarations.
157) What is the subtle error in the following code segment?
void fun(int n, int arr[])
{
int *p=0;
int i=0;
while(i++
p = &arr[i];
*p = 0;
}
Answer & Explanation:
If the body of the loop never executes p is assigned no
address. So p remains NULL where *p =0 may result in
problem (may rise to runtime error “NULL pointer
assignment” and terminate the program).
158) What is wrong with the following code?
int *foo()
{
int *s = malloc(sizeof(int)100);
assert(s != NULL);
http://www.exforsys.com Page 51 6/26/2004
51
return s;
}
Answer & Explanation:
assert macro should be used for debugging and finding out bugs.
The check s != NULL is for error/exception handling and for that
assert shouldn’t be used. A plain if and the corresponding remedy
statement has to be given.
159) What is the hidden bug with the following statement?
assert(val++ != 0);
Answer & Explanation:
Assert macro is used for debugging and removed in release
version. In assert, the experssion involves side-effects. So the
behavior of the code becomes different in case of debug version
and the release version thus leading to a subtle bug.
Rule to Remember:
Don’t use expressions that have side-effects in assert statements.
160) void main()
{
int *i = 0x400; // i points to the address 400
*i = 0; // set the value of memory location pointed by i;
}
Answer:
Undefined behavior
Explanation:
The second statement results in undefined behavior because it
points to some location whose value may not be available for
modification. This type of pointer in which the non-availability of
the implementation of the referenced location is known as
'incomplete type'.
161) #define assert(cond) if(!(cond)) \
(fprintf(stderr, "assertion failed: %s, file %s, line %d \n",#cond,\
__FILE__,__LINE__), abort())
void main()
{
int i = 10;
if(i==0)
assert(i <>
else
printf("This statement becomes else for if in assert macro");
}
Answer:
No output
Explanation:
The else part in which the printf is there becomes the else for if in the
assert macro. Hence nothing is printed.
The solution is to use conditional operator instead of if statement,
#define assert(cond) ((cond)?(0): (fprintf (stderr, "assertion failed: \ %s,
file %s, line %d \n",#cond, __FILE__,__LINE__), abort()))
http://www.exforsys.com Page 52 6/26/2004
52
Note:
However this problem of “matching with nearest else” cannot be
solved by the usual method of placing the if statement inside a
block like this,
#define assert(cond) { \
if(!(cond)) \
(fprintf(stderr, "assertion failed: %s, file %s, line %d \n",#cond,\
__FILE__,__LINE__), abort()) \
}
162) Is the following code legal?
struct a
{
int x;
struct a b;
}
Answer:
No
Explanation:
Is it not legal for a structure to contain a member that is of the
same
type as in this case. Because this will cause the structure
declaration to be recursive without end.
163) Is the following code legal?
struct a
{
int x;
struct a *b;
}
Answer:
Yes.
Explanation:
*b is a pointer to type struct a and so is legal. The compiler knows,
the size of the pointer to a structure even before the size of the
structure
is determined(as you know the pointer to any type is of same size).
This type of structures is known as ‘self-referencing’ structure.
164) Is the following code legal?
typedef struct a
{
int x;
aType *b;
}aType
Answer:
No
Explanation:
The typename aType is not known at the point of declaring the
structure (forward references are not made for typedefs).
165) Is the following code legal?
typedef struct a aType;
http://www.exforsys.com Page 53 6/26/2004
53
struct a
{
int x;
aType *b;
};
Answer:
Yes
Explanation:
The typename aType is known at the point of declaring the
structure, because it is already typedefined.
166) Is the following code legal?
void main()
{
typedef struct a aType;
aType someVariable;
struct a
{
int x;
aType *b;
};
}
Answer:
No
Explanation:
When the declaration,
typedef struct a aType;
is encountered body of struct a is not known. This is known as
‘incomplete types’.
167) void main()
{
printf(“sizeof (void *) = %d \n“, sizeof( void *));
printf(“sizeof (int *) = %d \n”, sizeof(int *));
printf(“sizeof (double *) = %d \n”, sizeof(double *));
printf(“sizeof(struct unknown *) = %d \n”, sizeof(struct unknown *));
}
Answer :
sizeof (void *) = 2
sizeof (int *) = 2
sizeof (double *) = 2
sizeof(struct unknown *) = 2
Explanation:
The pointer to any type is of same size.
168) char inputString[100] = {0};
To get string input from the keyboard which one of the following is better?
1) gets(inputString)
2) fgets(inputString, sizeof(inputString), fp)
Answer & Explanation:
The second one is better because gets(inputString) doesn't know
the size of the string passed and so, if a very big input (here, more
than 100 chars) the charactes will be written past the input string.
http://www.exforsys.com Page 54 6/26/2004
54
When fgets is used with stdin performs the same operation as gets
but is safe.
169) Which version do you prefer of the following two,
1) printf(“%s”,str); // or the more curt one
2) printf(str);
Answer & Explanation:
Prefer the first one. If the str contains any format characters like
%d then it will result in a subtle bug.
170) void main()
{
int i=10, j=2;
int *ip= &i, *jp = &j;
int k = *ip/*jp;
printf(“%d”,k);
}
Answer:
Compiler Error: “Unexpected end of file in comment started in line
5”.
Explanation:
The programmer intended to divide two integers, but by the
“maximum munch” rule, the compiler treats the operator
sequence / and * as /* which happens to be the starting of
comment. To force what is intended by the programmer,
int k = *ip/ *jp;
// give space explicity separating / and *
//or
int k = *ip/(*jp);
// put braces to force the intention
will solve the problem.
171) void main()
{
char ch;
for(ch=0;ch<=127;ch++)
printf(“%c %d \n“, ch, ch);
}
Answer:
Implementaion dependent
Explanation:
The char type may be signed or unsigned by default. If it is signed
then ch++ is executed after ch reaches 127 and rotates back to -
128. Thus ch is always smaller than 127.
172) Is this code legal?
int *ptr;
ptr = (int *) 0x400;
Answer:
Yes
Explanation:
http://www.exforsys.com Page 55 6/26/2004
55
The pointer ptr will point at the integer in the memory location
0x400.
173) main()
{
char a[4]="HELLO";
printf("%s",a);
}
Answer:
Compiler error: Too many initializers
Explanation:
The array a is of size 4 but the string constant requires 6 bytes to
get stored.
174) main()
{
char a[4]="HELL";
printf("%s",a);
}
Answer:
HELL%@!~@!@???@~~!
Explanation:
The character array has the memory just enough to hold the string
“HELL” and doesnt have enough space to store the terminating null
character. So it prints the HELL correctly and continues to print
garbage values till it accidentally comes across a NULL character.
175) main()
{
int a=10,*j;
void *k;
j=k=&a;
j++;
k++;
printf("\n %u %u ",j,k);
}
Answer:
Compiler error: Cannot increment a void pointer
Explanation:
Void pointers are generic pointers and they can be used only when
the type is not known and as an intermediate address storage
type. No pointer arithmetic can be done on it and you cannot apply
indirection operator (*) on void pointers.
176) main()
{
extern int i;
{ int i=20;
{
const volatile unsigned i=30; printf("%d",i);
}
printf("%d",i);
}
http://www.exforsys.com Page 56 6/26/2004
56
printf("%d",i);
}
int i;
177) Printf can be implemented by using __________ list.
Answer:
Variable length argument lists
178) char *someFun()
{
char *temp = “string constant";
return temp;
}
int main()
{
puts(someFun());
}
Answer:
string constant
Explanation:
The program suffers no problem and gives the output correctly because
the character constants are stored in code/data area and not allocated in stack,
so this doesn’t lead to dangling pointers.
179) char *someFun1()
{
char temp[ ] = “string";
return temp;
}
char *someFun2()
{
char temp[ ] = {‘s’, ‘t’,’r’,’i’,’n’,’g’};
return temp;
}
int main()
{
puts(someFun1());
puts(someFun2());
}
Answer:
Garbage values.
Explanation:
Both the functions suffer from the problem of dangling pointers. In
someFun1() temp is a character array and so the space for it is allocated in heap
and is initialized with character string “string”. This is created dynamically as the
function is called, so is also deleted dynamically on exiting the function so the
string data is not available in the calling function main() leading to print some
garbage values. The function someFun2() also suffers from the same problem but
the problem can be easily identified in this case.

the ultimate books TUTORIALS AND SHARING SITE


IT IS A PART OF A GREATEST LIBRARY ON EARTH THE INTERNET.

if u like my posts :- download all the materials for free and list thanks for encouragement..........

DOWNLOAD ....LINK AND SHARE FREELY WITH FREEDOM IS THE NEW RULE OF CREATIVIANS

BE A PART OF IT

availabale boks in this blog

utmost care has been taken to publish the correct links but unfortunately not every one works please bear with us.... GRE, GMAT, TOEFL, IELTS, SAT Ebooks Software Testing, ISTQB, Project Management, Six Sigma Ebooks Vb.Net and Visual Basic Ebooks ASP.Net and ASP Ebooks Computer Hardware, Peripherals, Design, Electronics, Electrical Ebooks Java, J2EE, J2ME, JSP, JavaScript, SCJA, SCJP, SCWCD Ebooks HTML, CSS, Perl, PHP, Python, XML, XHTML, DHTML, CGI Ebooks Cisco CCNA, CCNP, CCIE, CCDA, CCDP, CCIP, CCVP, CCDE Ebooks Photoshop, CorelDraw, Dreamweaver, Flash, Illustrator, 3dsMax, Maya Ebooks Computer Networking, TCP/IP, Network Security, Cryptography, Firewall Ebooks C#.Net, ADO.Net, .Net Framework Ebooks MySql, Sql Server Ebooks Oracle Apps, Oracle Database, OCA, OCP, DBA, Oracle PL/SQL Ebooks C and C++ Ebooks Unix, RHCE, Fedora, Suse Linux, Ubuntu, Debian, GNU/LInux Ebooks Windows, MCSE, MCSD, MCP The above given are various Rapidshare, Megaupload, Depositfiles, mihd, Filefactory, sendspace, Zshare Ebooks Download links. This sites focus on latest updation of books and find the latest download links of free ebooks available in web. Online and PDF Ebooks Downloads - Computers - Electronics ABAP ADA Programming Language Agile/Extreme Programming Algorithms Artificial Intelligence/Logic Programming Artificial Neural Networks ASP.Net Assembly/Machine Language Bio Informatics C Programming C#.Net C#.Net (Another) C++ Programming CCIE CCNA CCNP COBOL Compiler Design/Construction Computational Complexity Theory Computational Linguistics Computer Architecture/Organization Computer Graphics Computer Hardware Computer Networks Computer Programming (General) Computer References Computer Science (General) Computer Security Computer Vision Corel draw Cryptography CSS Data Mining Data Structures and Algorithms Database Management Systems Delphi Design Patterns DHTML Digital Image Processing Digital Signal Processing Electronics (Design/Programming) Forth Programming Fortran Functional Programming/ML/Caml/Haskell Game Programming Graph Theory

if you like it add to your book marks

Subscribe via email

SEARCH CREATIVE BLOG:

SEARCH RESULTS HERE:

top news! creative blog has a new forum now..

log on as a member into the creative forum and start posting yourselves ... for more details visit our creative forum: oops forum

free books tutorial


DISCLAIMER
read the disclaimer here