美柑の部屋

涙は見せないと誓った。

Loading…

C++中sizeof运算符的简要总结

sizeof是C++中的一个运算符(不是一个函数),它的作用被表述为:The sizeof keyword gives the amount of storage, in bytes, associated with a variable or a type (including aggregate types). This keyword returns a value of type size_t. 简而言之,就是返回一个变量或类型在内存中所占的字节数。但正是这么一个看似简单的运算符,却成为了面试中的热门考点。这说明关于这个运算符其实有很多需要注意的地方。在这篇文章中,我就对这个运算符进行一个简要的总结。

一、指针,数组与sizeof

面试的时候,面试官经常将sizeof运算符与char*类型字符串或者char[]类型字符串综合起来考察。比如下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void getSize(char var[]){
    cout << sizeof(var) << endl;
}

int main(){
    char* str1 = "0123456789";
    cout << sizeof(str1) << endl;

    char str2[] = "0123456789";
    cout << sizeof(str2) << endl;

    char str3[100] = "0123456789";
    cout << sizeof(str3) << endl;

    getSize(str3);
}

main函数中,我们依次定义了三个内容相似的字符串。首先对于str1,它是一个指针,指针大小在32位机器中恒定为4,所以输出为4;str2是字符数组,没有预定义大小,所以其大小由存储的字符串长度决定,字符串的长度为10,加上末尾的字符串结束符\0,共11个字符,所以输出11;str3是预定义大小的字符数组,不管内部存储的字符串长度是多少,其大小都是预定义的大小100,所以输出100。而对于函数getSize,我们需要了解的是,在C++中,任何数组作为函数参数传递进去后都会退化成指针,所以输出为4。
综上所述 ,输出依次为4、11、100和4。

二、structclass在内存中的数据对齐

数据对齐这件事,我们平时好像不太关注,但是一提到sizeof操作符,就会不可避免地涉及到内存对齐的机制。之所以要进行内存对齐,是出于如下考虑:对于n字节的元素,它的首地址能被n整除时,才能在程序执行时获得最好的性能。对于类或者结构体,为了保证这一点,就需要使其内部的成员变量按照最长的成员变量的长度去对齐。举个例子:

1
2
3
4
5
6
7
8
9
10
11
struct A{
    char a;
    short b;
    int c;
};

struct B{
    char a;
    int c;
    short b;
};

在上面的代码中,我们定义了两个结构体AB,它们的成员变量内容相同,但是顺序不同。通过观察发现,AB中最长的数据元素类型均为int,占4字节,所以两个结构体的成员变量均要以4字节为单位进行对齐。简而言之,就是成员变量按照顺序放入长度为4字节的位置中,如果能放得下,就继续往里放;如果放不下,就需要开辟一个新的位置。所以,AB数据对齐的情况如下:

1
2
3
4
5
6
7
8
9
10
A的数据对齐情况,按照声明的顺序,应该依次放置a,b,c:
|-a-|---|-b-|-b-|
|-c-|-c-|-c-|-c-|
所以sizeof(A)应该等于8。

B的数据对齐情况,按照声明的顺序,应该依次放置a,c,b:
|-a-|---|---|---|
|-c-|-c-|-c-|-c-|
|-b-|-b-|---|---|
所以sizeof(B)应该等于12。

而对于联合体union,相比于struct在内存上的顺序式组织,union是重叠式组织的,所以对一个union使用sizeof返回的是其最长成员的长度。

三、类和sizeof运算符

3.1 关于空类的继承:

1
2
3
4
5
6
7
8
9
class A{
};

class B{
    void func();
};

class C : public A, public B{
};

对一个空类使用sizeof结果为1,因为即使是空类,也需要在内存中占用一定位置,否则就不可寻址了,这是编译器做的一步特殊处理。所以sizeof(A)的结果为1。对于一个只有普通函数,没有成员变量的类使用sizeof,结果仍为1,因为类的普通函数不会占用类的每个实例的空间。所以sizeof(B)的结果为1。对一个多重继承的空类使用sizeof,输出仍然为1。所以sizeof(C)的结果为1。

3.2 关于有成员变量的类的继承:

1
2
3
4
5
6
7
8
9
10
11
class A{
    int a;
};

class B{
    int b;
};

class C : public A, public B{
    int c;
};

在上面这种情况下,C的实例不仅要存储自身定义的成员变量,还要存储其父类定义的成员变量。所以sizeof(C)的结果为12。

3.3 关于有虚函数的类:

1
2
3
4
5
6
7
8
9
10
11
class A{
    virtual int func();
};

class B{
    virtual int func1();
    virtual int func2();
};

class C : public A, public B{
};

有虚函数的类会保存一个指向虚表的指针,在32位机器上指针的大小为4,所以sizeof(A)的结果为4。一个类无论有多少个虚函数,都只有一张虚表,所以sizeof(B)的结果也是4。而如果一个类有多重继承,那么它就会保存多张虚表。在上面的例子中,C的实例保存了两张虚表,每张虚表的大小是4,所以sizeof(C)的结果为8。

3.4 关于有static成员变量的类:

1
2
3
4
class A{
    int a;
    static int b;
};

静态成员变量是存放在全局数据区的,而sizeof运算符只计算栈中分配的大小,所以静态成员变量不会计算在内。sizeof(A)的结果为4。

四、sizeofstrlen的区别与联系

  • 首先,sizeof是运算符,而strlen是函数。
  • 其次,sizeof可以作用于变量名、类型名、表达式或者函数;而strlen只能使用char类型字符串做参数,且必须是以\0结尾。对于一个char数组,sizeof返回的是该数组的总大小,而strlen返回的是该数组实际对应的字符串的长度(末尾的\0不计算在内);对于一个char*字符串,sizeof在32位机器上恒定返回4,而strlen仍然返回字符串的实际长度。
  • 最后,大部分程序在编译阶段做类型检测的时候就把sizeof的值计算过了(参考如下代码),而strlen必须是运行时才会计算出来。
1
2
3
4
5
6
7
8
9
10
11
int x = 0;

int add(){
    return x++;
}

int main(){
    sizeof(add());
    sizeof(x = 6);
    int a[sizeof x];
}

上述代码中,在main函数的第一行,sizeof(add())会在编译时刻被替换为sizeof(int)然后得出结果4,不需要在运行时调用add函数。同理,x=6这句表达式也是不被执行的。所以在这两个表达式执行完成后,x的值仍然为0。另外,由于在编译时刻已经确定了值,所以sizeof的结果可以被用来作为定义静态数组的维数。

评论