36

我参与了您尝试生成尽可能小的二进制文件的挑战之一,因此我正在构建没有C 或 C++ 运行时库 (RTL) 的程序。我没有链接到 DLL 版本或静态版本。我什至没有#include头文件。我有这个工作正常。

一些 RTL 函数,比如memset(),可能很有用,所以我尝试添加自己的实现。它在 Debug 版本中运行良好(即使对于那些编译器生成隐式调用的地方memset())。但是在发布版本中,我收到一条错误消息,说我无法定义内在函数。您会看到,在 Release 版本中,内部函数已启用,并且memset()是内部函数。

我很想memset()在我的发布版本中使用内在函数,因为它可能是内联的,并且比我的实现更小、更快。但我似乎是第 22 条军规中的一员。如果我不定义memset(),则链接器会抱怨它未定义。如果我定义了它,编译器会抱怨我不能定义一个内在函数。

有谁知道定义、声明、#pragma编译器和链接器标志的正确组合,以在不增加 RTL 开销的情况下获得内在函数?

Visual Studio 2008、x86、Windows XP+。

为了使问题更具体一点:

extern "C" void * __cdecl memset(void *, int, size_t);

#ifdef IMPLEMENT_MEMSET
void * __cdecl memset(void *pTarget, int value, size_t cbTarget) {
    char *p = reinterpret_cast<char *>(pTarget);
    while (cbTarget > 0) {
        *p++ = static_cast<char>(value);
        --cbTarget;
    }
    return pTarget;
}
#endif

struct MyStruct {
    int foo[10];
    int bar;
};

int main() {
    MyStruct blah;
    memset(&blah, 0, sizeof(blah));
    return blah.bar;
}

我这样构建:

cl /c /W4 /WX /GL /Ob2 /Oi /Oy /Gs- /GF /Gy intrinsic.cpp
link /SUBSYSTEM:CONSOLE /LTCG /DEBUG /NODEFAULTLIB /ENTRY:main intrinsic.obj

如果我使用 的实现进行编译memset(),则会出现编译器错误:

error C2169: 'memset' : intrinsic function, cannot be defined

如果我在没有实现的情况下编译它memset(),我会得到一个链接器错误:

error LNK2001: unresolved external symbol _memset
4

7 回答 7

24

我想我终于找到了解决方案:

首先,在头文件中,memset()使用 pragma 声明,如下所示:

extern "C" void * __cdecl memset(void *, int, size_t);
#pragma intrinsic(memset)

这允许您的代码调用memset(). 在大多数情况下,编译器将内联内在版本。

其次,在一个单独的实现文件中,提供一个实现。防止编译器抱怨重新定义内部函数的技巧是首先使用另一个 pragma。像这样:

#pragma function(memset)
void * __cdecl memset(void *pTarget, int value, size_t cbTarget) {
    unsigned char *p = static_cast<unsigned char *>(pTarget);
    while (cbTarget-- > 0) {
        *p++ = static_cast<unsigned char>(value);
    }
    return pTarget;
}

这为优化器决定不使用内部版本的情况提供了一种实现。

突出的缺点是您必须禁用整个程序优化(/GL 和 /LTCG)。我不确定为什么。如果有人在不禁用全局优化的情况下找到了一种方法,请加入。

于 2010-05-31T19:33:10.470 回答
5
  1. 我很确定有一个编译器标志告诉 VC++ 不要使用内在函数

  2. 运行时库的源代码与编译器一起安装。您确实可以选择您想要/需要的摘录功能,但通常您必须对它们进行大量修改(因为它们包含您不想要/不需要的功能和/或依赖项)。

  3. 还有其他可用的开源运行时库,可能需要较少的定制。

  4. 如果您对此非常认真,则需要了解(并且可能使用)汇编语言。

编辑添加:

我得到了你的新测试代码来编译和链接。这些是相关设置:

Enable Intrinsic Functions: No
Whole Program Optimization: No

最后一个抑制“编译器助手”,如内置的 memset。

编辑添加:

现在它已解耦,您可以将 asm 代码从 memset.asm 复制到您的程序中——它有一个全局引用,但您可以删除它。它足够大,因此它不会被内联,但如果你删除它用来提高速度的所有技巧,你也许可以让它足够小。

我拿了你上面的例子并memset()用这个替换了:

void * __cdecl memset(void *pTarget, char value, size_t cbTarget) {
    _asm {
    push ecx
    push edi

    mov al, value
    mov ecx, cbTarget
    mov edi, pTarget
    rep stosb

    pop edi
    pop ecx
    }
    return pTarget;
}

它可以工作,但库的版本要快得多。

于 2010-05-30T17:58:43.410 回答
1

我认为您必须将优化设置为“最小化大小(/O1)”或“禁用(/Od)”才能编译发布配置;至少这就是我使用 VS 2005 的诀窍。内在函数是为速度而设计的,因此可以为其他优化级别(速度和完整)启用它们是有道理的。

于 2010-05-30T18:04:37.650 回答
1

这绝对适用于 VS 2015:添加命令行选项 /Oi-。这是有效的,因为内在函数上的“否”不是开关,它是未指定的。/Oi- 你所有的问题都消失了(它应该与整个程序优化一起工作,但我没有正确测试这个)。

于 2017-02-12T20:48:03.890 回答
1

当你第一次问这个问题时,这当然不是答案,但现在可以通过使用 Visual Studio 2019 提供的 Clang 版本来做你想做的事,它就像你想要的那样工作,没有任何特殊的箍跳过。

使用 Clang 也有其他一些好处 - 特别是如果您也希望使用 x64 架构来实现类似的目标,因为它似乎是让爆炸的 pdata 部分消失的唯一方法!

根据 Visual C++ 本身,我采用了将 memset/memcpy 的实现放在一个单独的源文件中的方法,并且正如rc-1290所提到的,仅从全局优化中排除了一个文件,因此成本并没有那么高 - 尽管很烦人!

于 2021-05-01T20:26:39.920 回答
0

只需将函数命名为稍有不同。

于 2010-05-31T17:35:51.900 回答
-1

“常规”运行时库执行此操作的方式是编译具有 memset 定义的程序集文件并将其链接到运行时库(您可以在 C:\Program Files\Microsoft Visual Studio 10.0\VC 中或周围找到程序集文件\crt\src\intel\memset.asm)。即使在整个程序优化的情况下,这种事情也能正常工作。

另请注意,编译器只会在某些特殊情况下使用 memset 内在函数(当大小恒定且较小时?)。它通常会使用你提供的 memset 函数,所以你应该使用 memset.asm 中的优化函数,除非你要写一些优化的东西。

于 2010-08-30T00:16:22.063 回答