Lecture 11
Unix minute
I know a lot of you are Windows users (I used to be one, too), and there is certainly nothing wrong with that. Maybe sometimes you feel a bit frustrated at how complicated or tedious getting a Unix environment on your machine can be -- all these virtual machines to choose from, Cygwin to install and configure, etc. To make you feel better, or at least less bad, consider that not too long ago, this is what it took to get Unix on your Windows machine.
Assignment 3 solution
- A quick look at a few details
A bit more on the preprocessor
So far we have seen how to define preprocessor variables (with #define
), check whether a variable is defined (with #ifdef
) or not defined (with #ifndef
). You can do more in preprocessor macros, such as have arguments:
#define min (X, Y) ((X) < (Y) ? (X) : (Y))
Things to keep in mind (or why macros can be a VERY BAD IDEA). If you must, read enough to do it well, e.g., look at this C Preprocessor FAQ.
- The preprocessor replaces all macro names with their definitions -- so in the above example, wherever it encounters code that looks like
min(a,b)
, it will substitute ((a) < (b) ? (a) : (b)). Why is this bad? We have no namespace capabilities or symbol resolution (remember that the compiler handles that). So if some real function (not the macro) has the same name (e.g., amin(m,n)
function defined in some class or in the global scope), it will get replaced and you will get the wrong code. - Macros are not visible in the debugger, you only get information about the code the compiler saw (if you compiled with
-g
), which makes it very hard to find the place where you need to fix something. - Macros can get very ugly, very fast. Resist the temptation to do cool things with macros! Some examples of malicious or just plan ugly things to do:
- // Never, ever assume a macro is what its name is implying
- #define True 0
- #define False 1
- #define FileNotFound sqrt(False)
- // This one is pretty common (unfortunately)
- #define RETURN(result) return (result);}
- // Can you figure out what this actually does?
- #include <iostream>
- #define System S s;s
- #define public
- #define static
- #define void int
- #define main(x) main()
- struct F{void println(char* s){std::cout << s << std::endl;}};
- struct S{F out;};
- public static void main(String[] args) {
- System.out.println("Hello World!");
- }
- // Just "No".
- #define private public
- // Sometimes you may not have a choice
- #if 0
- Everything here will not be visible to the compiler
- #endif
Dynamic memory allocation in C++
By now you are used to malloc and free. In this part of the lecture we will see how to allocate and free memory with the C++ language constructs.
The new() operator
Simple types:
- int *intptr = new int;
- int *array = new char[10]; // an array of ints
- int **array2D = new int*[5]; // a 2-D array of ints
- for (int i = 0; i < 5; i++)
- array2D[i] = new int[5];
The delete operator
As with malloc and free, you have to deallocate any memory you have allocated with new()
, which you can do with the delete
operator.
- delete intptr;
- delete []array;
- for (int i = 0; i < 5; i++) delete []array2D[i];
- delete []array2D;
Complete example.
Reducing recompilation
Apart from readability, one of the main reasons for putting different class implementations in different files is that if you do that and use a Makefile, it will only compile the file which changed -- and a small file is compiled much faster than a very large one.
Exercise: split up the animals.cpp code into separate cpp files and measure how long it takes to compile when a single file is changed vs the monolithic animals.cpp compile time