开发环境:GNU GCC(g ++)4.1.2
虽然我正在尝试研究如何在单元测试中增加"代码覆盖率 - 特别是功能覆盖率",但我发现有些类dtor似乎是多次生成的.你们当中有些人知道为什么吗?
我通过使用以下代码尝试并观察了我上面提到的内容.
在"test.h"中
class BaseClass
{
public:
~BaseClass();
void someMethod();
};
class DerivedClass : public BaseClass
{
public:
virtual ~DerivedClass();
virtual void someMethod();
};
Run Code Online (Sandbox Code Playgroud)
在"test.cpp"中
#include <iostream>
#include "test.h"
BaseClass::~BaseClass()
{
std::cout << "BaseClass dtor invoked" << std::endl;
}
void BaseClass::someMethod()
{
std::cout << "Base class method" << std::endl;
}
DerivedClass::~DerivedClass()
{
std::cout << "DerivedClass dtor invoked" << std::endl;
}
void DerivedClass::someMethod()
{
std::cout << "Derived class method" << std::endl;
}
int main()
{
BaseClass* …Run Code Online (Sandbox Code Playgroud) 编译器如何实现虚拟继承?
在以下代码中:
class A {
public:
A(int) {}
};
class B : public virtual A {
public:
B() : A(1) {}
};
class C : public B {
public:
C() : A(3), B() {}
};
Run Code Online (Sandbox Code Playgroud)
编译器是否生成两个B::ctor函数实例,一个没有A(1)调用,一个带有它?因此,当B::constructor从派生类的构造函数调用时,将使用第一个实例,否则使用第二个实例.
我一直在阅读Clang源代码,发现了一些关于ARM C++ ABI的有趣内容,我似乎无法理解其中的理由.从ARM ABI文档的在线版本:
这个ABI要求C1和C2构造函数返回它(而不是void函数),这样C3构造函数可以尾调用C1构造函数,C1构造函数可以尾调用C2.
(对于非虚拟析构函数也是如此)
我不知道是什么C1,C2以及C3在这里引用.这一节,就是要的§3.1.5从通用(即安腾)ABI的修改,而这部分(至少在这个网上verison)简单地说:
构造函数返回void结果.
无论如何,我真的无法弄清楚这是什么目的:如何使构造函数返回此允许尾部调用优化,以及在什么情况下?
到目前为止,我可以说,构造函数可以尾部调用另一个具有相同this返回值的唯一时间是具有单个基类的派生类,一个简单的构造函数体,没有具有非平凡构造函数的成员,并且没有虚拟表指针.实际上,使用void返回来优化尾部调用似乎实际上更容易,而不是更难,因为这样可以消除单个基类的限制(在多基类的情况下,this指针从最后调用的构造函数不会是this派生对象的指针).
我在这里错过了什么?ARM调用约定是否有this必要使返回成为必要?
我在Ubuntu Trusty上使用C++ 11和g ++ 4.8.
考虑一下这个片段
class Parent {
public:
virtual ~Parent() = default;
virtual void f() = 0;
};
class Child: public Parent {
public:
void f(){}
};
Run Code Online (Sandbox Code Playgroud)
叫做使用
{
Child o;
o.f();
}
{
Parent * o = new Child;
delete o;
}
{
Child * o = new Child;
delete o;
}
Run Code Online (Sandbox Code Playgroud)
我使用gcov生成我的代码覆盖率报告.它报告带有符号的析构函数_ZN6ParentD0Ev永远不会被调用,而它_ZN6ParentD2Ev是.
回答构造函数符号的双重发射和GNU GCC(g ++):为什么它会生成多个dtors?报告_ZN6ParentD0Ev是删除构造函数.
有没有在Parent课堂上调用这个"删除析构函数"的情况?
附属问题:如果没有,有没有办法获得gcov/lcov代码覆盖工具(使用gcov与CMake/CDash一起使用的详细指南的回答?)在其报告中忽略该符号?
我使用升压测试进行单元测试,使用gcov和lcov测量覆盖率.
unfortuanlly genhtml为函数覆盖生成类似的报告:

我现在想知道函数_ZN7UtilLib11ProgressBarC2EjdRSo究竟是什么.
到目前为止,我无法将此函数与ProgressBar的任何类接口相关联:
class ProgressBar {
public:
explicit ProgressBar(
unsigned int expected_count,
double updateInterval = 30,
std::ostream& os = std::cout);
unsigned int operator+=(unsigned int increment);
unsigned int operator++();
unsigned int operator++(int i);
}
Run Code Online (Sandbox Code Playgroud)
任何人都可以帮助我如何使用gcov获得更好的函数名称或者如何理解这些函数名称.
该应用程序使用gcc4.7编译,带有以下标志:-g -g -save-temps=obj -Wall -Wextra -Wno-unused-parameter -Wno-error=unused-parameter -O0 -pedantic
从C++标准(ISO/IEC 14882:2003(E)),§12.5.4,关于重载operator delete:
如果delete-expression以unary :: operator开头,则在全局范围内查找释放函数的名称.否则,如果使用delete-expression释放静态类型具有虚拟析构函数的类对象,则释放函数是在动态类型的虚拟析构函数(12.4)的定义中通过查找找到的函数.否则,如果使用delete-expression释放类T或其数组的对象,则该对象的静态和动态类型应相同,并且在T的范围内查找解除分配函数的名称.如果此查找未能找到名称,在全局范围内查找名称.如果查找结果不明确或不可访问,或者查找选择了放置重新分配功能,则程序格式错误.
§12.5.7也很有趣:
由于成员分配和释放功能是静态的,因此它们不能是虚拟的.[注意:但是,当delete-expression的cast-expression引用类类型的对象时,因为实际调用的释放函数是在作为对象动态类型的类的范围内查找的,如果是析构函数是虚拟的,效果是一样的.例如,
struct B {
virtual ˜B();
void operator delete(void*, size_t);
};
struct D : B {
void operator delete(void*);
};
void f()
{
B* bp = new D;
delete bp; // uses D::operator delete(void*)
}
Run Code Online (Sandbox Code Playgroud)
这里,由于虚拟析构函数,D ::非数组对象的存储由D :: operator delete()释放.
看完之后,我想知道......
§5.3.5.5也可能是相关的:
在第一个替代(删除对象)中,如果操作数的静态类型与其动态类型不同,则静态类型应为操作数的动态类型的基类,静态类型应具有虚拟析构函数或行为未定义.在第二个备选(删除数组)中,如果要删除的对象的动态类型与其静态类型不同,则行为未定义.
请考虑以下文件
foo.h中
class Foo
{
Foo();
Foo(int x);
void bar();
}
Run Code Online (Sandbox Code Playgroud)
foo.cc
# include foo.h
Foo::Foo() {}
Foo::Foo(int x) {}
void Foo::bar() {}
Run Code Online (Sandbox Code Playgroud)
将这些文件编译为LLVM bitcode时foo.bc,如下所示
clang++ -c -o foo.bc -emit-llvm foo.cc
生成的LLVM bitcode文件,foo.bc每个构造函数定义包含两个符号,但函数定义只包含一个符号.为什么是这样?
我已经在LLVM版本3.4和4.0.1上测试了这个,并且两个版本都会出现这种情况.供参考,这是输出
llvm-nm foo.bc
T _ZN3Foo3barEv
T _ZN3FooC1Ei
T _ZN3FooC1Ev
T _ZN3FooC2Ei
T _ZN3FooC2Ev
Run Code Online (Sandbox Code Playgroud)
- 编辑 -
根据下面的milleniumbug评论,这里有一些关于完整对象构造函数的附加信息:
我正在用gcov分析我的代码.它说在堆栈中创建对象时,我的代码是2个函数.但是,当我做新删除100%功能覆盖是实现的.
码:
class Animal
{
public:
Animal()
{
}
virtual ~Animal()
{
}
};
int main()
{
Animal animal;
}
Run Code Online (Sandbox Code Playgroud)
我执行的命令用于生成gcov报告.
rm -rf Main.g* out.txt a.out coverage;
g++ -fprofile-arcs -ftest-coverage -lgcov -coverage Main.cpp;
./a.out;
lcov --capture --directory . --output-file out.txt;
genhtml out.txt --output-directory coverage;
Run Code Online (Sandbox Code Playgroud)
生成的htmls显示我的功能覆盖率为3/4 - 75%.
但是一旦我将堆栈对象更改为堆,
码:
class Animal
{
public:
Animal()
{
}
virtual ~Animal()
{
}
};
int main()
{
auto animal = new Animal;
delete animal;
}
Run Code Online (Sandbox Code Playgroud)
我的功能覆盖率是100%.
只有在调用"new"和"delete"时才会调用哪些隐藏函数?
假设我有一个非常基础的课程:
// lib.h
class A
{
public:
A();
void nop();
};
// lib.cpp
#include "lib.h"
A::A() {}
void A::nop() {}
Run Code Online (Sandbox Code Playgroud)
,我将其编译为共享库:
g++ lib.cpp -shared -o lib.so -fPIC
Run Code Online (Sandbox Code Playgroud)
当我从库的代码部分查看导出的符号时,我看到其中的三个:
$ nm lib.so | grep ' T '
00000000000010f6 T _ZN1A3nopEv
00000000000010ea T _ZN1AC1Ev
00000000000010ea T _ZN1AC2Ev
$ nm lib.so | grep ' T ' | cut -d ' ' -f3 | xargs c++filt
A::nop()
A::A()
A::A()
Run Code Online (Sandbox Code Playgroud)
为什么构造函数导出两次?我已经在Linux上使用gcc和clang进行了测试,结果是相同的。
PS:我并不是要解决一个真正的问题,这只是我注意到的一个特点。
测试用例如下:
// test.cpp
class X {
public:
X();
};
X::X() { }
void foo() {
X x;
}
Run Code Online (Sandbox Code Playgroud)
编译它并读取目标文件中的符号,如下所示:
[root@localhost tmp]# g++ -c test.cpp
[root@localhost tmp]# readelf -s -W test.o
Run Code Online (Sandbox Code Playgroud)
符号表'.symtab'包含12个条目:
Num:值大小类型绑定Vis Ndx名称
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS test.cpp
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 6
6: 0000000000000000 0 SECTION …Run Code Online (Sandbox Code Playgroud) 编译时,这个:
template <typename T>
struct ConstArray {
///MEMBERS
T* data_;
T* end_;
///Constructors
ConstArray(T* data, T* end) : data_(data), end_(end) {}
};
template struct ConstArray<const char>;
Run Code Online (Sandbox Code Playgroud)
给我(nm -C *.o):
0000000000000000 W ConstArray<char const>::ConstArray(char const*, char const*)
0000000000000000 W ConstArray<char const>::ConstArray(char const*, char const*)
0000000000000000 n ConstArray<char const>::ConstArray(char const*, char const*)
Run Code Online (Sandbox Code Playgroud)
对于我定义的每个构造函数,我似乎得到三个符号(2 W + 1 n(不知道那是什么)).功能似乎只给我一个预期的.有人可以解释一下这是为什么或指出我的解释?
请使用以下源代码:
struct Foo {
Foo(){}
};
Foo f;
Run Code Online (Sandbox Code Playgroud)
使用时clang++,它会为构造函数创建一个符号:
clang++ -c foo.cpp
nm -C foo.o | grep Foo
0000000000000000 W Foo::Foo()
Run Code Online (Sandbox Code Playgroud)
但是,在编译时g++,它会为构造函数创建多个符号:
g++ -c foo.cpp;
nm -C foo.o | grep Foo
0000000000000000 W Foo::Foo()
0000000000000000 W Foo::Foo()
0000000000000000 n Foo::Foo()
Run Code Online (Sandbox Code Playgroud)
为什么会g++在同一个目标文件中创建重复的弱符号?
我唯一的理论是它与内联有关,但这只是猜测.
我应该注意,受损的名称如下所示:
g++ -c foo.cpp; nm foo.o | grep Foo
0000000000000000 W _ZN3FooC1Ev
0000000000000000 W _ZN3FooC2Ev
0000000000000000 n _ZN3FooC5Ev
Run Code Online (Sandbox Code Playgroud)
因此,尽管未重整名称是相同的,ZN3FooC1Ev并且ZN3FooC2Ev是不同的.