I do most of my development work on macOS. Occasionally, I need to test a program or script on Windows.

If I’m building a C++ tool on Windows, I rely on cmake and ninja to abstract the details of setting all the various compiler flags to create a debug/release build.

With these two tools, creating a release build is simple on macOS, Linux, and Windows.

mkdir build
cd build
cmake -GNinja -DCMAKE_BUILD_TYPE=Release ..
ninja

You can make it even easier by letting cmake create the build directory.

cmake -B build -DCMAKE_BUILD_TYPE=Release -GNinja
cmake --build build --config Release

What if you find yourself on a Windows system that has an older version of Visual Studio or Visual Studio Build Tools, but the system does not have cmake or ninja. You could download and install these tools, but what if the Windows system is isolated from the internet or you do not have administrative rights? In the rest of this post, we’ll walk though using cl.exe and nmake.exe to build some simple C++ programs.

We’ll start with the canonical Hello world example.

#include <iostream>
 
int main () {
  std::cout << "Hello world!" << std::endl;
}

If you used cmake & ninja, the compile phase will looks like this:

cl.exe  /nologo /TP /DWIN32 /D_WINDOWS /W3 /GR /EHsc /MD /O2 /Ob2 /DNDEBUG -std:c++17 /showIncludes /FoCMakeFiles\hello.dir\hello.cc.obj /FdCMakeFiles\hello.dir\ /FS -c C:\Users\hulk\src\hello\hello.cc

There are many options passed to cl.exe. Let’s explain each option.

/nologo - Suppresses display of sign-on banner.

/TP - Specifies all source files are C++.

/DWIN32 - Define WIN32.

/D_WINDOWS - Define _WINDOWS.

/W3 - Set output warning level to 3.

/GR - Enables run-time type information (RTTI).

/EHs - Enable C++ exception handling (no SEH exceptions).
/EHc - extern "C" defaults to nothrow.
/EHsc - Set both the above options.

/MD - Compiles to create a multithreaded DLL, by using MSVCRT.lib.

/O2 - Creates fast code.

/Ob2 - Controls inline expansion.

/DNDEBUG - Define NDEBUG.

-std:c++17 - Specify C++ 17 standard.

/FS - Forces writes to the PDB file to be serialized through MSPDBSRV.EXE.

I started adding options one at a time and came up with this command line which built an executable with a size around 10 KB.

cl.exe /DNDEBUG /EHsc /nologo /O2 /MD /TP /W3 hello.cc

I did not end up using the /GR, /Ob2, /FS options initially; I need to explore them more. The older version of Visual Studio Tools I was using did not support -std:c++17.

Instead of typing the long build line for cl.exe, I made the following NMake Makefile.

hello: hello.cc
 cl.exe /DNDEBUG /EHsc /nologo /O2 /MD /TP /W3 hello.cc

You can then build the hello program with a single nmake command.