是否可以在保持RTTI启用的同时从可执行文件中删除类型名称?

时间:2012-07-09 21:05:05

标签: c++ visual-c++ compiler-optimization rtti

我最近在我的编译器(MSVC10)上禁用了RTTI,可执行文件的大小明显减少了。通过使用文本编辑器比较生成的可执行文件,我发现RTTI-less版本包含的符号名称少得多,解释了节省的空间。

AFAIK,这些符号名称仅用于填充与每种多态类型相关联的type_info结构,并且可以通过编程方式访问它们,调用type_info::name()

根据标准,type_info::name()返回的字符串格式未指定。也就是说,没有人可以依靠它来轻松地做一些严肃的事情。因此,实现总是可以在不破坏任何内容的情况下返回空字符串,从而减少可执行文件大小而不禁用RTTI支持(因此我们仍然可以使用typeid运算符& compare type_info' s对象安全)。

但......有可能吗?我正在使用MSVC10,我没有找到任何选项。我可以完全禁用RTTI(/GR-),也可以使用完整类型名称(/GR)启用它。有没有编译器提供这样的选项?

3 个答案:

答案 0 :(得分:3)

  

因此,实现总是可以在不破坏任何内容的情况下返回空字符串,从而减少可执行文件大小而不禁用RTTI支持(因此我们仍然可以使用typeid运算符并安全地比较type_info的对象)。 p>

你误读标准。从type_info::name()未指定返回值的意图(除了以空终止的二进制字符串之外)是为了给编译器/库/运行时环境的实现者免费执行以实现他们认为最好的RTTI要求。作为程序员,你没有说明如何设计或实现应用程序二进制接口(如果有的话)。

答案 1 :(得分:3)

你在这里问了三个不同的问题。

  1. 最初的问题是询问是否有任何方法可以让MSVC不生成名称,或者是否可以与其他编译器一起使用,或者,如果没有,那么是否有任何名称在不破坏事物的情况下从生成的type_info中删除名称的方法。

  2. 然后你想知道是否可以修改MS ABI(可能不是太激进),以便可以删除名称。

  3. 最后,您想知道是否可以设计一个没有名字的ABI。

  4. 问题#1本身就是一个复杂的问题。据我所知,没有办法让MSVC不生成名称。大多数其他编译器都针对专门定义typeid(foo).name()必须返回的ABI,因此它们也不能生成名称。

    更有趣的问题是,如果你删除名字会发生什么。对于MSVC,我不知道答案。在这里做的最好的事情可能是尝试它 - 进入你的DLL并将每个名字的第一个字符更改为\ 0并查看它是否破坏dynamic_cast等(我知道你可以用Mac和linux x86_64可执行文件来做到这一点)由g ++ 4.2生成并且它可以工作,但是现在让我们把它放在一边。)

    关于问题#2,假设消隐名称不起作用,将基于名称的系统修改为不再需要名称并不困难。一个简单的解决方案是使用名称的哈希,甚至是ROT13编码的名称(请记住,这里的原始目标是#34;我不希望临时用户看到我班级的尴尬名称") 。但我不确定那会是你想要的东西。稍微复杂的解决方案如下:

    • 对于" dllexport" ed类,生成一个UUID,将其放在typeinfo中,并将其放在随DLL一起生成的.LIB导入库中。
    • 对于" dllimport" ed类,从.LIB中读取UUID并使用它。

    因此,如果您设法将dllexport / dllimport设置为正确,它将起作用,因为您的exe将使用与dll相同的UUID。但是,如果你不这样做呢?如果你"意外"在DLL和EXE中指定相同的类(例如,具有相同参数的相同模板的实例化),而不将其标记为dllexport,将一个标记为dllimport? RTTI不会将它们看作同一类型。

    这是一个问题吗?好吧,C ++标准并没有说它。任何MS文档都没有。事实上,文档明确表示您不允许这样做。除非从一个模块明确地导出它并将其导入另一个模块,否则不能在两个不同的模块中使用相同的类或函数。事实上这很难与课程模板有关,这是一个问题,而且这是他们不会尝试解决的问题。

    让我们举一个现实的例子:创建一个带有全局静态标记的基于节点的linkedlist类模板,其中每个列表的最后一个节点都指向该标记,而end() function只返回指向它的指针。 (微软自己的std :: map实现过去就是这样做的;我不确定这是否仍然正确。)在你的exe中新增一个linkedlist<int>,然后通过它通过引用您的dll中的一个函数来尝试从l.begin()迭代到l.end()。它永远不会完成,因为exe创建的节点都不会指向dll中的sentinel副本。当然,如果您将l.begin()l.end()传递给DLL,而不是传递l本身,则您不会遇到此问题。您通常可以通过引用传递std::string或其他各种类型,只因为它们不依赖于任何破坏的东西。但你实际上并没有这样做,你只是幸运。因此,虽然用链接时需要查找的UUID替换名称意味着类型不能在链接加载器时间匹配,但类型已经无法匹配的事实链接加载时间意味着这是无关紧要的。

    有可能建立一个没有这些问题的基于名称的系统。 ARM C++ ABI(以及基于它的iOS和Android ABI)限制程序员可以比MS少得多,并且对链接加载器如何使其工作有非常具体的要求(3.2.5) 。这个不可能被修改为不是基于名称的,因为它是设计中明确的选择:

      

    •type_info :: operator ==和type_info :: operator!=比较type_info :: name()返回的字符串,而不仅仅是指向RTTI对象及其名称的指针。

         

    •不依赖于type_info :: name()返回的地址。 (即,t1.name()!= t2.name()并不意味着t1!= t2)。

         

    第一个条件实际上要求必须在线外调用这些运算符(和type_info :: before()),并且执行环境必须提供它们的适当实现。

    但也可以建立一个没有这个问题并且不使用名称的ABI。这很好地归结为#3。

    Itanium ABI(其中包括OS X和x86_64和i386上的最新linux)确保在一个对象中生成linkedlist<int>并从{0}生成linkedlist<int>另一个对象中的相同头可以在运行时链接在一起,并且将是相同的类型,这意味着它们必须具有相同的type_info对象。从2.9.1开始:

      

    当且仅当指针相等时,两个type_info指针指向等效类型描述。实现必须满足此约束,例如,通过使用符号抢占,COMDAT部分或其他机制。

    编译器,链接器和链接加载器必须协同工作,以确保在可执行文件中创建的linkedlist<int>指向与共享对象中创建的linkedlist<int>完全相同的type_info对象。

    所以,如果你刚拿出所有的名字,它根本就没有任何区别。 (这很容易测试和验证。)

    但是你怎么可能实现这个ABI规范? j_kubik有效地辩称,这是不可能的,因为你必须在.so文件中保留一些链接时间信息。这指出了明显的答案:保留.so文件中的一些链接时信息。实际上,您必须这样做才能处理,例如,加载时重定位;这只是扩展了你需要保留的东西。事实上,Apple和GNU / linux / g ++ / ELF都是这样做的。 (这是几年前构建复杂Linux系统的每个人都必须了解符号可见性和模糊链接的部分原因。)

    有一个更明显的方法来解决这个问题:编写一个基于C ++的链接加载器,而不是试图使C ++编译器和链接器协同工作来欺骗基于C的链接加载器。但据我所知,自从Be。以来,没有人试过。

答案 2 :(得分:0)

类型描述符的要求:

  • 在多编译单元和共享库环境中正常工作;
  • 适用于不同版本的共享库;
  • 正确工作虽然不同的编译单元不共享任何有关类型的信息,但它的名称除外:通常一个标题用于所有编译单元以定义相同的类型,但它不是必需的;即使它不会影响生成的目标文件。
  • 尽管事实上模板实例化必须在每个使用它们的库中完全定义(因此包括type_info数据),但如果一起使用多个这样的库,它们的行为就像一种类型。

第四条规则基本上禁止所有基于非名称的类型描述符,例如UUID(除非在类型定义中特别提到,但这只是名称替换,并且可能需要标准更改)。

在诸如suggeste .LIB文件之类的单独文件中使用UUID会导致麻烦:实现新类型的不同库版本会导致麻烦。

编译单元应该能够共享相同的类型(及其type_info)而无需涉及链接器 - 因为它应该不受任何语言特定的影响。

因此,type-name只能是唯一类型描述符,而无需完全重新编译和链接(也是动态的)。我可以想象它有效,但不是现有的计划。