温故指针之data pointer
指针不过是有类型信息的地址, 它可以是一个函数的地址 function pointer, 也可是一个数据的地址 data pointer. 在我的 64bit macbook上写个测试程序做一点简单回顾
变量的地址和变量的类型有关
1. 全局和局部静态变量
已初始化的全局以及局部静态变量都是放在目标文件的 .data 段中
而未初始化的全局以及局部静态变量则放在目标文件的 .bss 段中
int statistic(const char* file, int line, FILE* fd=stderr) {
static uint64_t total_count = 0;
//…
}
(gdb) p &total_count
$1 = (uint64_t *) 0x100002290
2. 栈变量
函数的局部变量是在栈上分配的, 其地址是从栈顶向下增长的, 也就是说这个栈是个倒过来的, 栈顶在下面(低地址方向)
int test_point(int argc, char* argv[]) {
int var = 1;
int val = 2;
int ptrSize = sizeof(void*);
printf("pointer size=%d\n", ptrSize);
printf("pointer addr--var:%p, val: %p\n", &var, &val);
}
pointer size=8
pointer addr--var:0x7fff5fbffaa4, val: 0x7fff5fbffaa0
(gdb)info register
栈底 rbp 0x7fff5fbffab0 0x7fff5fbffab0
栈顶 rsp 0x7fff5fbffaa0 0x7fff5fbffaa0
由于声明了两个int 变量, 栈顶向下移动了两次, 一次4个字节
3. 堆变量
堆变量是在堆上分配的, 其地址是向上增长的
class Point {
public:
Point(double x):m_x(x) {}
virtual ~Point() {}
double x() const { return m_x; }
virtual void dump() { printf("Point::dump x=%f\n", m_x); }
virtual void move() { printf("Point::move point\n"); }
private:
double m_x;
};
Point* pObj = new Point(30.0);
(gdb) p pObj
$4 = (Point *) 0x100200000
(gdb) p *pObj
$1 = {_vptr$Point = 0x100002120 <vtable for Point+16>, m_x = 30}
Class Point size is 16 bytes:
一开始 8 bytes 是个vptr, 指向一个virtual table, 这个table的元素就是一个个虚函数地址, 用 gdb 看下这个指针的内容
(gdb) p/x ((uint64_t)0x100200000)
$6 = 0x100002120这是一个指针的指针, 指向的是virtual table 的地址, 看看这个虚表的内容
(gdb) p/a ((uint64_t)0x100002120)@7
$23 = {0x100001780 <_ZN5PointD1Ev>, 0x100001790 <_ZN5PointD0Ev>, 0x1000017a0 <_ZN5Point4dumpEv>, 0x1000017c0 <_ZN5Point4moveEv>, 0x7fff79823390, 0x100001be8 <_ZTS5Point>, 0x0}1) _ZN5PointD1Ev: 虚析构函数
D0Ev mangled: heap object destructor is called when that version (ie with delete ; p destructor)2) _ZN5PointD0Ev: 虚析构函数
D1Ev mangled: at the bottom of the class hierarchy, that most derived class, call the version of the stack so any object destructor are called that version.
D0Ev mangled: located in a non-underlying class hierarchy, which is used as a base class, call the version.3) _ZN5Point4dumpEv: 虚函数 Point::dump
4) _ZN5Point4moveEv: 虚函数 Point::move
5) 0x7fff79823390, 不太清楚
6) _ZTS5Point: typeinfo name for Point
这里函数的名称不太容易看懂, 它们是经过C++编译器修饰过的, 可以用 binutils 里的 c++filt 来看 (macosx 上是gc++filt )
gc++filtZN5PointD1EvPoint:: Point()
gc++filt _ZN5PointD0Ev
Point::~Point()
$ gc++filt _ZTS5Point
typeinfo name for Point
接下来 8 bytes 是成员变量 m_x
(gdb) p &pObj->m_x
$3 = (double *) 0x100200518
C++的多重继承
需要用堆变量来支持C++的继承和多态的机制, 举例如下
class Printable
{
public:
virtual int print() = 0;
virtual ~Printable() {}
};
class Shape {
public:
Shape(): m_color(0) {}
virtual ~Shape() {}
int GetColor() {return m_color; };
void SetColor(int color) { m_color = color; };
virtual int draw() = 0;
virtual void dump() { printf("Shape::dump: color=%d", m_color);}
private:
int m_color;
};
class Circle: public Shape {
public:
Circle(double x, double y, double r): Shape(), m_center_x(x), m_center_y(y), m_radius(r) { SetColor(1);}
virtual int draw() { printf("Circle::draw: circle , color=%d\n", GetColor()); return 0; }
virtual void dump() { printf("Circle::dump: x=%f, y=%f, r=%f\n", m_center_x, m_center_y, m_radius);}
private:
double m_center_x;
double m_center_y;
double m_radius;
};
class Line: public Shape, public Printable {
public:
Line(double x1, double y1, double x2, double y2): Shape(),m_start_x(x1), m_start_y(y1), m_end_x(x2), m_end_y(y2) {SetColor(2);}
virtual int draw() { printf("Line::draw: line, color=%d\n", GetColor()); return 0;}
virtual void dump() { printf("Line::dump: x1=%f, y1=%f, x2=%f, y2=%f\n", m_start_x, m_start_y, m_end_x, m_end_y);}
virtual int print() { printf("Line::print\n"); return 0;}
private:
double m_start_x;
double m_start_y;
double m_end_x;
double m_end_y;
};
这里有四个类, Printable 是接口类, Circle 继承自 Shape, Line 继承自 Printable 和 Shape
Line* pLine = new Line(20.0, 30.0, 40.0, 60.0);
(gdb) p/a *pLine
$25 = {<Shape> = {_vptr$Shape = 0x1000021e8 <vtable for Line+16>, m_color = 0x2}, <Printable> = {_vptr$Printable = 0x100002220 <vtable for Line+72>}, m_start_x = 0x14, m_start_y = 0x1e,
m_end_x = 0x28, m_end_y = 0x3c}
Line size=56, 其中
- 一个对应于父类 Shape的 虚表, 8 bytes
- 一个对应于父类 Printable 的虚表, 8 bytes
- shape的一个成员变量 4 个字节, 8 bytes, 其中 4 bytes 是 padding
- class line自己有四个成员变量占了 8*4 = 32 个字节
故此, 8 + 8 + (4+4) + 8 * 4 = 56
在做指针转型的时候要注意
Line* pLine = new Line(20.0, 30.0, 40.0, 60.0);
Printable* pPrinter = (Printable*)pLine;
printf("pLine:%p, pPrinter: %p\n", pLine, pPrinter);
--- output ---
pLine:0x7fd283c04d00, pPrinter: 0x7fd283c04d10
注意这两个指针的值是不相等的, 最好用dynamic_cast来转型
Printable* pPrinter = dynamic_cast<Printable* >(pLine);