【问题标题】:VS2019 and _NO_CRT_STDIO_INLINE, how to explain this weirdo?VS2019 和 _NO_CRT_STDIO_INLINE,这个怪人怎么解释?
【发布时间】:2022-07-04 14:20:25
【问题描述】:

请在下面查看我的短代码。

包装器.h

#include <stdio.h>
#include <stdarg.h>

extern"C" int mm_printfA(const char *fmt, ...);
extern"C" int mm_printfW(const wchar_t *fmt, ...);

包装器.cpp

#include "pwrapper.h"

int mm_printfA(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);

    int ret = vprintf(fmt, args);

    va_end(args);
    return ret;
}

int mm_printfW(const wchar_t *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    
    int ret = vwprintf(fmt, args);
    
    va_end(args);
    return ret;
}

main.cpp

#include "pwrapper.h"

// cl /MT /D _NO_CRT_STDIO_INLINE main.cpp pwrapper.cpp

void main()
{
    mm_printfA("What is %d?\n", 123);
}

#if 0
void usedull()
{
    vprintf(NULL, NULL);
    vwprintf(NULL, NULL);
}
#endif

由于某种原因,我需要用_NO_CRT_STDIO_INLINE编译它,像这样:

cl /MT /D _NO_CRT_STDIO_INLINE main.cpp pwrapper.cpp

但链接阶段无法说出未解析的外部符号 vwprintfvprintf

我发现一个非常奇怪的解决方法是:启用usedull() 函数体——尽管从未被调用,并且通过pwrapper.lib 链接,使用下面的 bb.bat:

@setlocal EnableDelayedExpansion
@set CFLAGS=/D _NO_CRT_STDIO_INLINE

cl /nologo /c /MT %CFLAGS% pwrapper.cpp
@if errorlevel 1 exit /b 4

lib /nologo  /out:pwrapper.lib pwrapper.obj
@if errorlevel 1 exit /b 4

cl /nologo /c /MT  main.cpp
@if errorlevel 1 exit /b 4

link /nologo main.obj pwrapper.lib
@if errorlevel 1 exit /b 4

嗯,这确实有效,但为什么呢?

这不是一个令人愉快的解决方法,因为每个 exe 项目都需要包含一个“无用的”usedull() 函数。那么,有没有更好的办法呢?

我真的不知道为什么这种解决方法有效,非常欢迎对其进行解释。

【问题讨论】:

  • 我会首先尝试确定为什么需要定义_NO_CRT_STDIO_INLINE。您在此处提供的示例不需要它,因为 cl /MT main.cpp pwrapper.cpp 构建没有问题。
  • 嗯,它发生在我编写 KMDF 驱动程序时。我不小心将我自编译的用户模式 ​​mm_snprintf.lib 链接到最终的内核模式 .sys 并且发生了奇怪的行为,并且生成的 .sys 工作正常。最后,我用内核模式头重新编译了mm_snprintf并将其链接到.sys,问题最终消失了。是的,_NO_CRT_STDIO_INLINE 不需要调整。我把问题留在这里记录神秘的usedull()行为。

标签: visual-studio-2019 cl


【解决方案1】:

简而言之:

  • -D _NO_CRT_STDIO_INLINE 编译pwrapper.cpp 告诉编译器您将在链接时提供自己的vprintfvwprintf 实现,并且
  • 编译main.cpp 没有 -D _NO_CRT_STDIO_INLINE 告诉编译器包含vprintfvwprintf 的实现,它们在链接时使用以满足来自usedull() 和@987654337 的引用@/mm_printfW

所以,这种特殊的组合可以在链接时解析所有未定义的符号。但是,请参阅下面的更多讨论。

讨论

stdio.h 中,vprintf(我将重点介绍,但vwprintf 的配置方式相同)定义如下:

_Check_return_opt_
_CRT_STDIO_INLINE int __CRTDECL vprintf(
    _In_z_ _Printf_format_string_ char const* const _Format,
                                  va_list           _ArgList
    )
#if defined _NO_CRT_STDIO_INLINE
;
#else
{
    return _vfprintf_l(stdout, _Format, NULL, _ArgList);
}
#endif

请注意,如果定义了_NO_CRT_STDIO_INLINE,这将成为前向声明,而如果未定义,则整个正文将包含在包含translation unit的编译中。

另外,corecrt_stdio_config.h中是否定义了_NO_CRT_STDIO_INLINE决定了_CRT_STDIO_INLINE的值;如果定义了,则_CRT_STDIO_INLINE定义为空,否则定义为__inline

将这些放在一起,如果未定义 _NO_CRT_STDIO_INLINE,这些函数将成为 inline expansion 的候选函数,否则需要在链接时提供该函数的单独实现。

默认编译

以上内容适用于您正在使用的特定编译和链接调用,因为如果不进行优化,编译器只会将该函数包含在 main.obj 的编译中。您可以使用dumpbin 看到这一点;运行dumpbin -symbols main.obj | find "| vprintf" 打印:

01D 00000000 SECT8  notype ()    External     | vprintf

表明main.obj 提供vprintf 作为外部可用符号。

检查pwrapper.obj,我们得到:

00A 00000000 UNDEF  notype ()    External     | vprintf

表明vprintf 在此目标文件中未定义,需要在链接时提供。

内联扩展优化

但是,如果我们更改optimisation option for inline expansion,我们会得到不同的结果。甚至使用第一级优化(-Ob1,包含在-O1-O2),如下所示:

cl -c -Ob1 main.cpp

使编译器将vprintf的主体直接合并到usedull中,并去掉vprintf的单独实现,可以使用dumpbin确认。因此,正如您现在所期望的那样,尝试将 main.objpwrapper.obj 链接在一起将再次给出您原来的错误:

pwrapper.obj : error LNK2019: unresolved external symbol vwprintf referenced in function mm_printfW
pwrapper.obj : error LNK2019: unresolved external symbol vprintf referenced in function mm_printfA
main.exe : fatal error LNK1120: 2 unresolved externals

多个实现?

因此,很明显,使用-D _NO_CRT_STDIO_INLINE 编译这两个文件将失败,因为将没有相关方法的实现。如果两者都编译没有这个定义呢?

如果我们检查目标文件,两者都定义了vprintf 的符号:

01D 00000000 SECT8  notype ()    External     | vprintf

和:

01A 00000000 SECT7  notype ()    External     | vprintf

在正常情况下会由于multiple definitions of a symbol 和违反One Definition Rule 而导致错误。但是,在执行内联扩展时,编译器和链接器会为您提供支持。根据2

编译器可以将其创建为多个翻译单元中的可调用函数,而不是扩展头文件中定义的内联函数。编译器为链接器标记生成的函数,以防止违反单一定义规则 (ODR)。

【讨论】:

    猜你喜欢
    • 2018-02-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-06-15
    • 1970-01-01
    • 2019-01-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多