不匹配的返回类型和链接时符号解析

huangapple go评论97阅读模式
英文:

Mismatched return types and link-time symbol resolution

问题

a.h中的声明与a.cpp中的定义具有不同的返回类型。如果将a.h包含在其对应的实现文件a.cpp中,编译器将捕获到这个不一致。但是,如果我注释掉这个包含(如上所示),然后尝试使用g++ main.cpp a.cpp进行编译和链接,我会得到以下链接时错误:

/usr/bin/ld: /tmp/ccgWiADF.o: in function 'main':
main.cpp:(.text+0x2c): undefined reference to 'returnString()'
collect2: error: ld returned 1 exit status

因此,链接器似乎可以将对returnInt()的调用解析到a.cpp中的定义,但不能对returnString()的调用执行相同操作。为什么会这样呢?

英文:

I've read that it's good practice for implementation files to include their corresponding header, as it allows certain type errors to be caught at compile-time instead of at link-time. However, while searching for an example of such an error, I ended up with the following code, which produces a result that I don't quite understand:

a.h:

char returnString(void);

int returnInt(void);

a.cpp:

// #include "a.h"
#include <string>

std::string returnString(void)
{
    return "Hi";
}

void returnInt(void)
{
    // return 1;
}

main.cpp:

#include "a.h"
#include <iostream>

int main(void)
{
    std::cout << returnString() << '\n';
    std::cout << returnInt() << '\n';

    return 0;
}

The declarations in a.h have a different return type from the definitions in a.cpp. This discrepancy is picked up by the compiler if a.h is included in its corresponding implementation file, a.cpp. However, if I comment out the inclusion (as shown above) and then try to compile and link with g++ main.cpp a.cpp, I get the following link-time error:

/usr/bin/ld: /tmp/ccgWiADF.o: in function 'main':
main.cpp:(.text+0x2c): undefined reference to 'returnString()'
collect2: error: ld returned 1 exit status

So, the linker seems to resolve the call to returnInt() to the definition in a.cpp, but cannot to the same for the call to returnString(). Why is this?

答案1

得分: 1

在这个实现中,std::string 带有一个ABI标签(用于区分它与C++11之前的实现)。当它被用作returnString的返回类型时,该标签会自动应用于returnString,从而更改其编译名称。因此,从链接器的角度来看,char returnString(void)std::string returnString(void) 具有不同的名称。

根据GCC的abi_tag属性文档

abi_tag属性可以应用于函数、变量或类声明。它修改实体的编译名称,以包含标签名称,以区分具有不同ABI的先前版本的函数或类;也许类的大小已经改变,或者函数具有不在编译名称中编码的不同返回类型。

[...]

当涉及ABI标签的类型用作变量的类型或函数的返回类型,而该标签在函数的签名中尚未存在时,该标签会自动应用于变量或函数。

然而,请注意,这仅适用于特定编译器和标准库。不应假设这种行为是通用的。

英文:

On this implementation, std::string has an ABI tag (to distinguish it from the pre-C++11 implementation). When it is used as the return type of returnString, the tag is automatically applied to returnString, which changes its mangled name. As a result, from the perspective of the linker, char returnString(void) and std::string returnString(void) have distinct names.

From GCC's documentation of abi_tag:

> The abi_tag attribute can be applied to a function, variable, or class declaration. It modifies the mangled name of the entity to incorporate the tag name, in order to distinguish the function or class from an earlier version with a different ABI; perhaps the class has changed size, or the function has a different return type that is not encoded in the mangled name.
>
> [...]
>
> When a type involving an ABI tag is used as the type of a variable or return type of a function where that tag is not already present in the signature of the function, the tag is automatically applied to the variable or function.

However, note that this is specific to this particular compiler and standard library. You should not assume this behavior in general.

答案2

得分: 1

当前的标准草案中:

> 对于具有多个翻译单元中的定义的可定义项D,
如果D是非内联非模板函数或变量,或者
如果不同翻译单元中的定义不满足以下要求,
程序是非法的;只有在可定义项附加到命名模块并且在后续定义发生的地方可达的情况下才需要诊断。
对于这样的项,对于在任何给定程序点可达的D的所有定义[...],必须满足以下要求。

然后在要求列表中,它说:

> 每个这样的定义都应由相同的令牌序列组成[...]

你程序中的问题是,在某一时刻你将returnString定义为char()类型,在程序的另一点你将其定义为std::string()类型。在编译期间,如果你不包含头文件"a.h",则这两个定义点互不可达。因此,程序是非法的,无需诊断。对于returnInt也是同样的情况,但显然实现选择在某种情况下发出诊断,而在另一种情况下不发出,这是符合标准的行为。执行该程序会导致未定义的行为。

这个答案告诉您为什么GCC在一种情况下发出诊断,而在另一种情况下不发出诊断。

英文:

From the current standard draft:

> For any definable item D with definitions in multiple translation units,
if D is a non-inline non-templated function or variable, or
if the definitions in different translation units do not satisfy the following requirements,
the program is ill-formed; a diagnostic is required only if the definable item is attached to a named module and a prior definition is reachable at the point where a later definition occurs.
Given such an item, for all definitions of D [...] that are reachable at any given program point, the following requirements shall be satisfied.

And then in the list of requirements it reads

> Each such definition shall consist of the same sequence of tokens [...]

The problem in your program is that you at one point you define returnString to be of type char() and at another point in the program you define it to be of type std::string(). During compilation the two definition points are not reachable from oneanother if you don't include the header "a.h". Thus the program is ill-formed NDR (no diagnostic required). The same applies to returnInt, but apparently the implementation chose to issue a diagnostic in one case and not in the other, which is standard conforming behaviour. Executing the program results in undefined behaviour.

This answer tells you why GCC issues a diagnostic in one case but not in the other.

答案3

得分: 0

关于主函数而言,它只知道returnString返回一个char*。当完全链接时,存在这样一个函数的声明,但没有对它的定义(因此出现未定义引用)。

可以将其视为重载共享相同的前端名称,但每个都具有不同且唯一的后端名称。

返回类型的差异会使您得到不同的符号/实体。特别是因为您不能仅根据返回类型进行重载。

英文:

As far as main is concerned, it only knows about returnString that returns a char*. When fully linked, there's a declaration for such a function, but there's no definition of it (hence the undefined reference).

Think of it as overloads sharing a same front-end name, but all having a distinct and unique back-end name.

Difference in return type will make you a different symbol/entity. Especially since you can't overload solely on the return type.

huangapple
  • 本文由 发表于 2023年8月10日 13:55:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/76872964.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定