Function Pointers and Makefiles
======
# Function Pointers
## Motivation
Write functions to calculate cumulative values starting at 1 to n elements:
- factorial up to n
- cumulative sum up to n
- sum of squares for n elements
- sum of every other element starting from 1 up to n.\
Here is how I did it.
```
int factorial(int n)
{
int i;
int factorial;
i = 1;
factorial = 1;
while (i < n)
{
i = i + 1;
factorial = factorial * i;
}
return (factorial);
}
int cum_sum(int n)
{
int i;
int sum;
i = 1;
sum = 1;
while (i < n)
{
i = i + 1;
sum = sum + i;
}
return(sum);
}
int cum_squares(int n)
{
int i;
int cum_sum;
i = 1;
cum_sum = 1;
while (i < n)
{
i = i + 1;
cum_sum = cum_sum + (i * i);
}
return (cum_sum);
}
int sum_every_other(int n)
{
int i;
int sum;
i = 1;
sum = 1;
while (i < n)
{
i = i + 1;
sum = sum + (i * 2 - 1);
}
return (sum);
}
```
**Observe**: with the exception of the line where the values are accumulated (i.e.: sum = sum + i or factorial = factorial * i), everything else is the same.
This is breaking a fundamental rule for us. [DRY](https://hackmd.io/s/HJu5FyrUf).
## Function Pointer Basics
### Example of Function Pointer Usage
We can solve this using function pointers.
```
int factorial(int factorial, int i)
{
return (factorial * i);
}
int cum_sum(int sum, int i)
{
return (sum + i);
}
int cum_squares(int total, int i)
{
return (total + (i * i));
}
int sum_every_other(int sum, int i)
{
return (sum + (i * 2 - 1));
}
int cum_func(int n, int (*f) (int, int))
{
int i;
int cum;
i = 1;
cum = 1;
while (i < n)
{
i = i + 1;
cum = (*f)(cum, i);
}
return (cum);
}
```
### Function Pointer Breakdown
**NOTE:** ```cum_func``` takes a function pointer in the second argument.
```
function pointer breakdown
int (*f) (int, int)
^ ^ ^ ^
| | | |
| | These are the parameters of the function that the pointer points to.
| This is the function pointer that we use to refer to the function.
This is the return type of the function that the pointer points to
```
We use this format for:
- function declaration
- parameter for the function. (as shown above)
### Using Function Pointer
We use the function that the function pointer points to.
```
/* This is just like using: function_name(cum, i) */
cum = (*f)(cum, i);
/* The following is the same as the above */
cum = f(cum, i);
```
We pass in ```cum``` and ```i``` as the parameters to ```f```.
Let's see how we can use the function pointer to complete the program [above](#Function-Pointer-Basics)
```
int main(int argc, char **argv)
{
if (argc == 2)
{
/* Create a function pointer named f */
/* f returns an int and has two ints for parameters. */
int(*f)(int, int);
/* give the address of factorial to f */
f = &factorial;
/* pass the function pointer to cum_func as an argument*/
printf("factorial: %d\n", cum_func(atoi(argv[1]), f));
/* just use the address */
printf("cum_sum: %d\n", cum_func(atoi(argv[1]), &cum_sum));
}
}
```
## Function Pointer Uses
1. More streamlined code
2. Apply a function to every node in a Linked List
- This is an exercise that you will see in 42.
3. Sorting Flexibility
- Different objects might need to be sorted different ways.
- Ascending vs. Descending.
- Allows user of function to write their own comparing function.
- built in sorting functions has a parameter for your custom comparators.
4. Reaction for a Button.
- Read more about it [here](https://www.cprogramming.com/tutorial/function-pointers.html)
Note: I might come back and write up the example for sorting flexibility if there is high enough request or if I want to do it... For now, this will be all for function pointer.
# Makefile Basics
Makefile is an extensive topic so needless to say, I don't go over everything. Here is the [manual](https://www.gnu.org/software/make/manual/make.html#toc-An-Introduction-to-Makefiles).
I go over what I think is important for basic use of Makefiles. I still have much to learn, so if you spot an error, please let me know!
## What is a Makefile?
**Make - A tool at your disposal**:
```make``` - finds which pieces of a large program need to be recompiled and recompiles them.
**Makefile**
The file with instructions for ```make``` on how to compile and link the program.
**Rules**
Explain how and when to recompile certain files. Rules will usually consist of three parts:
1. Target - The file that we are trying to compile
2. Prerequisites - The files that are used as input to create the target. The target *depends* on the prerequisites.
3. Recipe - How the target will be created. These are actions that will be taken to by the rule.
- recipes must be started with a tab.
```
target : prerequisite00 prerequisite01 ... (there may be multiple prereqs)
recipe_action
recipe_action
...
```
## Simple Makefile
Here is a simple makefile (modified from the Piscine lecture). Let us understand what is going on.
```
NAME = awesomeprog
SRC = source.c
OBJ = source.o
all: $(NAME)
$(NAME):
gcc -Wall -Wextra -Werror -c $(SRC)
gcc -Wall -Wextra -Werror -o $(NAME) $(OBJ)
clean:
rm -f $(OBJ)
fclean: clean
rm -f $(NAME)
re: fclean all
```
### Variables
- In the first three lines, we define variables ```NAME``` to have the value ```awesomeprog```, ```OBJ``` to have the value ```source.c```, and ```SRC``` to have the value of ```source.c```.
- We can then use the variables by doing the following ```$(VARIABLE_NAME)```.
- In our case:
- ```$(NAME)``` is the executable that we want to create. An executable is like ```a.out```.
- ```$(SRC)``` is the file that we want to compile. We use this to create a object file.
- ```$(OBJ)``` is the object file that we create from the source file.
### Processing the Rules
#### all : $(NAME)
Make will process first target as the default goal. This is also what make will run if we just type ```make```.
```
all : $(NAME)
```
```all``` is a conventional target name for make to build a complete build. We can use any other word, but the word 'all' explains what make will do - build *all* that is required for a complete build of the program.
To be clear consider an example where entire project requires three executables: ex1, ex2, ex3. We would then write the following rule:
```
all : ex1 ex2 ex3
```
To compile the entire project, we can type ```make all```.
As convention dictates, we would write that line as the first rule. Because it is the first rule, we can type ```make``` and make will process that rule.
#### $(NAME) and gcc
In order to process the first rule, Make will need to process each of its prerequisites. In this manner, make will process all the rules that are related to the first rule in some chain of prerequisites. Examine the (modified) lecture example:
```
rule_1 : rule_2;
echo "the recipe for rule 1"
rule_2 :
echo "the recipe for rule 2"
```
Command: ```make```
Outputs:
```
echo "the recipe for rule 2"
the recipe for rule 2 <--- notice rule 2 is processed first
echo "the recipe for rule 1"
the recipe for rule 1
```
Let's examine our original Makefile
```
$(NAME):
gcc -c $(SRC) <--- gives us object files
gcc -o $(NAME) $(OBJ) <--- use object files to create executable
```
- Since the target ```$(NAME)``` doesn't have any dependencies/prerequisites, we just process this rule.
- The recipe tells us to use ```gcc -c``` which will create the object file ```source.o``` from the source file ```source.c```.
- Then ```gcc -o``` will use that object file to create the desired executable, ```awesomeprog```.
### clean fclean re
Notice that these rules are not related to ```all : $(NAME)``` so they are not called when we use ```make``` by itself.
These rules are meant to do things like clean our directory of object files. Things that we would normally do as we compile and recompile.
```
clean:
rm -f $(OBJ) <---- clean removes all object files
fclean: clean <--- will first clean
rm -f $(NAME) <--- removes the executable
re: fclean all <---- first fclean, then rebuild all.
```
I personally like to write fclean with ```rm -f $(NAME) $(OBJ)``` because if make errors with removing object files, it will still remove the executable.
## Let's Improve our Makefile
Here it is for your convenience.
```
NAME = awesomeprog
SRC = source.c
OBJ = source.o
all: $(NAME)
$(NAME):
gcc -Wall -Wextra -Werror -c $(SRC)
gcc -Wall -Wextra -Werror -o $(NAME) $(OBJ)
clean:
rm -f $(OBJ)
fclean: clean
rm -f $(NAME)
re: fclean all
```
### Implicit Rules
```Make``` has implicit rules for common compilation patterns like compiling C files into object files. [These](https://www.gnu.org/software/make/manual/make.html#Catalogue-of-Rules) implicit rules tell ```make``` how to execute these techniques so we don't have to redefine them.
Use implicit rules by not defining recipes for the file in question. When ```make``` sees the file and that there is no recipe for that file, it will use an implicit rule.
```
# Since no recipe for foo.o make usesimplicit rules to update/build foo.o
foo : foo.o
gcc -o foo foo.o
```
- The implicit rule will be able to find the prerequisite source files and the recipe.
- Prerequisites other than source files such as header files must be explicitly stated.
- If a prerequisite file, ```file_a```, has prerequisites, ```file_b```, the implicit rule will "chain" and try to find the prerequisites for ```file_b```, and so on to solve the dependency on ```file_a```
- Implicit rules use variables such as CFLAGS to control compiler actions. We can use this to specify the compiler flags -Wall -Wextra -Werror
***IMPROVEMENT TO ORIGINAL***:
1. Remove ```SRC``` and ```gcc -c $(SRC)```
2. Add variable ```CC``` and set the variable to ```gcc```
3. Add variable ```CFLAGS``` and set it to ```-Wall -Wextra -Werror```
4. Set ```$(OBJ)``` as the dependency for ```$(NAME)```
### Phony Targets
Phony Targets are targets that don't correspond to actual files. It is the name of the recipes that we want to call explicitly.
Here is what we should add: ```.PHONY : clean fclean re```
```.PHONY``` serves two purposes.
1. It prevents conflicts when you have a file named clean, fclean, or re.
- Example: clean without .PHONY
- If you don't have a file named clean, make will think that clean was never updated when you run the command ```make clean```. It will try to update it by running clean's recipe.
- If you do have a file named clean and it hasn't been changed, make will see that clean is updated, so make will not execute the recipe when you run ```make clean```.
2. It improves performance because phony targets are skipped by implicit rules.
**NOTE:** Phony targets should not be the prerequisite of any real file targets.
**IMPROVEMENT TO ORIGINAL**: add ```.PHONY : clean fclean re```
### Improved Makefile
```
CC = gcc
NAME = awesomeprog
CFLAGS = -Wall -Wextra -Werror
OBJ = source.o
all: $(NAME)
$(NAME): $(OBJ)
$(CC) $(CFLAGS) -o $(NAME) $(OBJ)
.PHONY : clean fclean re
clean:
rm -f $(OBJ)
fclean: clean
rm -f $(NAME)
re: fclean all
```
## Simple Makefile with Multiple Files
Let's say I have the following files
source2.c
```
void ft_swap(int *a, int *b)
{
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
```
source.c
```
#include <stdio.h>
#include "source.h"
int main(void)
{
int a;
int b;
a = 1;
b = 2;
printf("before swap: a is %d b is %d\n", a, b);
ft_swap(&a, &b);
printf("before swap: a is %d b is %d\n", a, b);
}
```
source.h
```
#ifndef SOURCE_H
# define SOURCE_H
void ft_swap(int *a, int *b);
# endif
```
Makefile
```
CC = gcc
NAME = awesomeprog
CFLAGS = -Wall -Wextra -Werror
OBJ = source.o source2.o
all: $(NAME)
$(NAME): $(OBJ)
$(CC) $(CFLAGS) -o $(NAME) $(OBJ)
source.o : source.h
.PHONY : clean fclean re
clean:
rm -f $(OBJ)
fclean: clean
rm -f $(NAME)
re: fclean all
```
If I run ```make``` in the terminal, I get the following:
```
gcc -Wall -Wextra -Werror -c -o source.o source.c
gcc -Wall -Wextra -Werror -c -o source2.o source2.c
gcc -Wall -Wextra -Werror -o awesomeprog source.o source2.o
```
We see that ```make``` creates the object files and then uses those .o files to create the executable.
Let's say I want to make source.c shorter. I make the following changes.
source.c
```
#include <stdio.h>
#include "source.h"
int main(void)
{
int a = 1;
int b = 2;
printf("before swap: a is %d b is %d\n", a, b);
ft_swap(&a, &b);
printf("before swap: a is %d b is %d\n", a, b);
}
```
And then I run ```make``` in the terminal. I get the following:
```
gcc -Wall -Wextra -Werror -c -o source.o source.c
gcc -Wall -Wextra -Werror -o awesomeprog source.o source2.o
```
Notice that ```make``` only updates the file that was changed!
## Why We Use Makefiles
1. We saw how makefiles can improve our workflow by doing various compilation and cleaning/managing tasks for us.
2. We also saw how makefile only updates the files that have been changed. This improves performance.