Main

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 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., a min(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:
  1.  
  2. // Never, ever assume a macro is what its name is implying
  3. #define True 0  
  4. #define False 1
  5. #define FileNotFound sqrt(False)
  6.  
  7. // This one is pretty common (unfortunately)
  8. #define RETURN(result) return (result);}
  9.  
  10. // Can you figure out what this actually does?
  11. #include <iostream>
  12. #define System S s;s
  13. #define public
  14. #define static
  15. #define void int
  16. #define main(x) main()
  17. struct F{void println(char* s){std::cout << s << std::endl;}};
  18. struct S{F out;};
  19.  
  20. public static void main(String[] args) {
  21.   System.out.println("Hello World!");
  22. }
  23.  
  24. // Just "No".
  25. #define private public
  26.  
  27. // Sometimes you may not have a choice
  28. #if 0
  29. Everything here will not be visible to the compiler
  30. #endif
  31.  

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:

  1. int *intptr = new int;
  2. int *array = new char[10];   // an array of ints
  3. int **array2D = new int*[5]; // a 2-D array of ints
  4. for (int i = 0; i < 5; i++)
  5.     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.

  1. delete intptr;
  2. delete []array;
  3. for (int i = 0; i < 5; i++) delete []array2D[i];
  4. 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

Green Marinee theme adapted by David Gilbert, powered by PmWiki