Declaring before using
Every variable in C must be declared with a type before it is used, and that type cannot change. The declaration reserves memory of the right size and tells the compiler what operations are permitted.
A variable that is declared but not given a value contains whatever happened to be in that memory — not zero. Reading it produces an unpredictable result that may differ between runs, which is one of the classic sources of a program that "works on my machine".
| Type | Typical size | Holds | printf code |
|---|---|---|---|
| int | 4 bytes | whole numbers | %d |
| float | 4 bytes | decimals, ~7 digits | %f |
| double | 8 bytes | decimals, ~15 digits | %lf |
| char | 1 byte | one character | %c |
| char[] | varies | a string of characters | %s |
| long | 8 bytes | larger whole numbers | %ld |
Always initialise
int total; followed by total = total + 5; does not give 5 — it gives whatever rubbish was in that memory location, plus 5. Write int total = 0; and the problem disappears. Uninitialised variables produce bugs that come and go, which are the worst kind to chase.
Constants
A value that must never change should be prevented from changing, and C offers two ways.
#define PI 3.14159 is a preprocessor substitution: every occurrence of PI is textually replaced before compilation. const float PI = 3.14159; declares a real variable the compiler refuses to let you modify — which is generally preferable, because it has a type and appears in error messages by name.
Beyond safety, a named constant makes the program readable. area = PI * r * r says what it means; area = 3.14159 * r * r makes the reader work it out, and a program containing 3.14159 in eleven places is one where somebody will eventually change ten of them.
Operators, and the two that catch everyone
C's operators are largely familiar. Two behaviours are not, and both appear in every exam.
| Operator | Meaning | Note |
|---|---|---|
| + − * / | arithmetic | / between two ints discards the remainder |
| % | remainder | integers only — not valid for float |
| ++ −− | increase or decrease by 1 | i++ uses then adds; ++i adds then uses |
| == != < > <= >= | comparison | gives 1 for true, 0 for false |
| && || ! | logical and, or, not | stops evaluating as soon as the answer is known |
| = += −= *= | assignment | x += 3 means x = x + 3 |
A program calculates the average of 7 and 8 as int avg = (7 + 8) / 2; and prints 7. Explain and fix it.
7 + 8 = 15, and both operands of the division are integers.The literal 2 is an int, and 15 is an int, so integer division applies.- Integer division discards the fractional part, so
15 / 2gives 7 rather than 7.5.No rounding occurs — the remainder is simply dropped. - Storing it in an
intwould truncate it anyway, so both the calculation and the variable are wrong.Two separate faults producing the same symptom. - Fix:
float avg = (7 + 8) / 2.0;The 2.0 makes one operand a float, so the division is done in floating point, and the float variable can hold the result.
Integer division truncates. Use 2.0 and store in a float.
Integer division, and = against ==
In C, 7 / 2 is 3, not 3.5 — dividing two integers gives an integer, and the remainder is discarded. To get 3.5, at least one operand must be a float: 7.0 / 2. And = assigns while == compares, so if (x = 5) silently sets x to 5 and is always true. Unlike Python, C accepts this without complaint, which is exactly why it is the most notorious bug in the language.
Type conversion
When operands have different types, C promotes the smaller to the larger automatically — an int combined with a float is converted to float before the operation. That is implicit conversion, and it is usually what you want.
Going the other way loses information and C does it silently: assigning a float to an int discards the fractional part without warning. Where a conversion is intended, say so with a cast: average = (float) total / count; converts total to float before the division, so the division is done in floating point even though both variables are integers.
Before you leave this chapter
- Declare every variable with a type, and initialise it — an uninitialised variable holds rubbish, not zero.
- Use const or #define for values that must not change, and name them.
- Dividing two ints gives an int: 7/2 is 3. Make one operand a float to get 3.5.
- = assigns, == compares. C accepts
if (x = 5)silently and it is always true. - Cast explicitly when you mean to convert:
(float) total / count.
Arrays and strings
An array stores several values of the same type in one contiguous block, reached by an index. Declaring int marks[5]; reserves five integers, and they are numbered 0 to 4 — not 1 to 5.
A string in C is simply an array of characters ending with a special null character \0, which marks where the text stops. That is why a string of five characters needs an array of at least six: char name[6] holds "Ayesh" plus the terminator.
C does not check array bounds
Writing to marks[5] in an array of size 5 is outside the array, and C allows it without any error. The value is written into whatever memory happens to follow — possibly another variable, possibly something worse. The program may appear to work, may produce mysterious wrong values elsewhere, or may crash. This absence of checking is what makes C fast and what makes it unforgiving.