Functions in C: Declaration, Definition and Call by Value (C Learning Guide, Part 5)
By Part 4 the packing tool could loop through orders, but every piece of logic still lived inside main. A function gives a piece of work a name: write the check once, call it from ten places, and fix it in one spot when it changes. This part moves the packing tool's jobs into functions and shows exactly what C copies when you call one.
This is Part 5 of the C Learning Guide series. Part 1 built the first working packing program, Part 2 added variables, types, printf and scanf, Part 3 taught the tool to refuse bad input with if and else, and Part 4 let one run handle many orders with loops. Every program below was compiled and run for these notes; the outputs shown are the real outputs.
1. Naming a piece of work: what a function is
A function is a named block of code that does one job. You have already used functions written by other people: printf prints, scanf reads, and every program starts in a function called main. Writing your own function means you pick a job, give it a name, and then use that name anywhere you need the job done.
The packing tool has three natural jobs: read a positive number safely, work out how many boxes an order needs, and print the packing lines for one order. Right now all three sit inside main, and the read-a-number logic is written twice. With functions, each job is written once.
2. Definition, declaration and call
Three words that exams love to mix up:
- A definition is the function itself: the name, the parameter list, the return type, and the body between braces. This is where the work lives.
- A declaration (also called a prototype) is only the first line plus a semicolon. It tells C the name, the parameter types and the return type, so calls can be checked before the full definition appears.
- A call is using the name with actual values, which runs the body and gives back the return value.
Here is the box-counting job as a real function, called three times:
#include <stdio.h>
int boxesNeeded(int items, int capacity) {
int boxes = items / capacity;
if (items % capacity != 0) {
boxes = boxes + 1;
}
return boxes;
}
int main(void) {
printf("items=%d capacity=%d boxes=%d\n", 17, 6, boxesNeeded(17, 6));
printf("items=%d capacity=%d boxes=%d\n", 12, 6, boxesNeeded(12, 6));
printf("items=%d capacity=%d boxes=%d\n", 5, 6, boxesNeeded(5, 6));
return 0;
}
Line by line: int boxesNeeded(int items, int capacity) is the definition's first line. The int at the front is the return type - the function hands back one int. Inside the braces is the body, the same full-boxes-plus-one logic the tool used in Part 1, now written once. In main, boxesNeeded(17, 6) is a call: C runs the body with items as 17 and capacity as 6, and the return boxes; line sends the answer back into the printf.
Verified output:
items=17 capacity=6 boxes=3
items=12 capacity=6 boxes=2
items=5 capacity=6 boxes=1
Seventeen items in boxes of six need three boxes, twelve need exactly two, and five fit in one. The function did not know or care which line called it; each call ran the same body with its own numbers.
3. Parameters in, return value out
Parameters are the function's inputs, listed in the parentheses with a type each. Arguments are the actual values you pass at the call: in boxesNeeded(17, 6), the argument 17 lands in the parameter items and 6 lands in capacity, in order. The return value is the single value the function hands back with return.
A function does not have to return anything. A function whose job is only to print can be declared with return type void, and it simply runs to the end of its body (or hits a bare return; with no value). You will see a void function in the full tool below.
4. Declaring before use: the prototype
C reads a file top to bottom, and it wants to know a function's name and types before any call. If the definition sits below main, put a declaration above main instead: the first line of the definition, ended with a semicolon. The definition can then live anywhere later in the file.
#include <stdio.h>
int boxesNeeded(int items, int capacity);
int main(void) {
printf("%d\n", boxesNeeded(17, 6));
return 0;
}
int boxesNeeded(int items, int capacity) {
int boxes = items / capacity;
if (items % capacity != 0) {
boxes = boxes + 1;
}
return boxes;
}
Verified output:
3
The second line, int boxesNeeded(int items, int capacity);, is the declaration. When the compiler reaches the call in main it already knows boxesNeeded takes two ints and returns an int, so it can check the call. The real body comes after main, unchanged.
5. Call by value: the function gets a copy
When you pass a variable to a C function, the function does not receive your variable. C makes a copy of the value and gives the copy to the parameter. Changing the parameter inside the function changes only the copy; the caller's variable is untouched. This rule is called call by value, and it is the only way plain C passes arguments.
#include <stdio.h>
void tryToDouble(int x) {
x = x * 2;
printf("inside the function, x is %d\n", x);
}
int main(void) {
int items = 21;
tryToDouble(items);
printf("back in main, items is still %d\n", items);
return 0;
}
Verified output:
inside the function, x is 42
back in main, items is still 21
Inside tryToDouble the parameter x really did become 42. Back in main, items is still 21, because x was a copy. If a function must give something back, return it - do not count on changing the caller's variable. (Changing a caller's variable directly needs pointers, which are a later part of this series.)
6. The packing tool rebuilt with functions
Now the three jobs become three functions, and main only reads the plan and calls them. readPositive owns the Part 3 input check (ask again until the answer is a positive whole number), boxesNeeded owns the box count, and packOrder owns the printing loop from Part 4.
#include <stdio.h>
int readPositive(char name[]) {
int value;
int ok;
do {
printf("Enter %s (positive whole number): ", name);
ok = scanf("%d", &value);
if (ok != 1) {
printf("That was not a number. Try again.\n");
scanf("%*[^\n]");
} else if (value <= 0) {
printf("%s must be above zero. Try again.\n", name);
}
} while (ok != 1 || value <= 0);
return value;
}
int boxesNeeded(int items, int capacity) {
int boxes = items / capacity;
if (items % capacity != 0) {
boxes = boxes + 1;
}
return boxes;
}
void packOrder(int order, int items, int capacity) {
int remaining = items;
int box = 1;
while (remaining > 0) {
int take = capacity;
if (remaining < capacity) {
take = remaining;
}
printf("Order %d, box %d: packed %d items\n", order, box, take);
remaining = remaining - take;
box = box + 1;
}
}
int main(void) {
int capacity = readPositive("box capacity");
int orders = readPositive("number of orders");
int i;
for (i = 1; i <= orders; i = i + 1) {
int items = readPositive("items in this order");
packOrder(i, items, capacity);
int need = boxesNeeded(items, capacity);
if (need == 1) {
printf("Order %d needs 1 box\n", i);
} else {
printf("Order %d needs %d boxes\n", i, need);
}
}
return 0;
}
Read main first: it asks for capacity and the number of orders, then loops through the orders. Every hard job is one named call. readPositive("box capacity") passes the question's name as a piece of text (a string, written between double quotes), so one function asks all three questions. packOrder is the void function from section 3: it returns nothing, because its job is printing.
Verified run with real input 6, 2, 5, 16:
Enter box capacity (positive whole number): Enter number of orders (positive whole number): Enter items in this order (positive whole number): Order 1, box 1: packed 5 items
Order 1 needs 1 box
Enter items in this order (positive whole number): Order 2, box 1: packed 6 items
Order 2, box 2: packed 6 items
Order 2, box 3: packed 4 items
Order 2 needs 3 boxes
And the refusal still works when the first answer is not a number and the second is negative (input abc, -3, then 6, 1, 9):
Enter box capacity (positive whole number): That was not a number. Try again.
Enter box capacity (positive whole number): box capacity must be above zero. Try again.
Enter box capacity (positive whole number): Enter number of orders (positive whole number): Enter items in this order (positive whole number): Order 1, box 1: packed 6 items
Order 1, box 2: packed 3 items
Order 1 needs 2 boxes
The check that used to be written out twice is now written once, inside readPositive, and both prompts get it for free. That is the practical point of functions: one fix, everywhere.
7. Wrong turns with functions
Three mistakes every beginner meets, with the real compiler messages from a C17 gcc.
Calling before declaring. If main calls boxesNeeded but no declaration and no definition has appeared above it, gcc prints:
warning: implicit declaration of function 'boxesNeeded'
Older C used to guess that an undeclared function returns int, and the guess could be silently wrong. Modern C does not allow this, and many compilers treat it as an error. The fix is the prototype from section 4.
Forgetting to return. If a non-void function's body ends without a return, gcc prints:
warning: control reaches end of non-void function
The caller then receives an unpredictable value. If a function promises an int, every path through it must return one.
Declaration and definition disagreeing. If the prototype says int boxesNeeded(int items, int capacity); but the definition below takes a double, gcc stops with an error:
error: conflicting types for 'boxesNeeded'
This one is a hard error, not a warning: the compiler refuses to build. Keep the declaration and the definition's first line identical.
8. C programs for practice with solutions
Type each solution yourself and run it before reading the explanation. You can paste any program from this part into the TechVani online compiler and run it there.
Practice 1: even or odd, decided by a function
#include <stdio.h>
int isEven(int n) {
if (n % 2 == 0) {
return 1;
}
return 0;
}
int main(void) {
int n;
for (n = 7; n <= 9; n = n + 1) {
if (isEven(n)) {
printf("%d is even\n", n);
} else {
printf("%d is odd\n", n);
}
}
return 0;
}
Verified output:
7 is odd
8 is even
9 is odd
isEven returns 1 for even and 0 for odd, using the remainder operator from Part 1. Returning 1 and 0 for yes and no is a common C habit; the caller then uses the answer directly inside an if.
Practice 2: power with a loop inside a function
#include <stdio.h>
long power(int base, int exp) {
long result = 1;
int i;
for (i = 0; i < exp; i = i + 1) {
result = result * base;
}
return result;
}
int main(void) {
printf("2^10 = %ld\n", power(2, 10));
printf("3^4 = %ld\n", power(3, 4));
printf("5^0 = %ld\n", power(5, 0));
return 0;
}
Verified output:
2^10 = 1024
3^4 = 81
5^0 = 1
power multiplies result by base exactly exp times, using the counted for loop from Part 4. The answer grows fast, so the function returns a long and printf uses %ld to print it. A base to the power 0 gives 1 here, because result starts at 1 and the loop never runs.
What was checked, and what comes next
All seven programs in this part were compiled with a C17 compiler with warnings on, and every output block above is the captured output of a real run, including the refused inputs and the compiler messages in section 7. Next in the series: arrays - storing many values under one name, so the tool can remember a whole day of orders instead of one number at a time. Continue with the series index, or browse the Exam Notes.
Sources and scope
- cppreference: functions for definition, declaration, parameters and return.
- cppreference: return statement for handing a value back to the caller.
- cppreference: function declaration for the prototype form used before a call.
- cppreference: function call operator for how arguments are passed by value.
Programs were tested on 64-bit Linux with gcc, where int is 32 bits and long is 64 bits. Results on other systems can differ for very large values.
Comments
Post a Comment