1.Why doesn't this code:
a[i] = i++;
work?
Answer
The subexpression i++ causes
a side effect--it modifies i's value--which leads to undefined behavior
since i is also referenced elsewhere in the same expression. (Note that
although the language in K&R suggests that the behavior of this expression
is unspecified, the C Standard makes the stronger statement that it is
undefined
a[i] = i++;
we don't know which cell of a[] gets written to, but i does get incremented by one.
Answer
No. Once an expression or program
becomes undefined, all aspects of it become undefined.
Answer
The language definition states that for each pointer type, there is a special value--the ``null pointer''--which is distinguishable from all other pointer values and which is ``guaranteed to compare unequal to a pointer to any object or function.'' That is, the address-of operator & will never yield a null pointer, nor will a successful call to malloc. (malloc does return a null pointer when it fails, and this is a typical use of null pointers: as a ``special'' pointer value with some other meaning, usually ``not allocated'' or ``not pointing anywhere yet.'')
A null pointer is conceptually
different from an uninitialized pointer. A null pointer is known not to
point to any object or function; an uninitialized pointer might point anywhere.
Answer
Perhaps surprisingly, character
constants in C are of type int, so sizeof('a') is sizeof(int) (though it's
different in C++).
5. How can I print a '%' character
in a printf format string? I tried \%, but it didn't work.
Simply double the percent sign: %% .
\% can't work, because the backslash \ is the compiler's escape character, while here our problem is that the % is printf's escape character.
int i = 7;
printf("%d\n", i++ * i++);
prints 49. Regardless of the order of evaluation, shouldn't it print 56?
Answer
Although the postincrement and postdecrement operators ++ and -- perform their operations after yielding the former value, the implication of ``after'' is often misunderstood. It is not guaranteed that an increment or decrement is performed immediately after giving up the previous value and before any other part of the expression is evaluated. It is merely guaranteed that the update will be performed sometime before the expression is considered "finished'' . In the example, the compiler chose to multiply the previous value by itself and to perform both increments afterwards.
The behavior of code which contains
multiple, ambiguous side effects has always been undefined. (Loosely speaking,
by ``multiple, ambiguous side effects'' we mean any combination of ++,
--, =, +=, -=, etc. in a single expression which causes the same object
either to be modified twice or modified en inspected. Don't even try to
find out how your compiler implements such things (contrary to the ill-advised
exercises in many C textbooks); as K&R wisely point out, ``if
ou don't know how they are done on various machines, that innocence may
help to protect you.''
7.What's a ``sequence point''?
Answer
A sequence point is the point (at the end of a full expression, or at the ||, &&, ?:, or comma operators, or just before a function call) at which the dust has settled and all side effects are guaranteed to be complete. The ANSI/ISO C Standard states that Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored.
The second sentence can be difficult
to understand. It says that if an object is written to within a full expression,
any and all accesses to it within the same expression must be for the purposes
of computing the value to be written. This rule effectively constrains
legal expressions to those in which the accesses demonstrably precede the
modification.
Answer
Precise answers to these and
many similar questions depend of course on the processor and compiler in
use. If you simply must know, you'll have to time
test programs carefully. (Often
the differences are so slight that hundreds of thousands of iterations
are required even to see them. Check the compiler's
assembly language output, if
available, to see if two purported alternatives aren't compiled identically.)
It is ``usually'' faster to march through large arrays with pointers rather than array subscripts, but for some processors the reverse is true.
Function calls, though obviously
incrementally slower than in-line code, contribute so much to modularity
and code clarity that there is rarely good reason to
avoid them.
Before rearranging expressions
such as i = i + 1, remember that you are dealing with a compiler, not a
keystroke-programmable calculator. Any decent
compiler will generate identical
code for ++i, i += 1, and i = i + 1. The reasons for using ++i or i +=
1 over i = i + 1 have to do with style, not
efficiency.
int a = 1000, b = 1000;
long int c = a * b;
work?
Answer
Under C's integral promotion
rules, the multiplication is carried out using int arithmetic, and the
result may overflow or be truncated before being promoted
and assigned to the long int
left-hand side. Use an explicit cast to force long arithmetic:
long int c = (long int)a * b;
Note that (long int)(a * b) would not have the desired effect.
A similar problem can arise when
two integers are divided, with the result assigned to a floating-point
variable.
10. I have a complicated expression which I have to assign to one of two variables, depending on a condition. Can I use code like this?
((condition) ? a : b) = complicated_expression;
Answer
No. The ?: operator, like most
operators, yields a value, and you can't assign to a value. (In other words,
?: does not yield an lvalue.) If you really want to,
you can try something like
*((condition) ? &a : &b) = complicated_expression;
although this is admittedly not
as pretty.
11. Does *p++ increment p, or what it points to?
Answer
Unary operators like *, ++, and
-- all associate (group) from right to left. Therefore, *p++ increments
p (and returns the value pointed to by p before the
increment). To increment the
value pointed to by p, use (*p)++ (or perhaps ++*p, if the order of the
side effect doesn't matter).
Answer
printf("%*d", width, n) will
do just what you want.
Answer
You can't; an asterisk in a scanf
format string means to suppress assignment. You may be able to use ANSI
stringizing and string concatenation to
accomplish about the same thing,
or to construct a scanf format string on-the-fly.
Answer
No. main must be declared as
returning an int, and as taking either zero or two arguments, of the appropriate
types. If you're calling exit() but still getting
warnings, you may have to insert
a redundant return statement (or use some kind of ``not reached'' directive,
if available).
Declaring a function as void
does not merely shut off or rearrange warnings: it may also result in a
different function call/return sequence, incompatible with
what the caller (in main's case,
the C run-time startup code) expects.
(Note that this discussion of
main pertains only to ``hosted'' implementations; none of it applies to
``freestanding'' implementations, which may not even
have main. However, freestanding
implementations are comparatively rare, and if you're using one, you probably
know it. If you've never heard of the
distinction, you're probably
using a hosted implementation, and the above rules apply.)
Answer
Perhaps its author counts himself among the target audience. Many books unaccountably use void main() in examples. They're wrong.
((int *)p)++;
work?
Answer
In C, a cast operator does not
mean ``pretend these bits have a different type, and treat them accordingly'';
it is a conversion operator, and by definition it
yields an rvalue, which cannot
be assigned to, or incremented with ++. (It is an anomaly in pcc-derived
compilers, and an extension in gcc, that expressions
such as the above are ever accepted.)
Say what you mean: use
p = (char *)((int *)p + 1);
or (since p is a char *) simply
p += sizeof(int);
Whenever possible, you should choose appropriate pointer types in the first place, instead of trying to treat one type as another.
Answer
Many programmers believe that
NULL should be used in all pointer contexts, as a reminder that the value
is to be thought of as a pointer. Others feel that the
confusion surrounding NULL and
0 is only compounded by hiding 0 behind a macro, and prefer to use unadorned
0 instead. There is no one right answer. (See
also questions 9.2 and 17.10.)
C programmers must understand that NULL and 0 are interchangeable in pointer
contexts, and that an uncast 0 is perfectly
acceptable. Any usage of NULL
(as opposed to 0) should be considered a gentle reminder that a pointer
is involved; programmers should not depend on it
(either for their own understanding
or the compiler's) for distinguishing pointer 0's from integer 0's.
NULL should not be used when
another kind of 0 is required, even though it might work, because doing
so sends the wrong stylistic message. (Furthermore,
ANSI allows the definition of
NULL to be ((void *)0), which will not work at all in non-pointer contexts.)
In particular, do not use NULL when the ASCII null
character (NUL) is desired.
Provide your own definition
#define NUL '\0'
if you must.
Answer
When the term ``null'' or ``NULL'' is casually used, one of several things may be meant:
1.1. The internal
(or run-time) representation of a null pointer, which may or may not be
all-bits-0 and which may be different for different pointer
types. The actual values should be of concern only to compiler writers.
Authors of C programs never see them, since they use...
2.2. The
null pointer constant, which is a constant integer 0 . It is often hidden
behind...
3.3. The
NULL macro, which is #defined to be 0 or ((void *)0) . Finally, as red
herrings, we have...
4.4. The
ASCII null character (NUL), which does have all bits zero, but has no necessary
relation to the null pointer except in name; and...
5.5. The
``null string,'' which is another name for the empty string (""). Using
the term ``null string'' can be confusing in C, because an empty string
involves a null ('\0') character, but not a null pointer, which brings
us full circle...
Answer
It is legal in ANSI C (and perhaps
in a few pre-ANSI systems), though useful only in rare circumstances. It
declares an array of size three, initialized with
the three characters 'a', 'b',
and 'c', without the usual terminating '\0' character. The array is therefore
not a true C string and cannot be used with
strcpy, printf %s, etc.
Most of the time, you should
let the compiler count the initializers when initializing arrays (in the
case of the initializer "abc", of course, the computed size
will be 4).
Answer
calloc(m, n) is essentially equivalent to
p = malloc(m * n);
memset(p, 0, m * n);
The zero fill is all-bits-zero,
and does not therefore guarantee useful null pointer values (see section
5 of this list) or floating-point zero values. free is
properly used to free the memory
allocated by calloc.
Answer
char const *p declares a pointer
to a constant character (you can't change the character); char * const
p declares a constant pointer to a (variable)
character (i.e. you can't change
the pointer).
Answer
The ANSI/ISO Standard says that
it may do either; the behavior is implementation-defined
char c;
while((c = getchar()) != EOF)
...
Answer
For one thing, the variable to
hold getchar's return value must be an int. getchar can return all possible
character values, as well as EOF. By passing
getchar's return value through
a char, either a normal character might be misinterpreted as EOF, or the
EOF might be altered (particularly if type char is
unsigned) and so never seen.
Answer
Just use sprintf. (Don't worry
that sprintf may be overkill, potentially wasting run time or code space;
it works well in practice.)
You can obviously use sprintf
to convert long or floating-point numbers to strings as well (using %ld
or %f).
Answer
The obvious way,
rand() % N /* POOR */
(which tries to return numbers
from 0 to N-1) is poor, because the low-order bits of many random number
generators are distressingly non-random.
A better method
is something like
(int)((double)rand() / ((double)RAND_MAX + 1) * N)
If you're worried about using floating point, you could use
rand() / (RAND_MAX / N + 1)
Both methods obviously require knowing RAND_MAX (which ANSI #defines in <stdlib.h>), and assume that N is much less than RAND_MAX.
(Note, by the way, that RAND_MAX
is a constant telling you what the fixed range of the C library rand function
is. You cannot set RAND_MAX to some other
value, and there is no way of
requesting that rand return numbers in some other range.)
If you're starting with a random
number generator which returns floating-point values between 0 and 1, all
you have to do to get integers from 0 to N-1 is
multiply the output of that
generator by N.