|Access||Adobe photoshop||Algoritmi||Autocad||Baze de date||C||C sharp|
|Calculatoare||Corel draw||Dot net||Excel||Fox pro||Frontpage||Hardware|
|Php||Power point||Retele calculatoare||Sql||Tutorials||Webdesign||Windows|
|Asp||Autocad||C||Dot net||Excel||Fox pro||Html||Java|
Expressions in C can get rather complicated because of the number of different types and operators that can be mixed together. This section explains what happens, but can get deep at times. You may need to re-read it once or twice to make sure that you have understood all of the points.
First, a bit of terminology. Expressions in C are built from combinations of operators and operands, so for example in this expressionx = a+b*(-c)
we have the operators
c. You will also have noticed that
parentheses can be used for grouping sub-expressions such as the
-c. Most of C's unusually rich set of
operators are either binary operators, which take two operands, or unary
operators, which take only one. In the example, the
A peculiarity of C is that operators may appear consecutively in expressions without the need for parentheses to separate them. The previous example could have been written asx = a+b*-c;
and still have been a valid expression. Because of the number of operators that C has, and because of the strange way that assignment works, the precedence of the operators (and their associativity) is of much greater importance to the C programmer than in most other languages. It will be discussed fully after the introduction of the important arithmetic operators.
Before that, we must investigate the type conversions that may occur.
C allows types to be mixed in expressions, and permits operations that
result in type conversions happening implicitly. This section describes the way
that the conversions must occur. Old C programmers should read this carefully,
because the rules have changed — in particular, the promotion of
double, the promotions of short integral types and the
introduction of value preserving rules are genuinely different in
Although it isn't directly relevant at the moment, we must note that the integral and the floating types are jointly known as arithmetic types and that C also supports other types (notably pointer types). The rules that we discuss here are appropriate only in expressions that have arithmetic types throughout - additional rules come into play when expressions mix pointer types with arithmetic types and these are discussed much later.
There are various types of conversion in arithmetic expressions:
Conversions between floating (real) types were discussed in Section 2.8 ; what we do next is to specify how the other conversions are to be performed, then look at when they are required. You will need to learn them by heart if you ever intend to program seriously in C.
The Standard has, among some controversy, introduced what are known as value
preserving rules, where a knowledge of the target computer is required to
work out what the type of an expression will be. Previously, whenever an
unsigned type occurred in an expression, you knew that the result had to be
unsigned too. Now, the result will only
unsigned if the
conversions demand it; in many cases the result will be an ordinary
The reason for the change was to reduce some of the surprises possible when you mix signed and unsigned quantities together; it isn't always obvious when this has happened and the intention is to produce the ‘more commonly required’ result.
No arithmetic is done by C at a precision shorter than
int, so these conversions are implied
almost whenever you use one of the objects listed below in an expression. The
conversion is defined as follows:
char(or a bitfield or enumeration type which we haven't met yet) has the integral promotions applied
intcan hold all of the values of the original type then the value is converted to
This preserves both the value and the sign of the original type. Note that
whether a plain
treated as signed or unsigned is implementation dependent.
These promotions are applied very often—they are applied as part of the usual
arithmetic conversions, and to the operands of the shift, unary
A lot of conversions between different types of integers are caused by mixing the various flavours of integers in expressions. Whenever these happen, the integral promotions will already have been done. For all of them, if the new type can hold all of the values of the old type, then the value remains unchanged.
When converting from a signed integer to an unsigned integer whose length is equal to or longer than the original type, then if the signed value was nonnegative, its value is unchanged. If the value was negative, then it is converted to the signed form of the longer type and then made unsigned by conceptually adding it to one greater than the maximum that can be held in the unsigned type. In a twos complement system, this preserves the original bit-pattern for positive numbers and guarantees ‘sign-extension’ of negative numbers.
Whenever an integer is converted into a shorter unsigned type, there can be no ‘overflow’, so the result is defined to be ‘the non-negative remainder on division by the number one greater than the largest unsigned number that can be represented in the shorter type’. That simply means that in a two's complement environment the low-order bits are copied into the destination and the high-order ones discarded.
Converting an integer to a shorter signed type runs into trouble if there is not enough room to hold the value. In that case, the result is implementation defined (although most old-timers would expect that simply the low-order bit pattern is copied).
That last item could be a bit worrying if you remember the integral
promotions, because you might interpret it as follows—if I assign a
char to another
char, then the one on the right is first
promoted to one of the kinds of
could doing the assignment result in converting (say) an
int to a char and provoking the
‘implementation defined’ clause? The answer is no, because assignment is
specified not to involve the integral promotions, so you are safe.
Converting a floating to an integral type simply throws away any fractional part. If the integral type can't hold the value that is left, then the behaviour is undefined—this is a sort of overflow.
As has already been said, going up the scale from
double, there is no problem with conversions—each higher one in
the list can hold all the values of the lower ones, so the conversion occurs
with no loss of information.
Converting in the opposite direction, if the value is outside the range that can be held, the behaviour is undefined. If the value is in range, but can't be held exactly, then the result is one of the two nearest values that can be held, chosen in a way that the implementation defines. This means that there will be a loss of precision.
A lot of expressions involve the use of subexpressions of mixed types
together with operators such as
long double, then the other one is converted to
long doubleand that is the type of the result.
double, then the other one is converted to
double, and that is the type of the result.
float, then the other one is converted to
float, and that is the type of the result.
unsigned long int, then the other one is converted to
unsigned long int, and that is the type of the result.
long int, then the other one is converted to
long int, and that is the type of the result.
unsigned int, then the other one is converted to
unsigned int, and that is the type of the result.
int, so that is the type of the result.
The Standard contains a strange sentence: ‘The values of floating operands
and of the results of floating expressions may be represented in greater
precision and range than that required by the type; the types are not changed
thereby’. This is in fact to allow the Old C treatment of
floats. In Old C,
float variables were automatically
double, the way
that the integral promotions promote
int. So, an expression
variables may be done as if they were
but the type of the result must appear to be
The only effect is likely to be on performance and is not particularly
important to most users.
Whether or not conversions need to be applied, and if so which ones, is discussed at the point where each operator is introduced.
In general, the type conversions and type mixing rules don't cause a lot of
trouble, but there is one pitfall to watch out for. Mixing signed and unsigned
quantities is fine until the signed number is negative; then its value can't be
represented in an unsigned variable and something has to happen. The standard
says that to convert a negative number to unsigned, the largest possible number
that can be held in the unsigned plus one is added to the negative number; that
is the result. Because there can be no overflow in an unsigned type, the result
always has a defined value. Taking a 16-bit
for an example, the unsigned version has a range of 0–65535. Converting a
signed value of -7 to this type involves adding 65536, resulting
in 65529. What is happening is that the Standard is enshrining previous
practice, where the bit pattern in the signed number is simply assigned to the
unsigned number; the description in the standard is exactly what would happen
if you did perform the bit pattern assignment on a two's complement computer.
The one's complement implementations are going to have to do some real work to
get the same result.
Putting it plainly, a small magnitude negative number will result in a large positive number when converted to unsigned. If you don't like it, suggest a better solution—it is plainly a mistake to try to assign a negative number to an unsigned variable, so it's your own fault.
Well, it's easy to say ‘don't do it’, but it can happen by accident and the results can be very surprising. Look at this example.#include <stdio.h>
You might expect that to print out the list of values from
with a value of
unsigned int first, then make the
it's converted, and is plainly somewhat larger than
The Standard, as we've already said, now makes allowances for extended
character sets. You can either use the shift-in shift-out encoding method which
allows the multibyte charactes to be stored in ordinary C strings (which are
really arrays of
we explore later), or you can use a representation that uses more than one byte
of storage per character for every character. The use of shift sequences only
works if you process the characters in strict order; it is next to useless if
you want to create an array of characters and access them in non-sequential
order, since the actual index of each
in the array and the logical index of each of the encoded characters are not
easily determined. Here's the illustration we used before, annotated with the
actual and the logical array indexes:
We're still in trouble even if we do manage to use the index of
Clearly, a better approach for this sort of thing is to come up with a distinct
value for all of the characters in the character set we are using, which may
involve more bits than will fit into a char, and to be able to store each one
as a separate item without the use of shifts or other position-dependent
techniques. That is what the
type is for.
Although it is always a synonym for one of the other integral types,
wchar_t (whose definition is found in
<stddef.h>) is defined to be the
implementation-dependent type that should be used to hold extended characters
when you need an array of them. The Standard makes the following guarantees
about the values in a wide character:
wchar_tcan hold distinct values for each member of the largest character set supported by the implementation.
wchar_twith the same value as it has in a
There is further support for this method of encoding characters. Strings,
which we have already seen, are implemented as arrays of
char, even though they look like this:
To get strings whose type is
simply prefix a string with the letter
In the two examples, it is very important to understand the differences. Strings are implemented as arrays and although it might look odd, it is entirely permissible to use array indexing on them:'a string'
are both valid expressions. The first results in an expression whose type is
char and whose value is the
internal representation of the letter ‘
(remember arrays index from zero, not one). The second has the type
wchar_t and also has the value of the
internal representation of the letter ‘
It gets more interesting if we are using extended characters. If we use the
<b>, and so on to indicate
‘additional’ characters beyond the normal character set which are encoded using
some form of shift technique, then these examples show the problems.
The second one is easiest: it has a type of
wchar_t and the appropriate internal encoding for
supposed to be—say the Greek letter alpha. The first one is unpredictable. Its
type is unquestionably
but its value is probably the value of the ‘shift-in’ marker.
As with strings, there are also wide character constants.'a'
char and the
value of the encoding for the letter ‘
is a constant of type
If you use a multibyte character in the first one, then you have the same sort
of thing as if you had written
—multiple characters in a character constant (actually, this is valid but
means something funny). A single multibyte character in the second example will
simply be converted into the appropriate
If you don't understand all the wide character stuff, then all we can say is that we've done our best to explain it. Come back and read it again later, when it might suddenly click. In practice it does manage to address the support of extended character sets in C and once you're used to it, it makes a lot of sense.
Exercise 2.15. Assuming that
are respectively 8, 16 and 32 bits long, and that
char defaults to
unsigned char on a given system, what is
the resulting type of expressions involving the following combinations of
variables, after the usual arithmetic conversions have been applied?
From time to time you will find that an expression turns out not to have the type that you wanted it to have and you would like to force it to have a different type. That is what casts are for. By putting a type name in parentheses, for example(int)
you create a unary operator known as a cast. A cast turns the
value of the expression on its right into the indicated type. If, for example,
you were dividing two integers
then the expression would use integer division and discard any remainder. To
force the fractional part to be retained, you could either use some
intermediate float variables, or a cast. This example does it both ways.
The easiest way to remember how to write a cast is to write down exactly what you would use to declare a variable of the type that you want. Put parentheses around the entire declaration, then delete the variable name; that gives you the cast. Table 2.6 shows a few simple examples—some of the types shown will be new to you, but it's the complicated ones that illustrate best how casts are written. Ignore the ones that you don't understand yet, because you will be able to use the table as a reference later.
pointer to function returning
Table 2.6. Casts
Or, put another way, multiplication
If the division is not exact and neither operand is negative, the result
If either operand is negative, the result of
It is always true that the following expression is equal to zero:(a/b)*b + a%b - a
b is zero.
The usual arithmetic conversions are applied to both of the operands.
a-b both use a
binary operator (the
The unary minus has an obvious function—it takes the negative value of its operand; what does the unary plus do? In fact the answer is almost nothing. The unary plus is a new addition to the language, which balances the presence of the unary minus, but doesn't have any effect on the value of the expression. Very few Old C users even noticed that it was missing.
The usual arithmetic conversions are applied to both of the operands of the binary forms of the operators. Only the integral promotions are performed on the operands of the unary forms of the operators.
One of the great strengths of C is the way that it allows systems programmers to do what had, before the advent of C, always been regarded as the province of the assembly code programmer. That sort of code was by definition highly non-portable. As C demonstrates, there isn't any magic about that sort of thing, and into the bargain it turns out to be surprisingly portable. What is it? It's what is often referred to as ‘bit-twiddling’—the manipulation of individual bits in integer variables. None of the bitwise operators may be used on real operands because they aren't considered to have individual or accessible bits.
There are six bitwise operators, listed in Table 2.7, which also shows the arithmetic conversions that are applied.
usual arithmetic conversions
usual arithmetic conversions
usual arithmetic conversions
Table 2.7. Bitwise operators
Only the last, the one's complement, is a unary operator. It inverts the state of every bit in its operand and has the same effect as the unary minus on a one's complement computer. Most modern computers work with two's complement, so it isn't a waste of time having it there.
Illustrating the use of these operators is easier if we can use hexadecimal
notation rather than decimal, so now is the time to see hexadecimal constants.
Any number written with
at its beginning is interpreted as hexadecimal; both
0XF) mean the same thing. Try running
this or, better still, try to predict what it does first and then try running
The way that the loop works in that example is the first thing to study. The
controlling variable is
which is initialized to zero. Every time round the loop it is compared
y, which has
been set to a word-length independent pattern of all
shifted left once and has 1 ORed into it, giving rise to a sequence that
For each of the AND, OR, and XOR (exclusive OR) operators,
x is operated on by the operator
and some other interesting operand, then the result printed.
The left and right shift operators are in there too, giving a result which has the type and value of their left-hand operand shifted in the required direction a number of places specified by their right-hand operand; the type of both of the operands must be integral. Bits shifted off either end of the left operand simply disappear. Shifting by more bits than there are in a word gives an implementation dependent result.
Shifting left guarantees to shift zeros into the low-order bits.
Right shift is fussier. Your implementation is allowed to choose whether, when shifting signed operands, it performs a logical or arithmetic right shift. This means that a logical shift shifts zeros into the most significant bit positions; an arithmetic shift copies the current contents of the most significant bit back into itself. The position is clearer if an unsigned operand is right shifted, because there is no choice: it must be a logical shift. For that reason, whenever right shift is being used, you would expect to find that the thing being shifted had been declared to be unsigned, or cast to unsigned for the shift, as in the example:int i,j;
The second (right-hand) operand of a shift operator does not have to be a constant; any integral expression is legal. Importantly, the rules involving mixed types of operands do not apply to the shift operators. The result of the shift has the same type as the thing that got shifted (after the integral promotions), and depends on nothing else.
Now something different; one of those little tricks that C programmers
find helps to write better programs. If for any reason you want to form a value
0x0f0 and all the
other bits to
The one's complement of the desired low-order bit pattern has been one's complemented. That gives exactly the required result and is completely independent of word length; it is a very common sight in C code.
There isn't a lot more to say about the bit-twiddling operators, and our experience of teaching C has been that most people find them easy to learn. Let's move on.
No, that isn't a mistake, ‘operators’ was meant to be plural. C has
several assignment operators, even though we have only seen the plain
result has the type of
and the value that was assigned. It can be used like this
a will now
have the value
has been assigned to. All of the simpler assignments that we have seen until
now (except for one example) have simply discarded the resulting value of the
assignment, even though it is produced.
It's because assignment has a result that an expression likea = b = c = d;
works. The value of
is assigned to
result of that is assigned to
and so on. It makes use of the fact that expressions involving only assignment
operators are evaluated from right to left, but is otherwise like any other
expression. (The rules explaining what groups right to left and vice versa are
given in Table 2.9.)
If you look back to the section describing ‘conversions’, there is a description of what happens if you convert longer types to shorter types: that is what happens when the left-hand operand of an assignment is shorter than the right-hand one. No conversions are applied to the right-hand operand of the simple assignment operator.
The remaining assignment operators are the compound assignment operators. They allow a useful shorthand, where an assignment containing the same left- and right-hand sides can be compressed; for examplex = x + 1;
can be written asx += 1;
using one of the compound assignment operators. The result is the same in each case. It is a useful thing to do when the left-hand side of the operator is a complicated expression, not just a variable; such things occur when you start to use arrays and pointers. Most experienced C programmers tend to use the form given in the second example because somehow it ‘feels better’, a sentiment that no beginner has ever been known to agree with. Table 2.8 lists the compound assignment operators; you will see them used a lot from now on.
Table Compound assignment operators
In each case, arithmetic conversions are applied as if the expression had
been written out in full, for example as if
a+=b had been written
Reiterating: the result of an assignment operator has both the value and the type of the object that was assigned to.
It is so common to simply add or subtract 1 in an expression that C has two
special unary operators to do the job. The increment operator
where the operator can come either before or after its operand. In the cases shown it doesn't matter where the operator comes, but in more complicated cases the difference has a definite meaning and must be used properly.
Here is the difference being used.#include <stdio.h>
The results printed were11
The difference is caused by the different positions of the operators. If the inc/decrement operator appears in front of the variable, then its value is changed by one and the new value is used in the expression. If the operator comes after the variable, then the old value is used in the expression and the variable's value is changed afterwards.
C programmers never add or subtract one with statements like thisx += 1;
they invariably use one ofx++; /* or */ ++x;
as a matter of course. A warning is in order though: it is not safe to use a variable more than once in an expression if it has one of these operators attached to it. There is no guarantee of when, within an expression, the affected variable will actually change value. The compiler might choose to ‘save up’ all of the changes and apply them at once, so an expression like thisy = x++ + --x;
does not guarantee to assign twice the original value of
y. It might be evaluated as if it expanded to this
because the compiler notices that the overall effect on the value of
x is zero.
The arithmetic is done exactly as if the full addition expression had been
used, for example
and the usual arithmetic conversions apply.
Exercise 2.16. Given the following variable definitionsint i1, i2;
i1is divided by
i1is divided by the value of
f1as an integer?
i1the low-order 8 bits in
i2, but swapping the significance of the lowest four with the next
After looking at the operators we have to consider the way that they work together. For things like addition it may not seem important; it hardly matters whether
is done as(a + b) + c
ora + (b + c)
does it? Well, yes in fact it does. If
a+b would overflow and
c held a value very close to
-b, then the second grouping might give
the correct answer where the first would cause undefined behaviour. The problem
is much more obvious with integer division:
gives very different results when grouped asa/(b/c)
If you don't believe that, try it with
c=3. The first gives
The grouping of operators like that is known as associativity. The other question is one of precedence, where some operators have a higher priority than others and force evaluation of sub-expressions involving them to be performed before those with lower precedence operators. This is almost universal practice in high-level languages, so we ‘know’ thata + b * c + d
groups asa + (b * c) + d
indicating that multiplication has higher precedence than addition.
The large set of operators in C gives rise to 15 levels of precedence! Only very boring people bother to remember them all. The complete list is given in Table 2.9, which indicates both precedence and associativity. Not all of the operators have been mentioned yet. Beware of the use of the same symbol for both unary and binary operators: the table indicates which are which.
left to right
right to left
left to right
left to right
left to right
left to right
left to right
left to right
left to right
left to right
left to right
left to right
right to left
right to left
left to right
1. Parentheses are for expression grouping, not function call.
2. This is unusual. See Section 3.4.1 .
Table 2.9. Operator precedence and associativity
The question is, what can you do with that information, now that it's there?
Obviously it's important to be able to work out both how to write expressions
that evaluate in the proper order, and also how to read other people's. The
technique is this: first, identify the unary operators and the operands that
they refer to. This isn't such a difficult task but it takes some practice,
especially when you discover that operators such as unary
by something, where the something is an expression involving
b and several unary
It's not too difficult to work out which are the unary operators; here are the rules.
are always unary operators.
Because the unary operators have very high precedence, you can work out what
they do before worrying about the other operators. One thing to watch out for
is the way that
has two unary operators applied to
The unary operators all associate right to left, so although the
The case is a little clearer if the prefix, rather than the postfix, form of the increment/decrement operators is being used. Again the order is right to left, but at least the operators come all in a row.
After sorting out what to do with the unary operators, it's easy to read the expression from left to right. Every time you see a binary operator, remember it. Look to the right: if the next binary operator is of a lower precedence, then the operator you just remembered is part of a subexpression to evaluate before anything else is seen. If the next operator is of the same precedence, keep repeating the procedure as long as equal precedence operators are seen. When you eventually find a lower precedence operator, evaluate the subexpression on the left according to the associativity rules. If a higher precedence operator is found on the right, forget the previous stuff: the operand to the left of the higher precedence operator is part of a subexpression separate from anything on the left so far. It belongs to the new operator instead.
If that lot isn't clear don't worry. A lot of C programmers have trouble with this area and eventually learn to parenthesize these expressions ‘by eye’, without ever using formal rules.
What does matter is what happens when you have fully parenthesized these expressions. Remember the ‘usual arithmetic conversions’? They explained how you could predict the type of an expression from the operands involved. Now, even if you mix all sorts of types in a complicated expression, the types of the subexpressions are determined only from the the types of the operands in the subexpression. Look at this.#include <stdio.h>
The value printed is
was involved the whole statement involving the division would be done in that
Of course, the division operator had only int types on either side, so the
arithmetic was done as integer division and resulted in zero. The addition had
float and an
int on either side, so the conversions
meant that the
float for the
arithmetic, and that was the correct type for the assignment, so there were no
The previous section on casts showed one way of changing the type of an expression from its natural one to the one that you want. Be careful though:(float)(j/i)
would still use integer division, then convert the result to
float. To keep the remainder, you should
which would force real division to be used.
C allows you to override the normal effects of precedence and associativity by the use of parentheses as the examples have illustrated. In Old C, the parentheses had no further meaning, and in particular did not guarantee anything about the order of evaluation in expressions like these:int a, b, c;
You used to need to use explicit temporary variables to get a particular order of evaluation—something that matters if you know that there are risks of overflow in a particular expression, but by forcing the evaluation to be in a certain order you can avoid it.
Standard C says that evaluation must be done in the order indicated by the precedence and grouping of the expression, unless the compiler can tell that the result will not be affected by any regrouping it might do for optimization reasons.
So, the expression
a = 10+a+b+5;
cannot be rewritten by the compiler as
= 15+a+b; unless it can be guaranteed that the resulting value of
a will be the same for all combinations of initial values of
b. That would be true if the variables were both unsigned
integral types, or if they were signed integral types but in that particular
implementation overflow did not cause a run-time exception and overflow was
To repeat and expand the warning given for the increment operators: it is
unsafe to use the same variable more than once in an expression if evaluating
the expression changes the variable and the new value could affect the result
of the expression. This is because the change(s) may be ‘saved up’ and only
applied at the end of the statement. So
= f+1; is safe even though
f appears twice in a value-changing expression,
f++; is also safe, but
f = f++; is unsafe.
The problem can be caused by using an assignment, use of the increment or decrement operators, or by calling a function that changes the value of an external variable that is also used in the expression. These are generally known as ‘side effects’. C makes almost no promise that side effects will occur in a predictable order within a single expression. (The discussion of ‘sequence points’ in Chapter 8 [https://publications.gbdirect.co.uk/c_book/chapter8/] will be of interest if you care about this.)
Previous section [https://publications.gbdirect.co.uk/c_book/chapter2/integral_types.html] | Chapter contents [https://publications.gbdirect.co.uk/c_book/chapter2/] | Next section [https://publications.gbdirect.co.uk/c_book/chapter2/constants.html]
Politica de confidentialitate|
Adauga cod HTML in site