永远不要拒绝 C++。它会 getcha。
我习惯于为我所做的一切编写单元测试。作为其中的一部分,我经常在测试的 .cxx 中定义名称为 A 和 B 的类以执行代码,安全地知道 i) 因为此代码永远不会成为库的一部分或在测试之外使用,名称冲突可能非常频繁,并且 ii) 可能发生的最坏情况是链接器会提示多次定义的 A::A() 或什么,我会修复该错误。我错了。
这里有两个编译单元:
#include <iostream>
using namespace std;
// Fwd decl.
void runSecondUnit();
class A {
public:
A() : version( 1 ) {
cerr << this << " A::A() --- 1\n";
}
virtual ~A() {
cerr << this << " A::~A() --- 1\n";
}
int version; };
void runFirstUnit() {
A a;
// Reports 1, correctly.
cerr << " a.version = " << a.version << endl;
// If you uncomment these, you will call
// secondCompileUnit: A::getName() instead of A::~A !
//A* a2 = new A;
//delete a2;
}
int main( int argc, char** argv ) {
cerr << "firstUnit BEGIN\n";
runFirstUnit();
cerr << "firstUnit END\n";
cerr << "secondUnit BEGIN\n";
runSecondUnit();
cerr << "secondUnit END\n";
}
和
#include <iostream>
using namespace std;
void runSecondUnit();
// Uncomment to fix all the errors:
//#define USE_NAMESPACE
#if defined( USE_NAMESPACE )
namespace mySpace
{
#endif
class A {
public:
A() : version( 2 ) {
cerr << this << " A::A() --- 2\n";
}
virtual const char* getName() const {
cerr << this << " A::getName() --- 2\n"; return "A";
}
virtual ~A() {
cerr << this << " A::~A() --- 2\n";
}
int version;
};
#if defined(USE_NAMESPACE )
} // mySpace
using namespace mySpace;
#endif
void runSecondUnit() {
A a;
// Reports 1. Not 2 as above!
cerr << " a.version = " << a.version << endl;
cerr << " a.getName()=='" << a.getName() << "'\n";
}
好的,好的。显然我不应该声明两个名为 A 的类。我的错。但我敢打赌你猜不到接下来会发生什么......
我编译了每个单元,并(成功)链接了两个目标文件并运行了。嗯……
这是输出 (g++ 4.3.3):
firstUnit BEGIN
0x7fff0a318300 A::A() --- 1
a.version = 1
0x7fff0a318300 A::~A() --- 1
firstUnit END
secondUnit BEGIN
0x7fff0a318300 A::A() --- 1
a.version = 1
0x7fff0a318300 A::getName() --- 2
a.getName()=='A'
0x7fff0a318300 A::~A() --- 1
secondUnit END
所以有两个独立的A类。在第二次使用中,使用了第一次的析构函数和构造函数,即使只有第二个在其编译单元中是可见的。更奇怪的是,如果我取消注释 runFirstUnit 中的行,而不是调用 A::~A,则会调用 A::getName。很明显,在第一次使用中,对象获得了第二个定义的 vtable(getName 是第二个类中的第二个虚函数,第一个中的第二个析构函数)。它甚至从一开始就正确地获取了构造函数。
所以我的问题是,为什么链接器没有提示多重定义的符号。 它似乎选择了第一场比赛。重新排序链接步骤中的对象确认。
Visual Studio 中的行为是相同的,所以我猜这是一些标准定义的行为。我的问题是,为什么?很明显,链接器很容易因为重复的名称而感到厌烦。 如果我添加,
void f() {}
它提示两个文件。为什么不是我的类构造函数和析构函数?
编辑 问题不是“我应该怎么做才能避免这种情况”,或者“如何解释这种行为”。它是,“为什么链接器不捕获它?”项目可能有数千个编译单元。明智的命名做法并不能真正解决这个问题——它们只会让问题变得模糊,只有在你可以训练每个人都遵循它们的情况下。
上面的示例导致了编译器工具可以轻松且明确解决的模棱两可的行为。那么,他们为什么不呢?这只是一个错误吗? (我怀疑不是。)
** 编辑 ** 请参阅下面 litb 的回答。我重复一遍以确保我的理解是正确的:
链接器只为强引用生成警告。 因为我们有共享头文件,内联函数定义(即声明和定义在同一位置,或模板函数)被编译成多个目标文件,供每个看到它们的 TU 使用。因为没有简单的方法来将此代码的生成限制为单个目标文件,链接器的工作是从许多定义中选择一个。为了使链接器不会产生错误,这些已编译定义的符号在目标文件中被标记为弱引用。
最佳答案
编译器和链接器依赖于这两个完全相同的类。在你的情况下,它们是不同的,所以奇怪的事情发生了。一个定义规则说结果是未定义的行为——所以行为根本不需要在编译器之间保持一致。 .我怀疑在 runFirstUnit 中,在 delete 行中,它调用了第一个虚拟表条目(因为在 its 翻译单元中,析构函数可能占据第一个条目) .
在第二个翻译单元中,该条目恰好指向A::getName,但在第一个翻译单元(您执行delete)中,该条目指向 A::~A。由于这两个名称不同(A::~A vs A::getName),因此您不会遇到名称冲突(您将同时为析构函数发出代码和 getName)。但是由于它们的类名相同,它们的 v 表将故意冲突,因为由于两个类具有相同的名称,链接器将认为它们是同一个类并假定相同的内容。
请注意,所有成员函数都是在类中定义的,这意味着它们都是内联函数。这些函数可以在一个程序中定义多次。在类内定义的情况下,基本原理是您可以将相同的类定义从它们的头文件包含到不同的翻译单元中。但是,您的测试函数不是内联函数,因此将其包含到不同的翻译单元中会触发链接器错误。
如果启用命名空间,就不会发生任何冲突,因为 ::A 和 ::mySpace::A 是不同的类,当然将得到不同的 v 表。
关于c++ - 链接器何时应生成多重定义的 X 警告?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1435924/
我正在尝试设置一个puppet节点,但rubygems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由rubygems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby
我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer
我试图使用yard记录一些Ruby代码,尽管我所做的正是所描述的here或here#@param[Integer]thenumberoftrials(>=0)#@param[Float]successprobabilityineachtrialdefinitialize(n,p)#initialize...end虽然我仍然得到这个奇怪的错误@paramtaghasunknownparametername:the@paramtaghasunknownparametername:success然后生成的html看起来很奇怪。我称yard为:$yarddoc-mmarkdown我做错了什么?
我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server
我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢
我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby1.9+ 关于ruby-主要:Objectwhenrun
我正在使用active_admin,我在Rails3应用程序的应用程序中有一个目录管理,其中包含模型和页面的声明。时不时地我也有一个类,当那个类有一个常量时,就像这样:classFooBAR="bar"end然后,我在每个必须在我的Rails应用程序中重新加载一些代码的请求中收到此警告:/Users/pupeno/helloworld/app/admin/billing.rb:12:warning:alreadyinitializedconstantBAR知道发生了什么以及如何避免这些警告吗? 最佳答案 在纯Ruby中:classA
最近,当我启动我的Rails服务器时,我收到了一长串警告。虽然它不影响我的应用程序,但我想知道如何解决这些警告。我的估计是imagemagick以某种方式被调用了两次?当我在警告前后检查我的git日志时。我想知道如何解决这个问题。-bcrypt-ruby(3.1.2)-better_errors(1.0.1)+bcrypt(3.1.7)+bcrypt-ruby(3.1.5)-bcrypt(>=3.1.3)+better_errors(1.1.0)bcrypt和imagemagick有关系吗?/Users/rbchris/.rbenv/versions/2.0.0-p247/lib/ru
我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin
我有一个只接受一个参数的方法:defmy_method(number)end如果使用number调用方法,我该如何引发错误??通常,我如何定义方法参数的条件?比如我想在调用的时候报错:my_method(1) 最佳答案 您可以添加guard在函数的开头,如果参数无效则引发异常。例如:defmy_method(number)failArgumentError,"Inputshouldbegreaterthanorequalto2"ifnumbereputse.messageend#=>Inputshouldbegreaterthano