说来也奇怪,为什么MSVC里面会出现MT和MD这两种模式?之前对此几乎是一无所知,但是最近遇到的关于这个选项的问题越来越多,所以特意花点时间研究一下这两种模式在使用时候的一些特征。作为对比,分别用一个静态库和一个调用这个静态库的exe文件来作为测试条件,分别用/MT和/MD两种选项编译,所以最后有四个测试程序:mt_mt,mt_md和md_md,md_mt:
首先是我们的静态库,为了简单和便利,加了一个头文件,实现里只保留一个函数,总之越小越好:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#ifndef LIB_H #define LIB_H #define FUNCNAME(x) whh_##x #ifdef __cplusplus extern "C" { #endif int __declspec(dllexport)FUNCNAME(login)(int h,unsigned char v[],int* hd); #ifdef __cplusplus } #endif #endif |
然后是lib.c
1 2 3 4 5 6 7 8 9 10 |
#include "lib.h" #include <stdlib.h> int FUNCNAME(login)(int h,unsigned char ch[],int *hd) { *hd=(int)rand()%10000; return 0; } |
这么写还有一个目的,测试一下宏定义中字符的联接功能。最后是主程序,在里面调用静态库里的接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <stdio.h> #include "lib.h" int main() { int status; unsigned char v[]="this is a test"; int hd; status = FUNCNAME(login)(1,v,&hd); printf("status: %d\n",status); } |
上面的这些都不是重点,此处的主要目的是看一下MT和MD程序在链接在一起的时候所表现出来的特征,首先将静态库分别用/MT和/MD两个选项各build出来一个版本,对应lib_mt.lib和lib_md.lib,以如下方式编译链接:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@echo cl /c lib.c /MT /Folib_mt.obj @echo ======================================================================== @cl /c lib.c /MT /Folib_mt.obj @echo lib /out:lib_mt.lib lib_mt.obj @echo ======================================================================== @lib /out:lib_mt.lib lib_mt.obj @echo. @echo cl /c lib.c /MD /Folib_md.obj @echo ======================================================================== @cl /c lib.c /MD /Folib_md.obj @echo. @echo lib /out:lib_md.lib lib_md.obj @echo ======================================================================== @lib /out:lib_md.lib lib_md.obj @echo. |
接下来用/MT和/MD选项分别编译出来对应版本的libcall的obj文件-libcall_mt.obj和libcall_md.obj文件,然后用这两个文件分别去链接lib_mt.lib和lib_md.lib,看看链接器会不会提示错误:
上面的错误很明显,用MT的obj链接MD的静态库,无法正常链接通过,这里没有用/nologo是为了对比不用版本下vs提供的工具具有的不同特征
用MD的obj去链接MT的静态库,在vs2015版本下则没有出现任何错误,虽然两个文件/MT和/MD选项不一致的时候,都会提示一个4098的警告,但是链接器还是妥协并生成了exe文件。下面就以MT的obj链接MD的.lib库继续测试,在这之前,先看一下/MT和/MD编译方法默认的defaultlib分别是什么,通过notpad++打开mt和md的obj或者.lib文件,然后搜索defaultlib就可以看到系统默认的库文件,/MT对应的是libcmt.lib,/MD对应的是msvcrt.lib,所以接下来使用/nodefaultlib:msvcrt.lib忽略掉这个默认库然后再链接生成libcall_mt_with_md.exe:
错误少了一个,但是还有一个无法解析的符号错误,难道是因为这里以.lib为取决因素?那就忽略掉libcmt.lib再看一下效果
成功了,没有输出任何的错误,看来vs2015中/MD优先于/MT,假设要把各种版本的obj或者lib文件链接到一块,VS2015都会强制建议你最终生成"/MD"的模式,而且它不会主动忽略conflict的库,通过CFF Explorer查看四个exe程序中的import directory,就可以看到情况确实是这样,只有mt_with_mt的程序中,不包含vc runtime库,其他成功编译链接出来的程序,都会包含runtime库。最后在vs2013和vs2010上,各做了一个测试,发现不使用/nodefaultlib:libcmt.lib都可以正常通过,如下
说明vs新版本中对/MT和/MD选项的处理机制确实是发生了变化的。在vc6时期,还有一个/ML选项,目测在vs2010以上的版本中都已取消,毕竟单线程已经没有什么实用性了,下面是MSDN官方给出的冲突列表:
To use this run-time library | Ignore these libraries |
Single-threaded (libc.lib) | libcmt.lib, msvcrt.lib, libcd.lib, libcmtd.lib, msvcrtd.lib |
Multithreaded (libcmt.lib) | libc.lib, msvcrt.lib, libcd.lib, libcmtd.lib, msvcrtd.lib |
Multithreaded using DLL (msvcrt.lib) | libc.lib, libcmt.lib, libcd.lib, libcmtd.lib, msvcrtd.lib |
Debug Single-threaded (libcd.lib) | libc.lib, libcmt.lib, msvcrt.lib, libcmtd.lib, msvcrtd.lib |
Debug Multithreaded (libcmtd.lib) | libc.lib, libcmt.lib, msvcrt.lib, libcd.lib, msvcrtd.lib |
Debug Multithreaded using DLL (msvcrtd.lib) | libc.lib, libcmt.lib, msvcrt.lib, libcd.lib, libcmtd.lib |
For example, if you received this warning and you want to create an executable file that uses the non-debug, single-threaded version of the run-time libraries, you could use the following options with the linker:
1 2 3 |
/NODEFAULTLIB:libcmt.lib /NODEFAULTLIB:msvcrt.lib /NODEFAULTLIB:libcd.lib /NODEFAULTLIB:libcmtd.lib /NODEFAULTLIB:msvcrtd.lib |