关于C++中的名称修饰问题

29

我正在学习和理解C++中的名称重整。以下是一些问题:

(1) 来自 devx

  

当全局函数被重载时,每个重载版本生成的名称都是唯一的。名称重整也适用于变量。因此,具有相同用户给定名称的局部变量和全局变量仍然具有不同的重整名称。

除了函数重载和相同名称的全局和局部变量之外,还有其他使用名称重整的示例吗?

(2) 来自 Wiki

  

需要出现在语言允许使用相同标识符命名不同实体的情况下,只要它们占用不同的命名空间(其中命名空间通常由模块、类或显式命名空间指令定义)。

我不太明白为什么名称重整仅适用于标识符属于不同命名空间的情况,因为重载函数可以在同一命名空间中,相同名称的全局和局部变量也可以在同一空间中。如何理解这一点?

具有相同名称但位于不同作用域的变量是否也使用名称重整?

(3) C语言是否具有名称重整?如果没有,它如何处理一些全局和局部变量具有相同名称的情况?C没有函数重载,对吗?

谢谢和问候!

6个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
31

C语言不会进行名称重整,但是它会在函数名之前加上下划线,因此printf(3)实际上在libc对象中为_printf

而在C++中,情况就不同了。最初,Stroustrup创建了“带类的C”或cfront,一种将早期C++编译成C的编译器。然后其他工具- C编译器和链接器-将被用于生成对象代码。这意味着C++名称必须以某种方式转换为C名称。这正是名称重整所做的。它为每个类成员和全局/命名空间函数和变量提供唯一的名称,因此,在最终链接器名称中,命名空间和类名称(用于解析)和参数类型(用于重载)都包含在内。

使用nm(1)等工具非常容易看到这一点-编译您的C ++源代码并查看生成的符号。以下是在OSX上使用GCC:

namespace zoom
{
    void boom( const std::string& s )
    {
        throw std::runtime_error( s );
    }
}

~$ nm a.out | grep boom
0000000100001873 T __ZN4zoom4boomERKSs

在C和C++中,局部(自动)变量不会产生符号,但是存在于寄存器或堆栈上。

编辑:

局部变量在生成的目标文件中没有名称,仅仅是因为链接器不需要知道它们。没有名称,也就没有名称混淆。在C++中,除了这些(链接器必须查看的内容),其他所有内容都进行了名称混淆。


5
如果您有嵌套的模板实例化,这将会更加有趣 :-) - James McNellis
13
是的,我通常称它为“疼痛”... :) - Nikolai Fetissov
谢谢Nikolai!正如你所说:“在C和C++中,局部(自动)变量不会产生符号,但是存在寄存器或堆栈中”,那么在C++中局部变量的名称是否被编码?如果没有,哪些变量的名称被编码? - Tim
1
在C语言中,前导下划线只存在于一些奇怪的平台上,它远非普遍规律。 - Marc Glisse

21

符号重整仅是编译器让链接器满意的方式。

在C语言中,无论如何你都不能有两个同名函数。所以链接器假定函数名称唯一。(你可以在不同的编译单元中有静态函数,因为它们的名称对链接器没有兴趣。)

在C++中,只要函数有不同的参数类型,就可以有两个同名函数。因此,C++以某种方式组合函数名称和类型,以使链接器将它们视为具有不同的名称。

符号重整的确切方式对程序员来说并不重要,只有编译器很重要,事实上每个编译器都以不同的方式进行符号重整。所有需要注意的是,使用相同基本名称的每个函数在链接器中都应该是独立的。

现在您可以看到,将命名空间和模板添加到混合物中会扩展原则。


3
请注意,名称被弄乱的方式并不重要。这是很重要的 - 例如,当你链接由不同编译器编译的库时。这就是为什么许多 C++ 库发行多个版本的原因,一个针对 MSVC,一个针对 mingw/gcc 等等。 - el.pescado - нет войне
2
我的意思是:混淆的确切方式对于你这个程序员来说并不重要。 - egrunin
非常重要的是不要破坏当前API的向后兼容性,可以通过向函数添加默认参数来实现。主要问题是,新函数是否具有不同的名称?从我在这里阅读的内容来看,答案很可能是YES :-( - jaques-sam

9

技术上来说,这被称为“装饰”。听起来不那么粗俗,但是“mangling”似乎意味着CreditInterest可能会被重新排列成IntCrederestit,而实际发生的更像是_CreditInterest@4,可以说是“装饰”而不是“破坏”。话虽如此,我也称之为“破坏” :-)但是如果你搜索“C ++名称装饰”,你会找到更多技术信息和示例。


实际上这取决于编译器。其中一些编译器会将名称混淆成对编译器来说毫无意义的字符串。早期版本的VC++在这方面尤其糟糕。:) 但是,两个搜索术语都是有效的。 - Donnie
我同意;当我开始时,我们只说mangling,随着时间的推移decorating变得更加普遍,当我开始查看混淆的名称时,decorating似乎合适。我的猜测是有人改变了他们的做事方式,并希望留下旧名称。不过,只有部分成功 :-) - Kate Gregory
1
嗯...搜索“C++名称修饰”大多数结果实际上都会出现标题中带有“mangling”的情况 :-). 看起来Google在这种情况下将“decoration”哈希到与“mangling”相同的槽中。 - Leo Heinsaar

5

除了函数重载和同名全局变量和局部变量之外,还有哪些示例使用名称混淆技术?

C++总是对所有符号进行名称混淆。这只是为了编译器更容易处理。通常,混淆会对参数列表或类型进行编码,因为这些是需要名称混淆技术的最常见原因。

C语言不进行名称混淆。通过作用域控制访问同名的局部变量和全局变量。


谢谢Donnie。你认为名称修饰只适用于不同命名空间中具有相同名称的标识符吗? - Tim

2

来源: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

0

Agner网站上有更多关于名称修饰的信息,以及不同编译器中如何进行名称修饰。

名称修饰(也称为命名重整)是C ++编译器用于向对象文件中的函数和对象名称添加附加信息的一种方法。当一个模块中定义的函数或对象被另一个模块引用时,链接器使用这些信息。名称修饰具有以下作用: 1. 使链接器能够区分不同版本的重载函数。 2. 使链接器能够检查所有模块中声明的对象和函数是否完全相同。 3. 使链接器能够在错误消息中提供有关未解析引用类型的完整信息。 名称修饰是为了实现第一目的而发明的。其他目的是次要的好处,并非所有编译器都完全支持。必须为函数提供的最小信息是函数的名称以及其所有参数的类型以及任何类或命名空间限定符。可能的附加信息包括返回类型、调用约定等。所有这些信息都被编码成单个ASCII文本字符串,对人类观察者来说看起来很神秘。 链接器不必知道这个代码的含义才能实现第1和第2个目的。 它只需要检查字符串是否相同即可。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,