Computer ScienceCore20 min read

Elements of C

Data types, variables, operators — and the traps each one sets

This topic appears in:

01

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

TypeTypical sizeHoldsprintf code
int4 byteswhole numbers%d
float4 bytesdecimals, ~7 digits%f
double8 bytesdecimals, ~15 digits%lf
char1 byteone character%c
char[]variesa string of characters%s
long8 byteslarger 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.

02

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.

03

Operators, and the two that catch everyone

C's operators are largely familiar. Two behaviours are not, and both appear in every exam.

OperatorMeaningNote
+ − * /arithmetic/ between two ints discards the remainder
%remainderintegers only — not valid for float
++ −−increase or decrease by 1i++ uses then adds; ++i adds then uses
== != < > <= >=comparisongives 1 for true, 0 for false
&& || !logical and, or, notstops evaluating as soon as the answer is known
= += −= *=assignmentx += 3 means x = x + 3
Worked example

A program calculates the average of 7 and 8 as int avg = (7 + 8) / 2; and prints 7. Explain and fix it.

  1. 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.
  2. Integer division discards the fractional part, so 15 / 2 gives 7 rather than 7.5.No rounding occurs — the remainder is simply dropped.
  3. Storing it in an int would truncate it anyway, so both the calculation and the variable are wrong.Two separate faults producing the same symptom.
  4. 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.

04

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

  1. Declare every variable with a type, and initialise it — an uninitialised variable holds rubbish, not zero.
  2. Use const or #define for values that must not change, and name them.
  3. Dividing two ints gives an int: 7/2 is 3. Make one operand a float to get 3.5.
  4. = assigns, == compares. C accepts if (x = 5) silently and it is always true.
  5. Cast explicitly when you mean to convert: (float) total / count.
05

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.

int marks[5] = {70, 65, 80, 55, 90};printf("%d", marks[0]);/* the FIRST element */printf("%d", marks[4]);/* the LAST element */char name[20] = "Ayesha";/* 6 characters plus \0 */an array of size n has valid indices 0 to n−1, and C does not check them for you

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.

Practice questions

6 questions · 20 marks · full working on every one

Try each one on paper first, then open the working. The marks are shown where they are actually awarded, because that is where they are actually lost.

Short questions

3 · 6 marks

Two marks each, in the style of the short-question section of the paper. Answer in two or three lines.

SQ1[2 marks]
Why must a variable be initialised before it is read?
Model answer

A declared but uninitialised variable contains whatever data happened to already be in that memory location — not zero. Reading it gives an unpredictable value that may differ between runs, producing bugs that appear and disappear.

Examiner tip. The phrase "not zero" is the mark. Many students assume C zeroes variables automatically, and it does not.

SQ2[2 marks]
What is the value of 9 / 2 and of 9 % 2 in C, given both operands are int?
Model answer

9 / 2 is 4 — integer division discards the fractional part. 9 % 2 is 1, the remainder.

Examiner tip. No rounding takes place: 9/2 is 4, not 5. The fraction is dropped, not rounded.

SQ3[2 marks]
Explain the difference between = and ==, and why confusing them is dangerous in C.
Model answer

= assigns a value; == tests equality. if (x = 5) assigns 5 to x and then treats the result as the condition, which is non-zero and therefore always true — so the branch always runs and x has been changed. C accepts this without error, so nothing warns you.

Examiner tip. The point is not just that they differ but that C compiles the mistake silently, which is why it is a classic bug.

Solved numericals

2 · 8 marks

Full working, one step per line, with the marks shown where they are awarded.

N1[4 marks]
State the output of each: (i) printf("%d", 15/4); (ii) printf("%d", 15%4); (iii) printf("%f", 15.0/4); (iv) int i=5; printf("%d", i++);
Full working
  1. (i) 3 — integer division discards the remaindernot 3.75 and not 4[1]
  2. (ii) 3 — the remainder when 15 is divided by 4[1]
  3. (iii) 3.750000 — one operand is a float, so the division is floating point%f prints six decimal places by default[1]
  4. (iv) 5 — the post-increment uses the value first and increments afterwardsi becomes 6 after the printf[1]

(i) 3 (ii) 3 (iii) 3.750000 (iv) 5

Examiner tip. For (iv), remember i++ means "use it, then add". ++i would print 6, because it adds first.

N2[4 marks]
A program has int total = 17, count = 4; float avg;. Write the line that correctly calculates the average, and explain why the obvious version fails.
Full working
  1. avg = (float) total / count;accept total / (float) count[1]
  2. Writing avg = total / count; divides two ints, giving 4the fractional part is discarded before assignment[1]
  3. Assigning that to a float then stores 4.0, so the float type does not rescue itthe damage is already done[1]
  4. The cast converts total to float before the division, so floating-point division is used and the answer is 4.25[1]

avg = (float) total / count; — the cast must come before the division, not after.

Examiner tip. Declaring avg as float is not enough. The type of the division is what matters, and that is decided by its operands.

Long questions

1 · 6 marks

Theory and numerical together, as they appear in the long-question section.

LQ1[6 marks]
Consider this fragment: int a = 7, b = 2; float result; result = a / b; printf("%f", result);
  1. State the output and explain why.
  2. Give two different ways to correct it.
  3. Explain the difference between implicit and explicit type conversion.
Mark scheme
  1. Output is 3.000000not 3.5[1]
  2. Both a and b are int, so integer division is performed and gives 3; converting 3 to float afterwards produces 3.0the conversion happens too late[1]
  3. Fix 1: cast one operand — result = (float) a / b;[1]
  4. Fix 2: declare a or b as float, or divide by a float literal such as 2.0any second valid approach[1]
  5. Implicit conversion is performed automatically by the compiler when types are mixed, promoting the smaller type[1]
  6. Explicit conversion is requested by the programmer with a cast, and is needed when the automatic behaviour is not what you want — as here[1]

(a) 3.000000, because int/int is evaluated first (b) cast an operand, or make one a float (c) implicit is automatic promotion; explicit is a programmer-requested cast

Examiner tip. The key insight is the order: the division happens with the types the operands have, and only then is the result converted. Fixing the variable type of the destination is always too late.