问题
声明一个pulse对象
class Pulse
{
public:
void Update()
{
if(!updated_)
{
// ...
}
}
private:
double time_in_;
double time_out_;
bool updated_;
// ...
}
编译一个pulse对象的单元测试,debug正常通过,realse版本报错
error: ‘*((void*)& pulse +16)’ may be used uninitialized in this function [-Werror=maybe-uninitialized]
if (!updated_)
^~~~~~~~
note: ‘*((void*)& pulse +16)’ was declared here
Pulse pulse;
分析
看报错的意思应该是updated_
变量没有赋初值,导致报错maybe-uninitialized
,修复很容易bool updated_ = false
既可。
但我决定深挖晦涩的报错*((void*)& pulse +16)
是怎么来的?
首先明确一点:计算class的sizeof时成员函数不参与统计。例如:
class A
{
private:
double x;
double y;
bool z;
};
// sizeof(A) = 24
class B
{
public:
bool init();
int get();
private:
double x;
double y;
bool z;
};
// sizeof(B) = 24
其中的原理在C++有关class内部的static关键字理解中有解释
知道了class计算sizeof时可以看作没有方法的struct就不难理解*((void*)& pulse +16)
的来历,
对于行如class.member
的语法糖,底层实现会直接将指针偏移到member
的起始位置。
struct Pulse
{
double time_in_; // offset = 0
double time_out_; // offset = sizeof(double) = 8
bool updated_; // offset = sizeof(double) + sizeof(double) = 16
};
因此以下几种表达等效
```cpp
pulse.updated_
(&pulse)->updated_
&(pulse.updated_) = &pulse + 16
结论:class的成员变量按顺序在内存中连续排列,调用某一变量实则通过计算offset进行内存偏移。
扩展
如何计算具体偏移量?
c++提供了offsetof
方法,严格讲offsetof
是一个宏而不是函数,实现略显晦涩
#define offsetof(st, m) \
((size_t)&(((st *)0)->m))
逐个击破,将计算成员位置的初始地址设为0(nullptr)
即使nullptr并不包含m成员
此时将0强行cast成st*指针(st *)0
进而访问m变量((st *)0)->m
结果取地址&(((st *)0)->m)
再次转换为整数(size_t)&(((st *)0)->m)
由于整个过程中只涉及地址转换并未发生对m内存实质性访问,结果不报错。