Consider using known tools on your C++ project

When you develop a project, you should use tools that the industry already has to offer, so when you have a C++ project you should consider the following

Build System

Use an industry standard widely accepted build tool. Use a tool that gives you abilities like accelerated build with minimum change to your environment. This prevents you from reinventing the wheel whenever your project grows and take longer to compile.

Compilers

Use every available and reasonable set of warning options.

You should use as many compilers as you can for your platform(s). Each compiler implements the standard slightly differently and supporting multiple will help ensure the most portable, most reliable code.

GCC / Clang

-Wall -Wextra -Wshadow -Wnon-virtual-dtor -pedantic

  • -Wall -Wextra reasonable and standard
  • -Wshadow warn the user if a variable declaration shadows one from a parent context
  • -Wnon-virtual-dtor warn the user if a class with virtual functions has a non-virtual destructor. This helps catch hard to track down memory errors
  • -Wold-style-cast warn for c-style casts
  • -Wcast-align warn for potential performance problem casts
  • -Wunused warn on anything being unused
  • -Woverloaded-virtual warn if you overload (not override) a virtual function
  • -Wpedantic (all versions of GCC, Clang >= 3.2) warn if non-standard C++ is used
  • -Wconversion warn on type conversions that may lose data
  • -Wsign-conversion (Clang all versions, GCC >= 4.3) warn on sign conversions
  • -Wmisleading-indentation (only in GCC >= 6.0) warn if indentation implies blocks where blocks do not exist
  • -Wduplicated-cond (only in GCC >= 6.0) warn if if / else chain has duplicated conditions
  • -Wduplicated-branches (only in GCC >= 7.0) warn if if / else branches have duplicated code
  • -Wlogical-op (only in GCC) warn about logical operations being used where bitwise were probably wanted
  • -Wnull-dereference (only in GCC >= 6.0) warn if a null dereference is detected
  • -Wuseless-cast (only in GCC >= 4.8) warn if you perform a cast to the same type
  • -Wdouble-promotion (GCC >= 4.6, Clang >= 3.8) warn if float is implicit promoted to double
  • -Wformat=2 warn on security issues around functions that format output (ie printf )
  • -Wlifetime (only special branch of Clang currently) shows object lifetime issues

Consider using -Weverything and disabling the few warnings you need to on Clang

-Weffc++ warning mode can be too noisy, but if it works for your project, use it also.

MSVC

/permissive- - Enforces standards conformance.

/W4 /w14640 - use these and consider the following (see descriptions below)

  • /W4 All reasonable warnings
  • /w14242 ‘identfier’: conversion from ‘type1’ to ‘type1’, possible loss of data
  • /w14254 ‘operator’: conversion from ‘type1:field_bits’ to ‘type2:field_bits’, possible loss of data
  • /w14263 ‘function’: member function does not override any base class virtual member function
  • /w14265 ‘classname’: class has virtual functions, but destructor is not virtual instances of this class may not be destructed correctly
  • /w14287 ‘operator’: unsigned/negative constant mismatch
  • /we4289 nonstandard extension used: ‘variable’: loop control variable declared in the for-loop is used outside the for-loop scope
  • /w14296 ‘operator’: expression is always ‘boolean_value’
  • /w14311 ‘variable’: pointer truncation from ‘type1’ to ‘type2’
  • /w14545 expression before comma evaluates to a function which is missing an argument list
  • /w14546 function call before comma missing argument list
  • /w14547 ‘operator’: operator before comma has no effect; expected operator with side-effect
  • /w14549 ‘operator’: operator before comma has no effect; did you intend ‘operator’?
  • /w14555 expression has no effect; expected expression with side-effect
  • /w14619 pragma warning: there is no warning number ‘number’
  • /w14640 Enable warning on thread un-safe static member initialization
  • /w14826 Conversion from ‘type1’ to ‘type_2’ is sign-extended. This may cause unexpected runtime behavior.
  • /w14905 wide string literal cast to ‘LPSTR’
  • /w14906 string literal cast to ‘LPWSTR’
  • /w14928 illegal copy-initialization; more than one user-defined conversion has been implicitly applied

Not recommended

  • /Wall - Also warns on files included from the standard library, so it’s not very useful and creates too many extra warnings.

General

Start with very strict warning settings from the beginning. Trying to raise the warning level after the project is underway can be painful.

Consider using the treat warnings as errors setting. /WX with MSVC, -Werror with GCC / Clang

LLVM-based tools

Continues Integration

In software engineering, continuous integration (CI) is the practice of merging all developers’ working copies to a shared mainline several times a day. Once you have picked your build tool, set up a continuous integration environment.

Continuous Integration (CI) tools automatically build the source code as changes are pushed to the repository.

Source Control

Source control is a class of systems responsible for managing changes to computer programs. Source control is an absolute necessity for any software development project.

Package Manager

Package management is an important topic in C++. Consider using a package manager to help you keep track of the dependencies for your project and make it easier for new people to get started with the project.

Static Analyzers

The best bet is the static analyzer that you can run as part of your automated build system. Cppcheck and clang meet that requirement for free options.

Clang’s Static Analyzer

Clang’s analyzer’s default options are good for the respective platform. It can be used directly from CMake. They can also be called via clang-check and clang-tidy from the LLVM-based Tools.

Also, CodeChecker is available as a front-end to clang’s static analysis.

clang-tidy can be easily used with Visual Studio via the Clang Power Tools extension.

MSVC’s Static Analyzer

Can be enabled with the /analyze command line option. For now we will stick with the default options.

ReSharper C++ / CLion

Both of these tools from JetBrains offer some level of static analysis and automated fixes for common things that can be done better. They have options available for free licenses for open source project leaders.

Cevelop

The Eclipse based Cevelop IDE has various static analysis and refactoring / code fix tools available. For example, you can replace macros with C++ constexprs , refactor namespaces (extract/inline using , qualify name), and refactor your code to C++11’s uniform initialization syntax. Cevelop is free to use.

Runtime Checkers

Code Coverage Analysis

A coverage analysis tool shall be run when tests are executed to make sure the entire application is being tested. Unfortunately, coverage analysis requires that compiler optimizations be disabled. This can result in significantly longer test execution times.

Heap profiling

CPU profiling

Reverse engineering tools

  • Cutter - A front-end for Radare2. It provides tools such as decompiler, disassembly, graph visualizer, hex editor.

GCC / Clang Sanitizers

These tools provide many of the same features as Valgrind, but built into the compiler. They are easy to use and provide a report of what went wrong.

  • AddressSanitizer
  • MemorySanitizer
  • ThreadSanitizer
  • UndefinedBehaviorSanitizer

Be aware of the sanitizer options available, including runtime options. Krister Walfridsson’s blog: Useful GCC address sanitizer checks not enabled by default

Fuzzy Analyzers

If your project accepts user defined input, considering running a fuzzy input tester.

Both of these tools use coverage reporting to find new code execution paths and try to breed novel inputs for your code. They can find crashes, hangs, and inputs you didn’t know were considered valid.

Continuous Fuzzing

Continuous fuzzing tools exist to run fuzz tests for you with each commit.

Mutation Testers

These tools take code executed during unit test runs and mutate the executed code. If the test continues to pass with a mutation in place, then there is likely a flawed test in your suite.

Control Flow Guard

MSVC’s Control Flow Guard adds high performance runtime security checks.

Checked STL Implementations

Heap Profiling

  • Memoro - A detailed heap profiler

Ignoring Warnings

If it is determined by team consensus that the compiler or analyzer is warning on something that is either incorrect or unavoidable, the team will disable the specific error to as localized part of the code as possible.

Be sure to reenable the warning after disabling it for a section of code. You do not want your disabled warnings to leak into other code.

Testing

CMake, mentioned above, has a built in framework for executing tests. Make sure whatever build system you use has a way to execute tests built in.

To further aid in executing tests, consider a library such as Google Test, Catch, CppUTest or Boost.Test to help you organize the tests.

Unit Tests

Unit tests are for small chunks of code, individual functions which can be tested standalone.

Integration Tests

There should be a test enabled for every feature or bug fix that is committed. See also Code Coverage Analysis. These are tests that are higher level than unit tests. They should still be limited in scope to individual features.

Negative Testing

Don’t forget to make sure that your error handling is being tested and works properly as well. This will become obvious if you aim for 100% code coverage.

Debugging

GDB

GDB - The GNU debugger, powerful and widely used. Most IDEs implement an interface to use it.

rr

rr is a free (open source) reverse debugger that supports C++.

Other Tools

Lizard

Lizard provides a very simple interface for running complexity analysis against a C++ codebase.

Metrix++

Metrix++ can identify and report on the most complex sections of your code. Reducing complex code helps you and the compiler understand it better and optimize it better.

ABI Compliance Checker

ABI Compliance Checker (ACC) can analyze two library versions and generates a detailed compatibility report regarding API and C++ ABI changes. This can help a library developer spot unintentional breaking changes to ensure backward compatibility.

CNCC

Customizable Naming Convention Checker can report on identifiers in your code that do not follow certain naming conventions.

SourceMeter

SourceMeter offers a free version which provides many different metrics for your code and can also call into cppcheck.

Bloaty McBloatface

Bloaty McBloatface is a binary size analyzer/profiler for unix-like platforms

pahole

pahole generates data on holes in the packing of data structures and classes in compiled code. It can also the size of structures and how they fit within the system’s cache lines.

BinSkim

BinSkim is a binary static analysis tool that provides security and correctness results for Windows Portable Executable and *nix ELF binary formats

This article was based on Jason Turner book

6 Likes

Hi Avi,

Great write-up, many great tools here more than worth getting familiar with (some of which I never heard of before :slight_smile: )
Here are some additional ones worth considering when starting new projects:

Buildsystem:
Gradle
Scons
Msbuild
GNU Make
BitBake

Compilers:
NVCC
HCC

CI:
Azure Devops
Bamboo

Source control:
Perforce

Static Analyzers
Klockwork

Edit: link format

4 Likes

And while on the subject of tools, wanted to ask - how would you go about choosing a compiler for a new project? For the sake of discussion - at what differentiating factors would you look between gcc and clang when targeting Linux x86 platforms?

3 Likes

As mentioned above I would use as many compilers as I can for my platform(s). Each compiler implements the standard slightly differently and supporting multiple will help ensure the most portable, most reliable code.

Also I would look at compiler support for features and adopt the one that support the features that I care for.

3 Likes