我最近在我的编译器(MSVC10)上禁用了RTTI,可执行文件的大小明显减少了。通过使用文本编辑器比较生成的可执行文件,我发现RTTI-less版本包含的符号名称少得多,解释了节省的空间。
AFAIK,这些符号名称仅用于填充与每种多态类型相关联的type_info
结构,并且可以通过编程方式访问它们,调用type_info::name()
。
根据标准,type_info::name()
返回的字符串格式未指定。也就是说,没有人可以依靠它来轻松地做一些严肃的事情。因此,实现总是可以在不破坏任何内容的情况下返回空字符串,从而减少可执行文件大小而不禁用RTTI支持(因此我们仍然可以使用typeid
运算符& compare type_info
' s对象安全)。
但......有可能吗?我正在使用MSVC10,我没有找到任何选项。我可以完全禁用RTTI(/GR-
),也可以使用完整类型名称(/GR
)启用它。有没有编译器提供这样的选项?
答案 0 :(得分:3)
因此,实现总是可以在不破坏任何内容的情况下返回空字符串,从而减少可执行文件大小而不禁用RTTI支持(因此我们仍然可以使用typeid运算符并安全地比较type_info的对象)。 p>
你误读标准。从type_info::name()
未指定返回值的意图(除了以空终止的二进制字符串之外)是为了给编译器/库/运行时环境的实现者免费执行以实现他们认为最好的RTTI要求。作为程序员,你没有说明如何设计或实现应用程序二进制接口(如果有的话)。
答案 1 :(得分:3)
你在这里问了三个不同的问题。
最初的问题是询问是否有任何方法可以让MSVC不生成名称,或者是否可以与其他编译器一起使用,或者,如果没有,那么是否有任何名称在不破坏事物的情况下从生成的type_info中删除名称的方法。
然后你想知道是否可以修改MS ABI(可能不是太激进),以便可以删除名称。
最后,您想知道是否可以设计一个没有名字的ABI。
问题#1本身就是一个复杂的问题。据我所知,没有办法让MSVC不生成名称。大多数其他编译器都针对专门定义typeid(foo).name()必须返回的ABI,因此它们也不能生成名称。
更有趣的问题是,如果你删除名字会发生什么。对于MSVC,我不知道答案。在这里做的最好的事情可能是尝试它 - 进入你的DLL并将每个名字的第一个字符更改为\ 0并查看它是否破坏dynamic_cast等(我知道你可以用Mac和linux x86_64可执行文件来做到这一点)由g ++ 4.2生成并且它可以工作,但是现在让我们把它放在一边。)
关于问题#2,假设消隐名称不起作用,将基于名称的系统修改为不再需要名称并不困难。一个简单的解决方案是使用名称的哈希,甚至是ROT13编码的名称(请记住,这里的原始目标是#34;我不希望临时用户看到我班级的尴尬名称") 。但我不确定那会是你想要的东西。稍微复杂的解决方案如下:
因此,如果您设法将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)
类型描述符的要求:
第四条规则基本上禁止所有基于非名称的类型描述符,例如UUID(除非在类型定义中特别提到,但这只是名称替换,并且可能需要标准更改)。
在诸如suggeste .LIB文件之类的单独文件中使用UUID会导致麻烦:实现新类型的不同库版本会导致麻烦。
编译单元应该能够共享相同的类型(及其type_info
)而无需涉及链接器 - 因为它应该不受任何语言特定的影响。
因此,type-name只能是唯一类型描述符,而无需完全重新编译和链接(也是动态的)。我可以想象它有效,但不是现有的计划。