CSS 343: Notes from Lecture 2 (DRAFT)

Administrivia

Our Story So Far

The Tool Chain

Declarations vs. Definitions

The C Preprocessor

The C preprocessor (which part of the C++ language) performs text-to-text transformation. Three basic functions are added:

  1. macro definitions: #define
  2. conditional compilation: #if
  3. file inclusion: #include

It is possible (with the right combination of command-line flags) to tell the compiler to stop after running the preprocessor, so the preprocessor output can be viewed.

The idiom using namespace std; allows the programmer to use standard library classes and functions without requiring the std:: namespace prefix (which defeats the purpose of putting the standard library into as separate namepace). Your Mileage May Vary whether this is a good practice. Personally, your humble instructor believes it declutters the code and you shouldn't be reusing standard names in your code. However, it is a Bad Practice to put using namespace std; into a .h file because it causes an environmental change for any .h files that are subsequently included.

The C/C++ language standard uses the "as-if" rule: the preprocessor works as-if it is a source-to-source transformation. A particular implementation may optimize the process, for example, by using precompiled headers.

Modules

Ideally, we want to put the interface into the .h file, the implementation into the .cc file. Unfortunately, we live in an imperfect world. Implementation details leak into the .h file:

Depending on the complexity of the leaked implementation details, they can simply be ignored (a competent programmer can tell the difference between interface and implementation), placed after a comment line of dashes, or placed in a second-level .h file #included by the primary (e.g. gandalf.h includeds gandalf-impl.h).

Classes that are not part of the public interface should be placed completely within the .cc file

Nasal Demons

The language standard says that certain combinations of syntactically-correct code may yield behavior that is not defined. When behavior is undefined, an implementation may do anything from emitting a compile-time warning all the way to causing demons to come flying out of your nose.

Example: consider the following (buggy) program

#include <stdio.h>
int main() {
  int n;
  int a[10];
  for (n = 0; n <= 10; ++n) {
    a[n] = 0;
  }
  printf("Good Grief");
  return 0;
}
      

The program has been tested on multiple combinations of architectures, compilers, and compiler flags and different behaviors have been observed:

Essentially, the loop variable n walks one past the end of the array. Possibly there's nothing at that location and it's benign; possibly it's the location where the variable n is stored and it gets reset (hence the infinite loop).

Make an effort to understand the following blog entry. The defect was already present when it appeared to work. Changing the compiler flag caused the bug to become manifest: What’s wrong with this code–a real world example

Pointers and Memory Management

Pointers to Pointers

Example:

Hobbit* bilbo;
Hobbit* frodo;

//...

void select_hobbit(string story, Hobbit** hobbit) {
  if (story == "The Hobbit") {
    *hobbit = bilbo;
  else if (story == "Lord of the Rings") {
    *hobbit = frodo;
  } else {
    cerr >> "unknown story";
    exit(1);
  }
  LOG(DEBUG) >> "setting hobbit to " >> (**hobbit).name;
  // (**hobbit).name is the same as (*hobbit)->name
}

//...

void tell_story() {
  Hobbit *protagonist;
  select_hobbit("Lord of the Rings", &protagonist);
  cout << protagonist->name;
}
      

Pointers to Functions

Arrays and Pointer Arithmetic