C++ 中的 sizeof()
【牛客题目1】在Visual C++ 和 Mingw64平台上,short a[100], sizeof(a) 返回什么?
A. 2 B. 4 C. 100 D. 200 E. 400
答案:D
Reference: https://www.nowcoder.com/test/question/done?tid=31013888&qid=1490#summary
解析:short int: 2个字节 【1.1】
sizeof 返回的数值表示的含义如下(单位字节)
- 数组 - 编译时分配的数组空间大小 【1.1】(特别地,若数组作为参数被传入函数中做sizeof()运算,则和指针运算没区别)【1.2】
- 指针 - 存储该指针所用的空间大小 (存储该指针的地址长度,是长整型,应该为4) 【2】
- 类型 - 该类型所占用的空间大小 【3】
- 对象 - 对象的实际占用空间大小 【4】
- 函数 - 函数的返回类型所占的空间大小。函数的返回类型不能是void【5】
【题目2-13】
1 char str[] = "Hello"; 2 char *p = str; 3 int n = 10; 4 sizeof(str) = 6; // H,e,l,l,o,\0 [1.1] 5 sizeof(p) = 4; // [2] 6 sizeof(n) = 4; // [3] 7 void Func(char str[10]){ 8 sizeof(str) = 4; // [1.1] 9 } 10 void *p = malloc(100); 11 sizeof(p) = 4; // [2]
【题目2-14】使用sizeof()计算类对象所占空间大小, 结构体和类中的字符串对齐原则
1 #include <iostream> 2 using namespace std; 3 4 class A{ 5 public: 6 int i; //4 7 }; 8 9 class B{ 10 public: 11 char ch; //1 12 }; 13 14 class C{ 15 public: 16 int i; //4+1 ---> 对齐 4+4 = 8 17 short j; 18 }; 19 20 class D{ 21 public: 22 int i; // 4+2+1 ---> 对齐 4+2+1+1 = 8 23 short j; 24 char ch; 25 }; 26 27 class E{ 28 public: 29 int i; // 4+4+2+1+1 = 12 30 int ii; 31 short j; 32 char ch; 33 char chr; 34 }; 35 36 class F{ 37 public: 38 int i; // 4+4+4+2+1+1 = 16 39 int ii; 40 int iii; 41 short j; 42 char ch; 43 char chr; 44 }; 45 46 int main(){ 47 cout<<sizeof(A)<<' '<<sizeof(B)<<' '<<sizeof(C)<<' '<<sizeof(D)<<' '<<sizeof(E)<<' '<<sizeof(F)<<endl; 48 }
答案:4 1 8 8 12 16
为什么要字节对齐?一般情况下,如果不按照适合平台要求对数据存放进行对齐,会给存取效率带来损失。例如,有些平台每次读都是从偶地址开始,如果一个 int 型存放在偶地址开始的地方,那么一个读周期就可以读出;而如果存放在奇地址开始的地方,可能会需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该 int 型数据。显然,在读取效率上下降很多。
字节对齐原则.
(1)结构体变量的首地址能够被其最宽基本类型成员的大小所整除
(2)结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,编译器会在成员之间加上填充字节 (internal adding)
(3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,编译器会在最末一个成员之后填充字节(trailing padding)
【题目2-15】使用 sizeof() 计算含有虚函数的类对象的空间大小
#include <iostream> using namespace std; class Base{ public: Base(int x):a(x){} void print(){cout<<"base:"<<endl;} private: int a; }; class Derived:public Base{ public: Derived(int x):Base(x-1),b(x){} void print(){cout<<"derived"<<endl;} private: int b; }; class A{ public: A(int x):a(x){} virtual void print(){cout<<"A"<<endl;} private: int a; }; class B: public A{ public: B(int x):A(x-1),b(x){} virtual void print(){cout<<"B"<<endl;} private: int b; }; int main(){ Base obj1(1); Derived obj2(2); A obj3(3); B obj4(4); cout<<sizeof(obj1)<<' '<<sizeof(obj2)<<' '<<sizeof(obj3)<<' '<<sizeof(obj4)<<endl; return 0; }
答案: 4 8 8 12
类的普通成员函数不占用内存,只有虚函数占用一个指针大小的内存,原因是系统多用一个指针维护这个类的虚函数表,并且注意这个虚函数无论含有多少项 (类中含有多少个虚函数)都不会再影响类的大小。
【题目2-16】使用sizeof()计算虚拟继承的类对象的空间大小
1 #include<iostream> 2 using namespace std; 3 class A{}; 4 class B{}; 5 class C:public A, public B{}; 6 class D:virtual public A{}; 7 class E:virtual public A,virtual public B{}; 8 class F{ 9 public: 10 int a; 11 static int b; 12 }; 13 int F::b = 10; 14 int main(){ 15 cout<<sizeof(A)<<" "<<sizeof(B)<<" "<<sizeof(C)<<" "<<sizeof(D)<<" "<<sizeof(E)<<" "<<sizeof(F)<<endl; 16 return 0; 17 }
运行结果:1 1 1 4 8 4
Line 3, 4 -> A, B 是空类,编译器会安插一个 char 给空类,用来标记它的每一个对象。因此它的大小为1
Line 5 -> 多类继承,大小为1
Line 6 -> 虚拟继承,编译器为该类安插一个指向父类的指针,编译器不会再安插 char,指针大小为4
Line 7 -> 虚拟继承 A和B, 有指向父类A和父类B的指针,加起来大小为8个字节
Line 8 -> F 含有一个静态成员变量,这个静态成员的空间不在类的实例中,而是像全局变量一样在静态存储区中,被每一个类的实例共享,因此其大小为 4 个字节。
【题目2-17】sizeof 与 strlen 有哪些区别
. sizeof 是操作符,strlen 是函数
. sizeof 操作符的结果是 size_t, 在头文件中用 typedef 为 unsigned int 类, 该类型保证能容纳实现所简历的最大对象的字节大小
. sizeof 可以用类型做参数, strlen 只能用 char* 做参数,且必须是以'\0'结尾的
. 数组做 sizeof 的参数不推户啊,传递给 strlen 就退化为指针了
. 大部分编译程序在编译的时候 sizeof 就被计算过了,这就是 sizeof() 可以用来定义数组维数的原因。strlen 的结果要在运行的时候才能计算出来,它用计算字符串的长度,不是类型占内存的大小
. 在字符串数组的长度上有差别, 如
1 char str[20]="0123456789"; 2 int a = strlen(str); // 10 不包含 '\0' 3 int b = sizeof(str); // 20 数组大小
1 char *ss = "0123456789"; 2 int a = sizeof(ss); // 4 指针大小 3 int b = strlen(ss); // 10
【题目2-19】找错 - 使用 strlen() 函数代替 sizeof 计算字符串长度
1 #include <iostream> 2 #include <string.h> 3 using namespace std; 4 5 void UpperCase(char str[]){ 6 for(size_t i=0;i<sizeof(str)/sizeof(str[0]);++i){ 7 if('a'<=str[i] && str[i]<='z') 8 str[i]-= ('a'-'A'); 9 } 10 } 11 12 int main(){ 13 char str[] = "aBcDe"; 14 cout<<sizeof(str)/sizeof(str[0])<<endl; 15 UpperCase(str); 16 cout<<str<<endl; 17 return 0; 18 }
Line 5 函数作用,把小写字母换成大写, 但是 sizeof(str) = 4 因为 char str[] 作为形参,已退化为指针
LIne 14 中 sizeof(str)/sizeof(str[0]) 要比 strlen(str) 多1,存储 '\0'
因此,正确代码应该如下
1 #include <iostream> 2 #include <string.h> 3 using namespace std; 4 5 void UpperCase(char str[]){ 6 for(size_t i=0;i<strlen(str);++i){ 7 if('a'<=str[i] && str[i]<='z') 8 str[i]-= ('a'-'A'); 9 } 10 } 11 12 int main(){ 13 char str[] = "aBcDe"; 14 cout<<strlen(str)<<endl; 15 UpperCase(str); 16 cout<<str<<endl; 17 return 0; 18 }
【题目2-20】使用sizeof()计算联合体大小
1 #include <iostream> 2 using namespace std; 3 4 union u{ 5 double a; 6 int b; 7 }; 8 9 union u2{ 10 char a[13]; 11 int b; 12 }; 13 14 union u3{ 15 char a[13]; 16 char b; 17 }; 18 19 int main(){ 20 cout<<sizeof(u)<<' '<<sizeof(u2)<<' '<<sizeof(u2)<<endl; 21 return 0; 22 }
运行结果:8 16 13
联合体的大小取决于它所有的成员中占用最大的一个成员的大小。并且对于复合数据类型,如union, struct, class 的对齐方法是为成员中最大的成员对齐方式。如
对于 u 来说,大小就是最大的double 类型成员a了,sizeof(a)=8
对于u2来说,最大的空间是 char[13] 类型的数组。这里要注意,由于它的另一个成员 int b 的存在,u2 的对齐方式变成 4, 也就是说, u2 的大小必须在 4 的对齐上,所以占用的空间变为最接近 13 的 16。
对于 u3来说,最大的空间是 char[13] 类型的数组,即 sizeof(u3) = 13
【题目2-21】使用 #pragma pack 可以改变编译器对齐方式
1 #include <iostream> 2 #pragma pack(2) 3 using namespace std; 4 union u{ 5 char buf[9]; 6 int a; 7 }; 8 9 int main(){ 10 cout<<sizeof(u)<<endl; 11 return 0; 12 }
运行结果:10
使用 #pragma pack(x) 可以改变编译器的对齐方式。C++固有类型的对齐编译器与自身大小中较小的一个。
1 #include <iostream> 2 #pragma pack(1) 3 using namespace std; 4 struct test{ 5 char c; 6 short s1; 7 short s2; 8 int i; 9 }; 10 11 int main(){ 12 cout<<sizeof(test)<<endl; 13 return 0; 14 }
运行结果:9