Project & Compiler Settings

Last modified: Aug 18, 2020
Contents:

In this exercise we are going to explore the impact of common compiler settings and how to adjust your Code::Blocks project settings to make the compiler do what you need.

1 The Set-up

  1. Get into Code::Blocks and create a new C++ project for this lab.

  2. As usual, Code::Blocks will create a main.cpp file for you.

    Remove it from your project. (…)

  3. In your project directory, create a file sqrtTable.cpp with the following content.

    sqrtTable.cpp
    #include <iostream>
    #include <cmath>
    
    using namespace std;
    
    
    void printTable(int arraySize)
    {
      int counter;
      double inputs[] {1.0, 2.0, 100.0, 1000.0};
      cout << "Square Roots\n\n";
      // Print the square roots
      for (double x: inputs)
    	{
    	  cout << "The square root of " << x << " is " << sqrt(x) << endl;
    	  counter++;
    	}
      cout << "\nDone printing " << counter << " square roots." << endl;
    }
    
    
    int main()
    {
        printTable(4);
        return 0;
    }
    
  4. Add sqrtTable.cpp to your project.

  5. Tell Code::Blocks to compile (build) the code.

You will get compilation messages, yet this is actually perfectly good C++ code.

If you read carefully, you may notice that these are warnings, not errors. For example, one is probably something like:

sqrtTable.cpp: In function ‘void printTable(int)’:
sqrtTable.cpp:10:19: warning: extended initializer lists only available with -std=c++11 or -std=gnu++11
   double inputs[] {1.0, 2.0, 100.0, 1000.0};

(If you are working in OS/X with the clang compiler, you may see something very different.)

Let’s look at this in a bit of detail

sqrtTable.cpp: In function ‘void printTable(int)’:
sqrtTable.cpp:10:19: warning: extended initializer lists only available with -std=c++11 or -std=gnu++11
   double inputs[] {1.0, 2.0, 100.0, 1000.0};
  1. This part tells us how or why the compiler got here – it was compiling a particular function.

  2. This part gives specific location information – in this case the problem was detected in sqrtTable.cpp at line 10, column 19.

    • Keep in mind that compilers cannot tell you where you made a mistake. They can only tell you where they detected the mistake. These are often two very different locations.
  3. This part gives the compiler’s diagnostic of what is wrong.

    Note that this is labeled a “warning”, not an “error”. A “warning” means the the compiler will go ahead and try to compile your code. Some warnings can be discounted after you have examined them, but you should not ignore them entirely. In this case, the problem really is serious enough that the compiled program will not work.

  4. This part shows the line of code in which the problem was detected.

When you are working in an IDE like Code::Blocks, you might not see all four parts of the error message. IDEs will sometimes chop an error message into pieces for the purpose of fitting the IDE’s display style. Sometimes this makes it easier to see what the message is about. Sometimes it makes it harder. But, if you look at the “console” listing after the compiler was run, you can often find the whole message.

In this case, both warning messages say that a language feature used in this code is “only available with -std=c++11”.

2 Welcome to 2017

2.1 Programming Languages Change

Like most programming languages (or like most “real” languages, for that matter), C++ has evolved over the years.

2.2 Compiling with the 2017 Standard

Code::Blocks is capable of issuing the proper compiler commands to use the 2017 standard. The warning messages we received have actually indicated what the compiler option is: -std=c++11. But we need to tell Code::Blocks to compile our code with that option.

  1. From the Settings menu, select “Compiler…”

  2. You should be on the “Global compiler settings” tab. If not, select it.

  3. Put a checkmark by the option:

      Have g++ follow ... C++17 ISO ...
    
  4. Click OK to accept the changes. Then from the “Project” menu select “Project Clean”, then rebuild your project.

    The code should now compile cleanly.

  5. Try running the program.

    You should see some very reasonable values being printed for square roots, followed by a closing message stating how many were printed.

If you are lucky, the closing line printed the correct number. There’s a very good chance, however, that the closing line will print what appears to be a completely random integer. It’s possible that if you run the program repeatedly, you will get a different number printed each time.

2.3 What’s Wrong?

void printTable(int arraySize)
{
  int counter;
  double inputs[] {1.0, 2.0, 100.0, 1000.0};
  cout << "Square Roots\n\n";
  // Print the square roots
  for (double x: inputs)
    {
      cout << "The square root of " << x << " is " << sqrt(x) << endl;
      counter++;
    }
  cout << "\nDone printing " << counter << " square roots." << endl;
}

The problem lies in the fact that here we declare counter, and here and here we make use of the value stored in counter, but nowhere in the code do we ever set counter to its logical starting value of zero.

counter is uninitialized. That means that the first time we come to this statement, counter contains whatever pattern of bits just happened to be residing in its storage location when the function was called, probably something left behind in memory by some other part of the program code or maybe by some completely different program entirely that was running earlier.

3 Asking the Compiler to Check for Common Mistakes

So we won’t fix the bug yet. Let’s instead see if the compiler can find it for us.

We’re going to add three new options to our compilation:

-Wall -Wextra -O2

3.1 Catching the Problem

  1. From the Settings menu, select “Compiler…”

  2. You should be on the Global compiler settings tab. If not, select it. Within that, look for a “Compiler Flags” tab. Again, you are probably already on it. If not, select it.

  3. Put a checkmark by each of the following:

       Have g++ follow ... C++11 ISO ...
    
       Enable all common compiler warnings
    
       Enable extra compiler warnings
    

    (The first of these should already be checked from earlier in this exercise.)

  4. Click OK to accept the changes. Then from the “Project” menu select “Project Clean”, then rebuild your project.

This time you should get two warnings.

3.2 How to Fix the Problem

There are a couple of “obvious” fixes for our uninitialized variable:

void printTable(int arraySize)
{
  int counter = 0;
  double inputs[] {1.0, 2.0, 100.0, 1000.0};
  cout << "Square Roots\n\n";
  // Print the square roots
  for (double x: inputs)
    {
      cout << "The square root of " << x << " is " << sqrt(x) << endl;
      counter++;
    }
  cout << "\nDone printing " << counter << " square roots." << endl;
}

or

void printTable(int arraySize)
{
  int counter;
  double inputs[] {1.0, 2.0, 100.0, 1000.0};
  cout << "Square Roots\n\n";
  // Print the square roots
  counter = 0;
  for (double x: inputs)
    {
      cout << "The square root of " << x << " is " << sqrt(x) << endl;
      counter++;
    }
  cout << "\nDone printing " << counter << " square roots." << endl;
}

Neither of these is ideal, though the first is better.

What’s wrong with the second one?

void printTable(int arraySize)
{
  int counter;
  double inputs[] {1.0, 2.0, 100.0, 1000.0};
  cout << "Square Roots\n\n";
  // Print the square roots
  counter = 0;
  for (double x: inputs)
    {
      cout << "The square root of " << x << " is " << sqrt(x) << endl;
      counter++;
    }
  cout << "\nDone printing " << counter << " square roots." << endl;
}

It leaves us with a block of code during which counter is legally available for use but not yet initialized.

As programs get modified over time and during testing and debugging, it’s all too easy to slip more and more new code into those kinds of intervals. Eventually, it’s no longer “obvious” that counter hasn’t been initialized yet, and eventually the programmer ecides that it’s OK to insert a statement that tries to make use of the uninitialized variable.

Rule of Thumb: Always initialize your variables in the same statement where you declare them.

So the declaration

int counter = 0;

is definitely preferred.

Why, then, did I say that

void printTable(int arraySize)
{
  int counter = 0;
  double inputs[] {1.0, 2.0, 100.0, 1000.0};
  cout << "Square Roots\n\n";
  // Print the square roots
  for (double x: inputs)
    {
      cout << "The square root of " << x << " is " << sqrt(x) << endl;
      counter++;
    }
  cout << "\nDone printing " << counter << " square roots." << endl;
}

was not an ideal fix?

It runs afoul of another good rule of thumb for good C++ programmers.

Rule of Thumb: Always declare your variables as close as possible to the place where you start using them.

 

That way you don’t have to remember what the initial value was or whether you have already used that variable name for a completely different purpose (a particularly common mistake with variables named “i”, “j”, etc.). If you have added comments to your variable declaration to explain what that variable is used for, this brings those comments closer to they place where they are relevant as well.

So the ideal fix for the uninitialized variable is:

void printTable(int arraySize)
{
  double inputs[] {1.0, 2.0, 100.0, 1000.0};
  cout << "Square Roots\n\n";
  // Print the square roots
  int counter = 0;
  for (double x: inputs)
    {
      cout << "The square root of " << x << " is " << sqrt(x) << endl;
      counter++;
    }
  cout << "\nDone printing " << counter << " square roots." << endl;
}

which satisfies both of those rules of thumb.

3.3 Fix it!

  1. Edit the code to make the above change.

  2. Compile and execute the program.