VS2019和_NO_CRT_STDIO_INLINE,这个怪人怎么解释?

发布于 2025-01-16 00:06:45 字数 2523 浏览 1 评论 0 原文

请检查下面我的短代码。

pwrapper.h

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

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

pwrapper.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() 函数。那么,有没有更好的办法呢?

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

==== 一些澄清 ====

我原来的帖子中有两个 main.cpp 。让我分别命名它们以供以后参考,以防有人愿意回答这个奇怪的问题。

  • main.0.cpp 指的是没有 usedull() 的那个。
  • main.1.cpp 指的是带有usedul() 的那个。

在这个问题中,我将 VC++ 标头和库用于应用程序(不适用于内核),并且

  • 编译 main.0.cppmain.1.cpp 而不使用 _NO_CRT_STDIO_INLINE
  • 我总是用 _NO_CRT_STDIO_INLINE 编译 pwrapper.cpp。

在此问题中,让 pwrapper.obj 通过 pwrapper.lib 是否会产生相同的结果。

Please check my short code below.

pwrapper.h

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

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

pwrapper.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

For some reason, I need to compile it with _NO_CRT_STDIO_INLINE, like this:

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

But link stage fails saying unresolved external symbol vwprintf and vprintf .

enter image description here

A very weird workaround I find out is: Enable the usedull() function body -- although never be called, and, link through pwrapper.lib, using bb.bat below:

@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

Well, this really works, but why?

enter image description here

This is not a pleasant workaround, because each exe project needs to include a "useless" usedull() function. So, is there any better way?

I really can't tell why this workaround works, an explanation of it is very welcome.

==== Some Clarification ====

There were two main.cpp in my original post. Let me name them separately for later reference in case someone would bother to answer this weird question.

  • main.0.cpp refers to the one without usedull().
  • main.1.cpp refers to the one with usedull().

In this question, I use VC++ headers and libs for application(not for kernel), and

  • I compile main.0.cpp and main.1.cpp without _NO_CRT_STDIO_INLINE.
  • I always compile pwrapper.cpp with _NO_CRT_STDIO_INLINE.

Whether having pwrapper.obj go through pwrapper.lib produce the same result in this issue.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

生生不灭 2025-01-23 00:06:45

简而言之:

  • 使用 -D _NO_CRT_STDIO_INLINE 编译 pwrapper.cpp 告诉编译器您将提供您自己的 vprintfvwprintf 实现 在链接时,并
  • 编译 main.cpp 没有 -D _NO_CRT_STDIO_INLINE告诉编译器包含 vprintfvwprintf 的实现,它们在链接时使用,以满足来自 usedullmm_printfA< 的引用/code>/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,则这将成为前向声明,
  • 而如果未定义,则完整的正文将包含在包含翻译单元。

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

将这些放在一起,

  • 如果未定义 _NO_CRT_STDIO_INLINE,这些函数将成为 内联扩展
  • 否则该函数的单独实现将需要在链接时提供。

默认编译(无 /O1/O2、无 _NO_CRT_STDIO_INLINE

以上适用于您正在使用的特定编译和链接调用,就像没有优化后,编译器将简单地将函数体包含在 main.1.obj 的编译中。您可以使用 < 查看此内容代码>dumpbin;运行 dumpbin -symbols main.1.obj | find "| vprintf" 打印:

01D 00000000 SECT8  notype ()    External     | vprintf

显示 main.1.obj 提供 vprintf 作为外部可用符号。

检查pwrapper.obj,我们得到:

00A 00000000 UNDEF  notype ()    External     | vprintf

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

内联扩展的优化

但是,如果我们更改 内联扩展的优化选项,我们得到不同的结果。即使使用第一级优化(-Ob1,包含在 -O1-O2 中),如下所示:

cl -c -Ob1 main.1.cpp

使编译器合并主体将vprintf直接转化为usedull,并去掉vprintf的单独实现,可以使用dumpbin来确认。因此,正如您现在所期望的那样,尝试将 main.1.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

符号,在正常情况下,这两个符号都会由于 符号的多个定义和违反一个定义规则。然而,当执行内联扩展时,编译器和链接器会为你提供支持。根据 2

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

In short:

  • compiling pwrapper.cpp with -D _NO_CRT_STDIO_INLINE tells the compiler you are going to provide your own implementation of vprintf and vwprintf at link time, and
  • compiling main.cpp without -D _NO_CRT_STDIO_INLINE tells the compiler to include implementations of vprintf and vwprintf which are used at link time to satisfy both the references from usedull and mm_printfA/mm_printfW

so, this particular combination works to resolve all undefined symbols at link time. See below for more discussion however.

Discussion

In stdio.h, vprintf (which I'll focus on, but vwprintf is configured in the same way) is defined like so:

_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

Note that

  • if _NO_CRT_STDIO_INLINE is defined, this becomes a forward declaration
  • whereas if it is not defined, the full body is included in the compilation of the including translation unit.

Additionally, in corecrt_stdio_config.h whether _NO_CRT_STDIO_INLINE is defined determines the value of _CRT_STDIO_INLINE; if it is defined, _CRT_STDIO_INLINE is defined as empty, otherwise it is defined as __inline.

Putting these together,

  • if _NO_CRT_STDIO_INLINE is not defined, these functions will be candidates for inline expansion,
  • otherwise a separate implementation of that function will need to be provided at link time.

Default Compilation (no /O1, /O2, no _NO_CRT_STDIO_INLINE)

The above works with the specific compile and link invocations you are using, as without optimization the compiler will simply include the function body in the compilation of main.1.obj. You can see this using dumpbin; running dumpbin -symbols main.1.obj | find "| vprintf" prints:

01D 00000000 SECT8  notype ()    External     | vprintf

showing that main.1.obj provides vprintf as an externally available symbol.

Checking pwrapper.obj, we get:

00A 00000000 UNDEF  notype ()    External     | vprintf

showing that vprintf is undefined in this object file, and will need to be provided at link time.

Optimisation for Inline Expansion

However, if we change the optimisation option for inline expansion, we get different results. Using even the first level of optimisation (-Ob1, included in -O1 and -O2) like so:

cl -c -Ob1 main.1.cpp

causes the compiler to incorporate the body of vprintf directly into usedull, and remove the separate implementation of vprintf, which can be confirmed using dumpbin. So, as you would now expect, attempting to link main.1.obj and pwrapper.obj together will once again give your original error:

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

Multiple Implementations?

So, following on from that it is apparent that compiling both files with -D _NO_CRT_STDIO_INLINE will fail as there will be no implementations of the relevant methods. What about if both are compiled without this definition?

If we check the object files, both have defined symbols for vprintf:

01D 00000000 SECT8  notype ()    External     | vprintf

and:

01A 00000000 SECT7  notype ()    External     | vprintf

which under normal circumstances would result in errors due both to multiple definitions of a symbol and violations of the One Definition Rule. However, when performing inline expansion, the compiler and linker have your back. As per 2:

Rather than expand an inline function defined in a header file, the compiler may create it as a callable function in more than one translation unit. The compiler marks the generated function for the linker to prevent one-definition-rule (ODR) violations.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文