【问题标题】:What is name mangling, and how does it work?什么是名称修饰,它是如何工作的?
【发布时间】:2010-11-21 19:59:57
【问题描述】:

请解释什么是名称修饰,它是如何工作的,它解决了什么问题,以及在哪些上下文和语言中使用。名称修改策略(例如,编译器选择什么名称以及原因)加分。

【问题讨论】:

  • 错位名称在 C++ 的“特洛伊木马”历史中发挥了作用,请参阅:ejohnson.blogs.com/software/2004/11/i_find_c_intere.html
  • @harpo :有趣,但有点“苛刻”。我的意思是,所说的是新功能的结果。我认为在任何情况下都不能被视为故意的。

标签: language-agnostic name-mangling


【解决方案1】:

在您选择的编程语言中,如果标识符是从单独编译的单元中导出的,则它需要一个名称,以便在链接时知道它。名称修改解决了编程语言中标识符重载的问题。 (如果在多个上下文中使用相同的名称或具有多个含义,则标识符被“重载”。)

一些例子:

  • 在 C++ 中,函数或方法 get 可以在多种类型上重载。

  • 在 Ada 或 Modula-3 中,函数 get 可能出现在多个模块中。

多种类型和多个模块涵盖了通常的上下文。

典型策略:

  • 将每种类型映射到一个字符串,并使用组合的高级标识符和“类型字符串”作为链接时名称。在 C++ 中很常见(特别容易,因为重载只允许用于函数/方法并且只允许用于参数类型)和 Ada(您也可以重载结果类型)。

  • 如果在多个模块或命名空间中使用了标识符,请将模块名称与标识符名称连接起来,例如,List_get 而不是List.get

根据链接时名称中哪些字符是合法的,您可能需要进行额外的修改;例如,可能需要使用下划线作为“转义”字符,以便区分

  • List_my.get -> List__my_get

来自

  • List.my_get -> List_my__get

(诚然,这个例子很有效,但作为编译器编写者,我必须保证源代码中的不同标识符映射到不同的链接时间名称。这就是名称的全部原因和目的破坏。)

【讨论】:

  • 乱七八糟的名字通常更恶心。例如,__ZmlRK8Matrix3fRK3Ray 是我的函数名为 Ray operator*( const Matrix3f& matrix, const Ray& oRay ) 的错位名称。我也喜欢有些人称其为“名称装饰”。 “是的。请给我的函数名称装饰。Blargh!”
  • 确实非常有用的答案。关于名称修改过程中“转义”下划线的示例对于源到源编译器非常有用。
【解决方案2】:

简单地说,名称修改是编译器更改源代码中标识符名称的过程,以帮助linker 消除这些标识符之间的歧义。

Wikipedia has a wonderful article on this subject 有几个很好的例子。

【讨论】:

    【解决方案3】:

    Name mangling 是编译器修改对象的“已编译”名称的一种方式,以使其与您以一致的方式指定的名称不同。

    这允许编程语言灵活地为多个编译对象提供相同的名称,并以一致的方式查找适当的对象。例如,这允许多个具有相同名称的类存在于不同的命名空间中(通常通过将命名空间添加到类名中等)。

    许多语言中的操作符和方法重载更进一步——每个方法在编译库中都有一个“损坏”的名称,以便允许一种类型的多个方法以相同的名称存在。

    【讨论】:

      【解决方案4】:

      在 python 中,name-mangling 是一个系统,通过该系统,类变量在类内部和外部具有不同的名称。程序员通过在变量名的开头放置两个下划线来“激活”它。

      例如,我可以定义一个包含一些成员的简单类:

      >>> class Foo(object):
      ...  def __init__(self):
      ...   self.x = 3
      ...   self._y = 4
      ...   self.__z = 5
      ... 
      

      在 python 实践中,以下划线开头的变量名是“内部的”,而不是类接口的一部分,因此程序员不应该依赖它。但是,它仍然可见:

      >>> f = Foo()
      >>> f.x
      3
      >>> f._y
      4
      

      以两个下划线开头的变量名仍然是公开的,但它是名称混乱的,因此更难访问:

      >>> f.__z  
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      AttributeError: 'Foo' object has no attribute '__z'
      

      但是,如果我们知道名称修饰的工作原理,我们就可以搞定:

      >>> f._Foo__z
      5
      

      即类名在变量名前面加上一个额外的下划线。

      Python 没有“私有”与“公共”成员的概念;一切都是公开的。名称修改是程序员可以发送的最强信号,即不应从类外部访问该变量。

      【讨论】:

      【解决方案5】:

      来源:http://sickprogrammersarea.blogspot.in/2014/03/technical-interview-questions-on-c_6.html

      名称修饰是 C++ 编译器使用的过程,它为程序中的每个函数赋予一个唯一的名称。在 C++ 中,通常程序至少有几个同名的函数。因此,名称修饰可以被认为是 C++ 中的一个重要方面。

      示例: 通常,成员名称是通过将成员名称与类名称连接来唯一生成的,例如给定声明:

      class Class1
       {
              public:
                  int val;
                  ...
        };
      

      val 变成这样:

        // a possible member name mangling
           val__11Class1
      

      【讨论】:

        【解决方案6】:

        在 Fortran 中,需要名称修改,因为该语言不区分大小写,这意味着 Foo、FOO、fOo、foo 等都将解析为相同的符号,其名称必须以某种方式规范化。不同的编译器以不同的方式实现 mangling,当与使用不同编译器编译的 C 或二进制对象交互时,这是一个很大的麻烦。例如,GNU g77/g95 总是在小写名称后面添加一个下划线,除非该名称已经包含一个或多个下划线。在这种情况下,添加了两个下划线。

        例如下面的例程

            program test
            end program 
        
            subroutine foo()
            end subroutine
        
            subroutine b_ar()
            end subroutine
            subroutine b_a_r()
            end subroutine
        

        产生以下错位符号:

        0000000000400806 g     F .text  0000000000000006              b_ar__
        0000000000400800 g     F .text  0000000000000006              foo_
        000000000040080c g     F .text  0000000000000006              b_a_r__
        

        为了从 C 调用 Fortran 代码,必须调用正确修改的例程名称(显然要考虑到真正独立于编译器的可能的不同修改策略)。要从 fortran 调用 C 代码,C 编写的接口必须导出正确损坏的名称并将调用转发到 C 例程。然后可以从 Fortran 调用此接口。

        【讨论】:

          【解决方案7】:

          大多数面向对象的语言都提供函数重载特性。 函数重载 如果任何类有多个具有相同名称但参数类型和编号不同的函数,则称它们为重载。函数重载允许您为不同的函数使用相同的名称。

          重载函数的方法

          1. 通过更改参数的数量。
          2. 列表项通过具有不同类型的参数。

          如何通过名称修饰实现函数重载?
          C++ 编译器在生成目标代码时会区分不同的函数——它会根据参数的类型和数量添加有关参数的信息来更改名称。这种添加附加信息以形成函数名称的技术称为名称修饰。 C++ 标准没有指定任何特定的名称修饰技术,因此不同的编译器可能会将不同的信息附加到函数名称。 我已经在 gcc4.8.4 上运行了示例程序。

          class ABC
          {       
           public:
            void fun(long a, long b) {}
            void fun(float a, float b) {} 
            void fun(int a, float b) {}   
          };
          int main()
          {
           ABC obj;
           obj.fun(1l,2l);
           obj.fun(1,2.3f);
           obj.fun(3.2f,4.2f);
           return 0;
          }
          

          这个程序有 3 个名为 fun 的函数,它们根据参数的数量及其类型而有所不同。 这些函数名称如下所示:

          ayadav@gateway1:~$ nm ./a.out |grep fun
          000000000040058c W _ZN3ABC3funEff
          00000000004005a0 W _ZN3ABC3funEif
          000000000040057a W _ZN3ABC3funEll
          
          • ABC 是类名的命令字符串
          • fun 是函数名的常用字符串
          • ff 两个浮点数->f 类型的参数
          • ll 两个 long->l 类型参数
          • 如果第一个整数参数->i 和一个浮点数->f 参数

          【讨论】:

          • 如果 C++ 标准没有指定任何特定的名称修饰技术,并且不同的编译器可能会将不同的信息附加到函数名称。如果不同的编译器不遵循相同的技术,那么共享库将如何链接。如果有错误,请纠正我我刚刚开始了解 c++。
          【解决方案8】:

          在设计链接编辑器时,C、FORTAN 和 COBOL 等语言没有命名空间、类、类成员等。需要名称修改来支持面向对象的功能,例如那些带有不支持它们的链接编辑器的功能。链接编辑器不支持附加功能这一事实经常被忽略;人们通过说由于链接编辑器而需要名称修改来暗示它。

          由于支持名称修饰所做的语言要求之间存在很大差异,因此对于如何在链接编辑器中支持它的问题没有简单的解决方案。链接编辑器旨在处理来自各种编译器的输出(对象模块),因此必须有一种通用的方式来支持名称。

          【讨论】:

            【解决方案9】:

            之前的所有答案都是正确的,但这里是 Python 的观点/推理示例。

            定义

            当类中的变量具有 __ 前缀(即两个下划线)& 没有 __ 后缀(即两个下划线或更多)时,它被视为私有标识符。 Python 解释器转换任何私有标识符,并将名称修改为 _class__identfier

            Example:
            MyClassName --> _myClassName
            __variable --> __variable
            

            为什么

            这是必需的,因为可以避免覆盖属性可能导致的问题。换句话说,为了覆盖,Python 解释器必须能够为子方法和父方法构建不同的 id,并且使用 __(双下划线)使 python 能够做到这一点。在下面的示例中,如果没有 __help,此代码将无法工作。

            class Parent:
                def __init__(self):
                   self.__help("will take child to school")
                def help(self, activities):
                    print("parent",activities)
            
                __help = help   # private copy of original help() method
            
            class Child(Parent):
                def help(self, activities, days):   # notice this has 3 arguments and overrides the Parent.help()
                    self.activities = activities
                    self.days = days
                    print ("child will do",self.activities, self.days)
            
            
            # the goal was to extend and override the Parent class to list the child activities too
            print ("list parent & child responsibilities")
            c = Child()
            c.help("laundry","Saturdays")
            

            【讨论】:

              【解决方案10】:

              这里的答案太棒了,所以这只是我的小经验的补充:我使用名称修饰来了解什么工具(gcc / vs /...)以及参数如何传递到堆栈以及什么调用约定我正在处理,并且基于名称,例如,如果看到_main,我知道它是Cdecl,其他人也一样

              【讨论】:

                猜你喜欢
                • 2010-10-24
                • 1970-01-01
                • 1970-01-01
                • 2019-09-28
                • 2016-12-07
                • 2013-02-13
                • 2018-06-13
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多