在使用Visual Studio进行C++开发时,有一个编译选项,运行库。包含了/MD, /MDd, /MT, /MTd四个选项。

他们分别的含义可以在MSDN查询到:

选项 描述
/MD 使用运行时库的多线程DLL版本。编译器会将MSVCRT.lib放入.obj文件中。实际运行时的工作代码在MSVCxxx.DLL中。
/MDd /MD的debug版本
/MT 使用运行时库的多线程,静态编译的版本。编译器会将LIBCMT.lib放入.obj文件中,以便链接器使用 LIBCMT.lib 解析外部符号。
/MTd /MT的debug版本

简单来说,/MD是使用运行时库的动态库版本,运行时需要环境有MSVCxxx.dll。而/MT是使用静态编译的运行时库,运行时不需要环境有MSVCxxx.dll。

/MD

  • 输出的.exe或.dll文件更小

  • 运行时需要环境有MVSCRxxx.dll。一般我们可以通过在客户环境安装vc_redist,或在.exe的运行目录拷贝MSVCxxx.dll这两种方式。

    如果是通过安装vc_redist的方式,这些相关的DLL将会被安装在系统目录下(%SYSTEMROOT%/System32 or SysWOW64)。这有可能会带来问题,如果其他程序(或系统更新)修改了运行时库,可能会造成现有程序出现异常。

    StackOverflow有人说安装的目录已经采用WinSxS的方式,不会有覆盖的问题,这是错误的。

    在Visual Studio 2010之后,就取消了这种方式。官方说明在这里

  • 依赖的其他库也必须是/MD编译

  • 运行时,exe和它依赖的第三方库,以及其他以这种方式玩的进程,都是同一套运行时库。在一定程度上减少了内存的消耗。

/MT

  • 输出的.exe或.dll文件更大

  • 运行时不需要环境有MVSCxxx.dll

  • 依赖的其他库也必须是/MT编译

  • 如果依赖的第三方库是动态库(.dll),则exe在运行时会包含多份运行时库代码,一个是exe自己的,一个是依赖的三方库的。

  • 不正确的使用可能会造成程序Crash
    例如在依赖的Dll中malloc,在exe中free。

    foo.h

    1
    2
    3
    4
    5
    6
    7
    #pragma once
    #if defined(FOO_LIBRARY)
    # define FOO_EXPORT __declspec(dllexport)
    #else
    # define FOO_EXPORT __declspec(dllimport)
    #endif
    FOO_EXPORT void* foo();

    foo.cpp

    1
    2
    3
    4
    5
    6
    #include "foo.h"
    #include <cstdlib>
    void* foo()
    {
    return malloc(10);
    }

    在主工程中调用foo拿到指针,进行free。

    main.cpp

    1
    2
    3
    4
    5
    #include "foo.h"
    int main()
    {
    free(foo());
    }

    运行程序会出现下面的结果

    为什么会出现__acrt_first_block == header的断言错误呢?

    这是因为/MTd模式下,DLL的运行时库与EXE的运行时库不一样。详细的解析可参考这位大佬的blog

最佳实践

  1. 使用/MD

  2. 使用本地部署,即将依赖的运行时库的相关DLL拷贝到发布的应用程序的路径下。

    这样做的另一个好处是普通用户也能正常安装应该程序,而不需要管理员权限去写入系统目录。

    它的目录结构看起来像这样:

    1
    2
    3
    4
    |--My App\
    | |--MyApp.exe
    | |--msvc*.dll
    | |--...

    如果为了好看,可以把运行时库单独放到一个子目录内,然后写一个壳子程序或批处理,将子目录添加到环境变量中再启动。

    它的目录结构看起来像这样:

    1
    2
    3
    4
    |--My App\
    | |--MyApp.exe
    | |--Microsoft.VC142.CRT\
    | | |--msvc*.dll

    然后批处理启动

    1
    2
    3
    @echo off
    set PATH=%~dp0Microsoft.VC142.CRT;%PATH%
    MyApp.exe

    或用壳子程序启动,壳子程序大概长下面这样(伪代码)

    1
    2
    set_env_to_path(vc_dir);
    CreateProcess(...);

Reference