-复合式数据类型的大小:
很久前看过有计算机考题如下:
struct tcs
{
char a, b, c;
};
则sizeof(tcs) == ______
编程时发现实际情况与答案不同:
如果你写了一个这样的结构体,编译运行,调试时应该会显示结果为4。这是因为有对齐到WORD边界的规定(答案就是4)。
现在在结构体上下加几句,结果如下:
#pragma pack(push)
#pragma pack(1)
struct tcs
{
char a, b, c;
};
#pragma pack(pop)
编译运行,调试时会显示结果为3。(答案?...)
如果自己定义这种结构体指针,还要加关键字UNALIGNED:
#pragma pack(push)
#pragma pack(1)
typedef struct _tcs
{
char a, b, c;
} tcs, UNALIGNED * ptcs;
#pragma pack(pop)
现在回到原问题,我们知道一个BYTE占用一个字节,8位:
现在在结构体上下加几句,结果如下:
#pragma pack(push)
#pragma pack(1)
struct tcs
{
DWORD a: 8;
DWORD b: 8;
DWORD c: 8;
};
#pragma pack(pop)
编译运行,调试时又会显示结果为4。($@$%$#?...)
还要注意代码sizeof(tcs::a)只会导致编译期错误。不能求解位域成员的sizeof运算。
所以,看来还是“实际一点”...
收集了几篇专门讲对齐的文章。文章可能比较长。其实只需要注意看绿色的部分就可以了,不清楚的话再看上下文~~
http://blog.csdn.net/unituniverse2/article/details/6630597
- 注意虚类和多态类还会附加有隐含成员,不要认为结构的大小等于成员大小加对齐字节之和。
- 注意方法和静态属性成员是不参与决定结构本身的大小的。
- C++支持编译期的成员权限关键字。但要注意标准给出了可选的成员分组重排规则。是否支持,支持到什么程度,由编译器厂商自己决定。我们一定要注意该规则带来的可能性影响(现在微软的编译器不支持,我们不但不该不当回事,反而更要注意):
struct FH
{
public:
BYTE Knw[3];
public:
DWORD cbSize;
DWORD mm1;
BYTE trl;
};
有些编译器会把它修改成(微软的编译器还不支持):
struct FH
{
public:
DWORD cbSize;
DWORD mm1;
BYTE trl;
public:
BYTE Knw[3];
};
源代码上是看不到这种修改的。
- 空类型的大小
假设你定义了一个结构体:
struct Empty {};
这个结构体的大小是多少?不一定!
当此结构体单独用于定义变量或作为其他结构体或类的成员时,有可能是0、1、2、sizeof(void*)或其他什么值,视所用的编译器而定。有个对于编译器厂商的建议是:尽量不要用0作为这种情况下的对象的大小。因为如果是0,就有可能出现这样的情况:
Empty zeroarr[10];
这里zeroarr[0]、zeroarr[1]、...全部共用了同一个地址。这样你没法区分不同的元素了:
if(zeroarr + i != zeroarr)
...
else
...
但是,标准规定了在单继承情况下当无数据成员结构体或类在被继承后,其直接子类的大小将不考虑这个类/结构体的大小,如果子类包含成员,这些成员所占总大小相当于没有继承于此类/结构体而单独声明的大小。成员的起始地址也和不继承时相同,即从子类的入口地址开始算起。换句话说,被继承后的空基类在继承类内部的大小为零。
- 我们该认为结构体和类的用途是不同的,而且结构体最好不要带虚成员,不要从虚类、多态类继承。虽然C++标准认为定义一个结构,写struct还是class都一样。
- 将构造函数声明成保护或私有可以防止类直接被用来定义对象。
- 带指针、引用成员的类或结构体要避免默认拷贝构造函数、默认拷贝赋值函数的问题。应该显示地实现他们,或声明他们为私有来禁用他们。
- 声明与定义混淆的解决方法:
模板类型参数问题:
假设你声明了一个模板
template <typename T> struct MssTyp { typedef T typeID; };
当你试图用typeID作为其他的模板的模板类型参数或函数返回值类型或类型转换类型或用来声明临时对象...凡是这个MssTyp的特化不完全的地方,全部都加上typename。例如
当你想在一个模板函数里自定义一个变量a:
template <typename T> void foo() { typename MssTyp<T>::type a; }
理由是这个type在某个MssTyp特化版本里可能是类型,在另一个里却可能是数据或函数成员。你可以搜索nested dependent type name获取相关的信息。
类名做临时对象,用来初始化另一个类对象的问题:
假设有下面的代码:
class A { public: A(void){}; }; class B { public: B(const A &){}; }; ... B o1(A());
编译时你会收到类似这样的警告:'B o1(A (__cdecl *)(void));': prototyped function not called (was a variable definition intended?)看了后你会问,什么,怎么变成函数了?这个警告和你的目标看似根本不沾边。如果你还不服气,可以试着下断点在B o1(A());这一行,运行时发现这一行根本就没有出现在执行体中!被编译器直接无视...
可以换个角度看一下:既然编译后“消失”,有可能是优化掉的,也有可能是当作声明处理了。如果属于前一种情况,经常会改变程序的逻辑,所以编译器很可能不会那样做。另一种情况倒有可能。如果o1是一个函数名,那这一行就可以解释为某个名为o1函数的前向声明了。解决的方法是写成这样来避免:
B o1( (A()) );
注意如果真的是想声明一个名为o1的函数,显然这样的声明是非法的。
用这种方法写出的代码样子丑陋但也没别的办法了。也许你会说我不如这样写:
A a; B o1(a);
这样子写只是“好看”,但是和前面的代码是完全不同的东西:首先用于初始化o1的对象生命期被延续到了比o1更长;另一个也是最致命的是,这里的a是一个左值。如果B的A初始化构造函数重载了一个右值版本的A,比如B(A &&);那么这两块代码的o1将分别调用不同的A初始化构造函数了。