【问题标题】:link functions with mismatching signature签名不匹配的链接函数
【发布时间】:2016-08-11 22:56:27
【问题描述】:

我正在玩弄 gccg++ 编译器并尝试在其中编译一些 C 代码,我的目的是看看编译器/链接器是如何强制执行的将具有某些函数声明的模型链接到具有该函数的实现的模型,正确的函数被链接(就传递的参数和返回的值而言)

例如让我们看看这段代码

#include <stdio.h>

extern int foo(int b, int c);

int main()
{
 int f = foo(5, 8);

 printf("%d",f);
}

在我的符号表中编译后,我会有一个 foo 的符号,但是在 elf 文件格式中,没有地方描述所采用的参数和函数签名(int(int,int)),所以基本上如果我写一些其他代码如:

char foo(int a, int b, int c)
{

 return (char) ( a + b + c );

}

编译那个模型,它也会有一些叫做 foo 的符号,如果我把这些模型链接在一起,会发生什么?我从来没有想过这一点,编译器将如何克服这个弱点......我知道在 g++ 中,编译器会为每个符号生成一些与其命名空间有关的前缀,但它是否也考虑到签名?如果有人遇到过这个问题,如果他能阐明这个问题,那就太好了

【问题讨论】:

  • 您已使用 C 标记,但询问 C++。它们是不同的语言,答案可能会有所不同,具体取决于您真正询问的语言。请澄清。
  • 不清楚。您在任何地方都没有提到“在 g++ 中编译 c”。您确实意识到 C++ 代码可能看起来像 C 代码但根本不是一回事吗?因此,“在 g++ 中编译 C”本身并不清楚 - 如果您编译 g++ 中所示的代码,那么根据定义,它是 C++ 代码而不是 C 代码。使用 g++ 编译 C 代码需要额外的语法。如果你不明白这意味着什么,那么建议你研究一下。这一点实际上对您的问题非常重要,需要说清楚。
  • C 和 C++ 是不同的语言。关于这些语言,您的问题特别不同。所以选择其中一个
  • 我同意你的观点,也许应该编辑标题但故意没有在 C++ @Olaf 下标记
  • 那么答案是错误的。

标签: gcc linker g++ elf


【解决方案1】:

问题已解决with name mangling

在编译器构造中,名称修饰(也称为名称修饰) 是一种用于解决因需要而引起的各种问题的技术 解析许多现代编程实体的唯一名称 编程语言。

它提供了一种以 函数、结构、类或其他数据类型,以便传递更多 从编译器到链接器的语义信息。

当语言允许不同的实体存在时,就会出现需求 只要它们占用不同的标识符,就用相同的标识符命名 命名空间(命名空间通常由模块、类、 或显式命名空间指令)或具有不同的签名(例如 函数重载)。

注意the simple example

考虑以下两个 C++ 程序中 f() 的定义:

int  f (void) { return 1; }
int  f (int)  { return 0; }
void g (void) { int i = f(), j = f(0); }

这些是不同的功能,彼此之间没有任何关系 从名字。如果它们被本地翻译成 C 而没有 更改,结果将是一个错误 - C 不允许两个 具有相同名称的函数。因此,C++ 编译器将编码 符号名称中的类型信息,结果是什么 类似:

int  __f_v (void) { return 1; }
int  __f_i (int)  { return 0; }
void __g_v (void) { int i = __f_v(), j = __f_i(0); }

请注意,即使没有冲突,g() 也会被破坏;姓名 mangling 适用于所有符号。

【讨论】:

  • 啊,非常感谢你,但我自己找到了我的问题的答案:)
  • @DrPrItay:当你想到 C 和 C++ 时它是不完整的,现在它是错误的,因为你已经澄清你问过 C。如果你不理解区别,你应该从头开始.
  • 如果这个答案确实回答了你的问题,那么你的问题显然是关于 C++ 的。
  • 那为什么还要故意使用错误的标签,违反网站规则?
  • 如前所述,这是一个错误,我已经编辑了我的问题 :)
【解决方案2】:

哇,我一直在自己探索和测试它,我想出了一个让我大吃一惊的解决方案,

所以我写了以下代码并在 gcc 编译器上编译了它

ma​​in.c

#include <stdio.h>

extern int foo(int a, char b);

int main()
{
 int g = foo(5, 6);
 printf("%d", g);
 return 0;
}

foo.c

typedef struct{
 int a;
 int b;
 char c;
 char d;

} mystruct;

mystruct foo(int a, int b)
{
 mystruct myl;

 my.a = a;
 my.b = a + 1;
 my.c = (char) b;
 my.d = (char b + 1;

 return my1;
}

现在我首先使用 gccfoo.c 编译为 foo.o 并使用检查符号表 readelf 我有一些条目叫做 foo

在此之后,我将main.c 编译为main.o,检查了符号表,它还有一些名为foo 的条目,我将这两个链接在一起,令人惊讶的是它起作用了,我运行main.o,显然遇到了一些分段错误,这是有道理的,因为在foo.o 中实现的foo 的实际实现可能需要三个参数(第一个应该是结构加法器),一个在main.o 的定义下没有传递给foo 的参数那么实际的实现会从main的栈帧中访问一些不属于自己的内存,然后尝试访问它认为得到的地址,结果是段错误,没关系,

现在我用 g++ 而不是 gcc 再次编译了这两个模型,结果令人惊讶。我发现foo.o 下的符号条目是@987654338 @ 并且在main.o 下是_Z3fooic,现在我的猜测是ii 后缀表示int intic 后缀表示int char,这可能是指应该传递给函数的参数,因此允许编译器知道一些函数减速得到实际的实现。所以我将main.c 中的foo 声明更改为

extern int foo(int a, int b);

重新编译,这次得到符号_Z3fooii,我再次链接了两个模型,这次它工作得令人惊讶,我尝试运行它并再次遇到分段错误,这也是有道理的,因为编译器甚至不会总是授权正确的返回值..无论如何我最初的想法是 - g++ 在符号名称中包含函数签名,从而强制链接器为函数实现提供正确的参数以正确的函数声明

【讨论】:

  • 您的代码调用了未定义的行为。这是不对的!
  • 调用UB的目的是什么?我希望你永远不会和我在同一个项目团队中工作(而且我永远不必依赖你编写的代码)。
  • OT:在 C11 中有一种方法可以在不折磨编译器的情况下重载函数 :) 看看 _Generic
猜你喜欢
  • 1970-01-01
  • 2018-03-24
  • 1970-01-01
  • 2015-03-26
  • 2017-07-05
  • 1970-01-01
  • 1970-01-01
  • 2019-06-16
  • 1970-01-01
相关资源
最近更新 更多