如何使用没有运行时库的VC ++内部函数

时间:2010-05-30 14:16:26

标签: c++ visual-c++ intrinsics memset demoscene

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

某些RTL函数(如memset())可能很有用,所以我尝试添加自己的实现。它在Debug构建中工作正常(即使对于编译器生成对<{1}}的隐式调用的那些地方)。但是在Release版本中,我得到一个错误,说我无法定义内部函数。您可以看到,在发布版本中,内部函数已启用,memset()是内在函数。

我希望在我的发布版本中使用memset()的内在函数,因为它可能内联,比我的实现更小,更快。但我似乎是一个陷阱22。如果我没有定义memset(),链接器会抱怨它是未定义的。如果我确定它,编译器会抱怨我无法定义内部函数。

有没有人知道定义,声明,memset()以及编译器和链接器标志的正确组合,以获得内部函数而不会引入RTL开销?

Visual Studio 2008,x86,Windows XP +。

使问题更具体:

#pragma

我这样建造:

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()

7 个答案:

答案 0 :(得分:20)

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

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

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)。我不知道为什么。如果有人在没有禁用全局优化的情况下找到了解决方法,请插入。

答案 1 :(得分:5)

  1. 我很确定有一个编译器标志告诉VC ++不要使用内在函数

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

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

  4. 如果你真的很认真,你需要知道(也许可以使用)汇编语言。

  5. 已编辑添加:

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

    Enable Intrinsic Functions: No
    Whole Program Optimization: No
    

    最后一个抑制“编译器助手”就像内置的memset一样。

    已编辑添加:

    现在它已经解耦,你可以将memm.asm中的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;
    }
    

    它有效,但库的版本要快得多。

答案 2 :(得分:1)

我认为你必须将Optimization设置为“Minimize Size(/ O1)”或“Disabled(/ Od)”才能获得Release配置进行编译;至少这就是VS 2005的诀窍。本体是为速度设计的,所以有意义的是它们可以用于其他优化级别(速度和完全)。

答案 3 :(得分:1)

当您第一次问这个问题时,这当然不是一个答案,但是现在可以使用 Visual Studio 2019 提供的 Clang 版本来做您想做的事情,它可以在没有的情况下按照您的意愿工作要跳过的任何特定箍。

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

根据 Visual C++ 本身,我采取了将 memset/memcpy 的实现放在一个单独的源文件中的方法,正如 rc-1290 提到的那样,只从全局优化中排除了那个文件,所以成本不是那么高高 - 虽然很刺激!

答案 4 :(得分:0)

只需将功能命名略有不同。

答案 5 :(得分:0)

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

答案 6 :(得分:-1)

“常规”运行时库执行此操作的方式是通过使用memset的定义编译程序集文件并将其链接到运行时库(您可以在C:\ Program Files \ Microsoft Visual Studio中或其周围找到程序集文件) 10.0 \ VC \ CRT \ SRC \英特尔\ memset.asm)。即使对整个程序进行优化,这种方法也能正常工作。

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