4

这是使用 g++ 4.4 和 g++ 4.7 在 Debian Squeeze 上测试的。考虑两个 C++ 源文件。

################
foo.cc
#################
#include <string>
using std::string;

int foo(void)
{
  return 0;
}

#################
bar.cc
#################
#include <string>
using std::string;

//int foo(void);
string foo(void);

int main(void)
{
  foo();
  return 0;
}
##################

如果我编译并运行它,可以预见会有问题。我正在使用scons。

################################
SConstruct
################################
#!/usr/bin/python


env = Environment(
    CXX="g++-4.7",
    CXXFLAGS="-Wall -Werror",
    #CXX="g++",
    #CXXFLAGS="-Wall -Werror",
    )

env.Program(target='debug', source=["foo.cc", "bar.cc"])
#################################

编译并运行...

$ scons

g++-4.7 -o bar.o -c -Wall -Werror bar.cc
g++-4.7 -o foo.o -c -Wall -Werror foo.cc
g++-4.7 -o debug foo.o bar.o

$ ./debug 

*** glibc detected *** ./debug: free(): invalid pointer: 0xbff53b8c ***
======= Backtrace: =========
/lib/i686/cmov/libc.so.6(+0x6b381)[0xb7684381]
/lib/i686/cmov/libc.so.6(+0x6cbd8)[0xb7685bd8]
/lib/i686/cmov/libc.so.6(cfree+0x6d)[0xb7688cbd]
/usr/lib/libstdc++.so.6(_ZdlPv+0x1f)[0xb7856c5f]
/lib/i686/cmov/libc.so.6(__libc_start_main+0xe6)[0xb762fca6]
./debug[0x8048461]
======= Memory map: ========
08048000-08049000 r-xp 00000000 fd:10 7602195    /home/faheem/corrmodel/linker/debug
08049000-0804a000 rw-p 00000000 fd:10 7602195    /home/faheem/corrmodel/linker/debug
09ae0000-09b01000 rw-p 00000000 00:00 0          [heap]
b7617000-b7619000 rw-p 00000000 00:00 0 
b7619000-b7759000 r-xp 00000000 fd:00 1180005    /lib/i686/cmov/libc-2.11.3.so
b7759000-b775a000 ---p 00140000 fd:00 1180005    /lib/i686/cmov/libc-2.11.3.so
b775a000-b775c000 r--p 00140000 fd:00 1180005    /lib/i686/cmov/libc-2.11.3.so
b775c000-b775d000 rw-p 00142000 fd:00 1180005    /lib/i686/cmov/libc-2.11.3.so
b775d000-b7760000 rw-p 00000000 00:00 0 
b7760000-b777c000 r-xp 00000000 fd:00 4653173    /lib/libgcc_s.so.1
b777c000-b777d000 rw-p 0001c000 fd:00 4653173    /lib/libgcc_s.so.1
b777d000-b777e000 rw-p 00000000 00:00 0 
b777e000-b77a2000 r-xp 00000000 fd:00 1179967    /lib/i686/cmov/libm-2.11.3.so
b77a2000-b77a3000 r--p 00023000 fd:00 1179967    /lib/i686/cmov/libm-2.11.3.so
b77a3000-b77a4000 rw-p 00024000 fd:00 1179967    /lib/i686/cmov/libm-2.11.3.so
b77a4000-b7889000 r-xp 00000000 fd:00 2484736    /usr/lib/libstdc++.so.6.0.17
b7889000-b788d000 r--p 000e4000 fd:00 2484736    /usr/lib/libstdc++.so.6.0.17
b788d000-b788e000 rw-p 000e8000 fd:00 2484736    /usr/lib/libstdc++.so.6.0.17
b788e000-b7895000 rw-p 00000000 00:00 0 
b78ba000-b78bc000 rw-p 00000000 00:00 0 
b78bc000-b78bd000 r-xp 00000000 00:00 0          [vdso]
b78bd000-b78d8000 r-xp 00000000 fd:00 639026     /lib/ld-2.11.3.so
b78d8000-b78d9000 r--p 0001b000 fd:00 639026     /lib/ld-2.11.3.so
b78d9000-b78da000 rw-p 0001c000 fd:00 639026     /lib/ld-2.11.3.so
bff41000-bff56000 rw-p 00000000 00:00 0          [stack]
Aborted

哇。如果链接器已警告foo以两种不同的方式声明,则可以避免这种情况。即使-Wall没有。那么,是否有它不这样做的原因,是否有一些标志可以打开以使其发出警告?提前致谢。

编辑:感谢所有的答案。当存在冲突的函数定义时,链接器确实会发出警告,而不是像我上面的示例中的冲突函数定义和声明。我不明白这种不同行为的原因。

4

4 回答 4

4

C++ 链接器仅识别唯一标识所需的函数。

这是来自以下关于 C++ 链接器的深入文章。

...符号的名称用额外的字符串装饰。这称为名称修改。

因为 C++ 支持命名空间,所以需要标识符名称前的修饰。例如,相同的函数名称可以在不同的命名空间中出现多次,但每次都表示不同的实体。为了使链接器能够区分这些实体,每个标识符的名称前面都带有表示其封闭命名空间的标记。

因为 C++ 允许函数重载,所以需要标识符名称后的修饰。再次,相同的函数名称可以表示不同的标识符,它们的不同之处仅在于它们的参数列表。为了使链接器能够区分这些,代表参数列表的标记被附加到标识符的名称中。忽略函数的返回类型,因为两个重载函数不能仅在返回类型上有所不同。

所以关键是应用于函数的名称修饰忽略了返回类型,因为重载函数不能因返回类型而有所不同。因此,链接器无法发现问题。

于 2012-01-30T23:59:09.183 回答
2

这是拥有foobar.h包含所有此类功能的本地项目头文件(可能)的最佳示例。这样编译器就可以看到这样的问题。

链接器从未打算识别此类问题。必须为 Real Engineers™ 留下一些事情要做。:-)

于 2012-01-31T00:08:50.147 回答
2

链接器只对编译器所说的在模块中定义的名称起作用,或者被模块引用(需要)。GCC 显然使用“Itanium C++ ABI”来修改函数名称(从 GCC 3 开始)。对于大多数函数,返回类型不会合并到损坏的名称中,这就是链接器不考虑它的原因:

安腾 C++ ABI

函数类型由它们的参数类型和可能的结果类型组成。除了 an 的外部级别类型,或者在 a 或函数编码中以其他方式分隔的外部名称中,这些类型由“F..E”对分隔。出于替换的目的(请参阅下面的压缩),分隔和未分隔的函数类型被认为是相同的。

函数类型的修饰是否包括返回类型取决于上下文和函数的性质。判断是否包含返回类型的规则是:

  • 模板函数(名称或类型)具有编码的返回类型,下面列出的例外情况。
  • 不作为函数名称修饰的一部分出现的函数类型,例如参数、指针类型等,具有编码的返回类型,下面列出的例外情况。
  • 非模板函数名称没有编码的返回类型。

上面 (1) 和 (2) 中提到的异常,从不包括返回类型,是

  • 构造函数。
  • 析构函数。
  • 转换运算符函数,例如 operator int

通常在 C++ 中,当编译器执行名称查找时(例如用于重载解析),函数的返回类型不会被考虑。这可能是名称修饰中通常不包含返回类型的部分原因。我不知道是否有更充分的理由不将返回类型合并到损坏的名称中。

于 2012-01-31T01:17:37.713 回答
0
$ cat foo.cpp

#include <string>
using std::string;

int foo(void)
{
    return 0;
}

$ cat bar.cpp

#include <string>
using std::string;

//int foo(void);
string foo(void);

int main(void)
{
    foo();
    return 0;
}

$ g++ -c -o bar.o bar.cpp
$ g++ -c -o foo.o foo.cpp
$ g++ foo.o bar.o
$ ./a.out 
$ echo $?
0
$ g++ --version
g++ (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1

无法重现。

于 2012-01-31T00:08:44.900 回答