临近过年,进程调度就先暂停,因为进程调度涉及的东西比较多,也不能都带回老家,所以过年这段时间准备卷《深入探索c++对象模型》。这本书越看越让人绝望,好难啊。所以就一边看一遍做笔记,分析,希望能看的懂。加油,哈哈哈。
1.1 空的类所占大小
从c转到c++,都比较喜欢做对比,特别是学习对象的时候,就喜欢比较struct和class了,所以这一次我们也来讨论一下struct数据占用的大小。
1.1.1 c语言中空的struct占用的大小
在写c语言的时候,我们经常使用struct,但是都没有去研究空的struct所占的大小,下面我们来看看
上代码:
#include <stdio.h>
struct A
{
//char a;
};
int main(int argc, char **argv)
{
printf("sizeof(struct A) = %ld\n", sizeof(struct A));
struct A a;
printf("sizeof(a) = %ld &a = %p\n", sizeof(a), &a);
*(char *)(&a) = 13;
printf("*(char *)&a = %d\n", *(char *)(&a));
struct A b;
printf("sizeof(b) = %ld &b = %p\n", sizeof(b), &b);
*(char *)(&b) = 1;
printf("*(char *)&b = %d\n", *(char *)(&b));
struct A c;
printf("sizeof(c) = %ld &c = %p\n", sizeof(c), &c);
*(char *)(&c) = 55;
printf("*(char *)&c = %d\n", *(char *)(&c));
return 0;
}
编译,运行:
sizeof(struct A) = 0
sizeof(a) = 0 &a = 0x7ffc10a16895
*(char *)&a = 13
sizeof(b) = 0 &b = 0x7ffc10a16896
*(char *)&b = 1
sizeof(c) = 0 &c = 0x7ffc10a16897
*(char *)&c = 55
差一点就被c语言的语法糖给骗了,sizeof可能做了处理,毕竟a,b,c都是实实在在的变量,并且也分配了地址,竟然分配了地址那就肯定有空间的吧,所以我就用指针指向这个空间,并且修改了值,编译输出发现真的是可以改变这个值,原来底层是这样的。
1.1.2 c++中空的struct占用大小
看了c语言的,我们也看看c++的如何:
#include <iostream>
#include <stdio.h>
using namespace std;
struct A
{
//char a;
};
int main(int argc, char **argv)
{
cout << "sizeof(A) = " << sizeof(A) << endl;
A a; // c++中的struct是可以省略struct
cout << "sizeof(a) = " << sizeof(a) << " &a = " << &a << endl;
*reinterpret_cast<char *>(&a) = 45;
printf("*reinterpret_cast<char *>(&a) = %d\n", *reinterpret_cast<char *>(&a));
A b;
cout << "sizeof(b) = " << sizeof(b) << " &b = " << &b << endl;
*(char *)(&b) = 122;
printf("*(char *)(&b) = %d\n", *(char *)(&b));
A c;
cout << "sizeof(c) = " << sizeof(c) << " &c = " << &c << endl;
cout << "*(char *)&c = " << *(char *)(&c) << endl;
return 0;
}
编译运行:
root@ubuntu:~/c++_mode/01# ./test_struct_cpp
sizeof(A) = 1
sizeof(a) = 1 &a = 0x7ffdb8cd3335
*reinterpret_cast<char *>(&a) = 45
sizeof(b) = 1 &b = 0x7ffdb8cd3336
*(char *)(&b) = 122
sizeof(c) = 1 &c = 0x7ffdb8cd3337
*(char *)&c =
root@ubuntu:~/c++_mode/01#
还是printf函数无敌啊,printf函数实现之后再解释把,意思就是把这块内存按指定格式输出,cout不支持这种格式,所以就没输出吧,应该是这样的。
这个sizeof的值也是1个字节,通过指针指向这个地址,并且修改里面的值,都是有效的,也符合我们的预期。
1.1.3 空的class所占大小
经过上面的两轮测试,猜测这个空的class也是占用一个字节,然后这个字节也是可以操作的,我们就来用代码来测试一波:
#include <iostream>
#include <stdio.h>
using namespace std;
class A
{
};
int main(int argc, char **argv)
{
cout << "sizeof(A) = " << sizeof(A) << endl;
A a; // c++中的struct是可以省略struct
cout << "sizeof(a) = " << sizeof(a) << " &a = " << &a << endl;
*reinterpret_cast<char *>(&a) = 45;
printf("*reinterpret_cast<char *>(&a) = %d\n", *reinterpret_cast<char *>(&a));
A b;
cout << "sizeof(b) = " << sizeof(b) << " &b = " << &b << endl;
*(char *)(&b) = 122;
printf("*(char *)(&b) = %d\n", *(char *)(&b));
A c;
cout << "sizeof(c) = " << sizeof(c) << " &c = " << &c << endl;
cout << "*(char *)&c = " << *(char *)(&c) << endl;
return 0;
}
我们来看看编译运行后的结果:
root@ubuntu:~/c++_mode/01# ./test_class
sizeof(A) = 1
sizeof(a) = 1 &a = 0x7ffe9c30a1e5
*reinterpret_cast<char *>(&a) = 45
sizeof(b) = 1 &b = 0x7ffe9c30a1e6
*(char *)(&b) = 122
sizeof(c) = 1 &c = 0x7ffe9c30a1e7
*(char *)&c =
root@ubuntu:~/c++_mode/01#
跟我们猜想的一样,哈哈哈。
这样面试的时候,碰到这个问题就可以大胆的说,空类大小是一个字节。
1.2 成员变量占的空间
上面介绍了空的类占用的大小,接下来我们就来看看在类中或结构体中定义成员变量所占的空间。
1.2.1 c语言结构体变量
#include <stdio.h>
struct A
{
char a; // 4
int b; // 4
float c; // 8
double d; // 8
};
int main(int argc, char **argv)
{
printf("sizeof(struct A) = %ld %ld %ld\n", sizeof(struct A), sizeof(int), sizeof(float));
struct A a;
printf("sizeof(a) = %ld &a = %p\n", sizeof(a), &a);
printf("&a = %p, &b = %p, &c = %p &d = %p\n", &(a.a), &(a.b), &(a.c), &(a.d));
struct A b;
printf("sizeof(b) = %ld &b = %p\n", sizeof(b), &b);
printf("&a = %p, &b = %p, &c = %p &d = %p\n", &(b.a), &(b.b), &(b.c), &(b.d));
return 0;
}
编译运行:
root@ubuntu:~/c++_mode/01# ./test_struct
sizeof(struct A) = 24 4 4
sizeof(a) = 24 &a = 0x7ffdaa2ba8b0
&a = 0x7ffdaa2ba8b0, &b = 0x7ffdaa2ba8b4, &c = 0x7ffdaa2ba8b8 &d = 0x7ffdaa2ba8c0
sizeof(b) = 24 &b = 0x7ffdaa2ba8d0
&a = 0x7ffdaa2ba8d0, &b = 0x7ffdaa2ba8d4, &c = 0x7ffdaa2ba8d8 &d = 0x7ffdaa2ba8e0
root@ubuntu:~/c++_mode/01#
24个字节,还是很符合的,有人问了,为啥不是17个字节,那是因为cpu为了提高访问速度,而做了内存对齐,我是64位的系统,所以是8字节对齐,这样也能保证一个int能在一个时钟周期就能读到变量的值。
因为内存对齐,会把char后面的3个字节补0,然后从第4个字节开始才是int b,这样b是可以一个时钟周期就能访问到,然后float被对齐成8个字节了,double还是原来的8个字节。
struct A b;也再次对齐,所以起始地址是0x7ffdaa2ba8d0。
这是因为在现在计算中,内存不是那么的紧缺了,所以编译器就会做一些适当的优化,来提高cpu的访问。
1.2.2 c++结构体变量
这个就不写了吧,跟c语言的一模一样,就把代码贴上来?凑凑字数,哈哈。
#include <iostream>
#include <stdio.h>
using namespace std;
struct A
{
char a; // 4
int b; // 4
float c; // 8
double d; // 8
};
int main(int argc, char **argv)
{
printf("sizeof(A) = %ld %ld %ld\n", sizeof(A), sizeof(int), sizeof(float));
A a;
printf("sizeof(a) = %ld &a = %p\n", sizeof(a), &a);
printf("&a = %p, &b = %p, &c = %p &d = %p\n", &(a.a), &(a.b), &(a.c), &(a.d));
A b;
printf("sizeof(b) = %ld &b = %p\n", sizeof(b), &b);
printf("&a = %p, &b = %p, &c = %p &d = %p\n", &(b.a), &(b.b), &(b.c), &(b.d));
return 0;
}
代码都贴了,运行结果也贴出来吧。
root@ubuntu:~/c++_mode/01# ./test_struct_cpp
sizeof(A) = 24 4 4
sizeof(a) = 24 &a = 0x7ffd4a2b02a0
&a = 0x7ffd4a2b02a0, &b = 0x7ffd4a2b02a4, &c = 0x7ffd4a2b02a8 &d = 0x7ffd4a2b02b0
sizeof(b) = 24 &b = 0x7ffd4a2b02c0
&a = 0x7ffd4a2b02c0, &b = 0x7ffd4a2b02c4, &c = 0x7ffd4a2b02c8 &d = 0x7ffd4a2b02d0
root@ubuntu:~/c++_mode/01#
没什么好分析的。
1.2.3 class成员变量
其实class也是一样的,直接上代码吧。
#include <iostream>
#include <stdio.h>
using namespace std;
class A
{
public:
char a; // 4
int b; // 4
float c; // 8
double d; // 8
};
int main(int argc, char **argv)
{
printf("sizeof(A) = %ld %ld %ld\n", sizeof(A), sizeof(int), sizeof(float));
A a;
printf("sizeof(a) = %ld &a = %p\n", sizeof(a), &a);
printf("&a = %p, &b = %p, &c = %p &d = %p\n", &(a.a), &(a.b), &(a.c), &(a.d));
A b;
printf("sizeof(b) = %ld &b = %p\n", sizeof(b), &b);
printf("&a = %p, &b = %p, &c = %p &d = %p\n", &(b.a), &(b.b), &(b.c), &(b.d));
return 0;
}
一模一样的代码,肯定是在凑字数,水文。。。
运行结果(水也要水下去,没办法):
root@ubuntu:~/c++_mode/01# ./test_class
sizeof(A) = 24 4 4
sizeof(a) = 24 &a = 0x7ffef948b970
&a = 0x7ffef948b970, &b = 0x7ffef948b974, &c = 0x7ffef948b978 &d = 0x7ffef948b980
sizeof(b) = 24 &b = 0x7ffef948b990
&a = 0x7ffef948b990, &b = 0x7ffef948b994, &c = 0x7ffef948b998 &d = 0x7ffef948b9a0
root@ubuntu:~/c++_mode/01#
这个就不解释了。。。。。。。。。
1.3 静态变量占的空间
一看到静态变量占的空间,我就在想结构体中可以有static修饰的变量么?好像没有这个印象,那我们就来体验一波。
1.3.1 c语言结构体是否支持静态变量
root@ubuntu:~/c++_mode/01# gcc test_struct.c -o test_struct
test_struct.c:9:5: error: expected specifier-qualifier-list before ‘static’
static int e;
^
root@ubuntu:~/c++_mode/01#
恭喜大家,不支持。(少了不少字哈哈)
1.3.2 c++结构体静态变量
那c++结构体中能支持么?感觉是可以的,因为c++的strut跟class差别就只有权限了。那我们来看看:
#include <iostream>
#include <stdio.h>
using namespace std;
struct A
{
char a; // 4
int b; // 4
float c; // 8
double d; // 8
static int e;
};
int A::e = 0; // 这个重点,需要初始化才能使用
int main(int argc, char **argv)
{
printf("sizeof(A) = %ld %ld %ld\n", sizeof(A), sizeof(int), sizeof(float));
A a;
printf("sizeof(a) = %ld &a = %p\n", sizeof(a), &a);
printf("&a = %p, &b = %p, &c = %p &d = %p &e = %p\n", &(a.a), &(a.b), &(a.c), &(a.d), &(A::e));
A b;
printf("sizeof(b) = %ld &b = %p\n", sizeof(b), &b);
printf("&a = %p, &b = %p, &c = %p &d = %p &e = %p\n", &(b.a), &(b.b), &(b.c), &(b.d), &(A::e));
return 0;
}
想不到c++的struct还真可以定义static成员变量,并且需要初始化后才能使用,不初始化是不能使用的,这个要记住。
编译运行:
root@ubuntu:~/c++_mode/01# ./test_struct_cpp
sizeof(A) = 24 4 4
sizeof(a) = 24 &a = 0x7ffeceac8ea0
&a = 0x7ffeceac8ea0, &b = 0x7ffeceac8ea4, &c = 0x7ffeceac8ea8 &d = 0x7ffeceac8eb0 &e = 0x60105c
sizeof(b) = 24 &b = 0x7ffeceac8ec0
&a = 0x7ffeceac8ec0, &b = 0x7ffeceac8ec4, &c = 0x7ffeceac8ec8 &d = 0x7ffeceac8ed0 &e = 0x60105c
root@ubuntu:~/c++_mode/01#
发现结构体的大小并没有改变,还是24个字节,并且我们打印出来的值也不是在栈区,而是在数据区,0x60105c这地址就是数据区的部分,如果不知道的,可以看我之前的重学计算机的文章,所以这个静态变量是不算在对象空间内的。
那c++编译器是怎么把这个A::e这个变量名称存储起来的?
这个也可以看我的重学计算机里的符号表分析,其实c++编译器会把这个变量名加上修饰,下面是我反编译出来的结果:
68: 000000000060105c 4 OBJECT GLOBAL DEFAULT 26 _ZN1A1eE
我是用g++来编译的,在gcc的基本c++名称修饰方法:
- 所有符号都以"_Z"开头。
- 对于在名称空间或在类里面,后面紧跟"N".
- 然后就是各个名称空间和类的名字,每个名字前是名字字符串长度。如:1A。长度为1,类名为A
- 最后以"E"结尾。
- 如果是函数的话,E后面会跟函数的参数列表。
所以这一串字符_ZN1A1eE就能解释的通了,学习真是一个网啊。
1.3.3 class的静态变量
class和struct其实是差不多的拉。这个就不写了,写太多也不好看,运行结果就跟1.3.2的一样。
符号保存也在1.3.2的一样,在c++中class跟struct真的区别不大。
1.4 成员函数占的空间
终于把成员变量讲完了,我们来看看成员函数。
1.4.1 c语言结构体不支持成员函数
毋庸置疑c语言结构体是不支持成员函数的,但是我们可以用函数指针来代替成员函数,这也是c实现面向对象的方法。
下面看代码:
#include <stdio.h>
struct A
{
char a; // 4
int b; // 4
float c; // 8
double d; // 8
//static int e;
int (*func1)(int a); // 8
int (*func2)(int a); // 8
};
int main(int argc, char **argv)
{
printf("sizeof(struct A) = %ld %ld %ld\n", sizeof(struct A), sizeof(int), sizeof(float));
struct A a;
printf("sizeof(a) = %ld &a = %p\n", sizeof(a), &a);
printf("&a = %p, &b = %p, &c = %p &d = %p\n", &(a.a), &(a.b), &(a.c), &(a.d));
struct A b;
printf("sizeof(b) = %ld &b = %p\n", sizeof(b), &b);
printf("&a = %p, &b = %p, &c = %p &d = %p\n", &(b.a), &(b.b), &(b.c), &(b.d));
return 0;
}
编译运行:
sizeof(struct A) = 40 4 4
sizeof(a) = 40 &a = 0x7ffecac8f830
&a = 0x7ffecac8f830, &b = 0x7ffecac8f834, &c = 0x7ffecac8f838 &d = 0x7ffecac8f840
sizeof(b) = 40 &b = 0x7ffecac8f860
&a = 0x7ffecac8f860, &b = 0x7ffecac8f864, &c = 0x7ffecac8f868 &d = 0x7ffecac8f870
大小40,因为我这是64位系统,指针的大小是8个字节,所以刚好是40个字节。
通过上面的运行,就明白这种方法的缺点,就是太占用类对象的空间了,如果结构体中的方法很多,那这个结构体的变量就很大,如果这个结构体申请很多个变量,那也会导致内存的消耗。
在《深入探索c++对象模型》里确实出现了一种简单的对象模型是这样的:
就因为这个简单所以叫简单对象模型,哈哈哈。
1.4.2 c++结构体成员函数
c++的struct中的结构体跟类的是一样的,都可以支持成员函数。
#include <iostream>
#include <stdio.h>
using namespace std;
struct A
{
char a; // 4
int b; // 4
float c; // 8
double d; // 8
static int e;
int fun1(int a);
int fun2(int b);
};
int A::e = 0;
int A::fun1(int a)
{
return a;
}
int A::fun2(int b)
{
return b;
}
int main(int argc, char **argv)
{
printf("sizeof(A) = %ld %ld %ld\n", sizeof(A), sizeof(int), sizeof(float));
A a;
printf("sizeof(a) = %ld &a = %p\n", sizeof(a), &a);
printf("&a = %p, &b = %p, &c = %p &d = %p &e = %p\n", &(a.a), &(a.b), &(a.c), &(a.d), &(A::e));
printf("func addr : fun1=%d func2=%d\n", a.fun1(1), a.fun2(2));
A b;
printf("sizeof(b) = %ld &b = %p\n", sizeof(b), &b);
printf("&a = %p, &b = %p, &c = %p &d = %p &e = %p\n", &(b.a), &(b.b), &(b.c), &(b.d), &(A::e));
//printf("func addr : fun1=%p func2=%p\n", b.fun1, b.fun2);
return 0;
}
我们看一下编译运行的结果:
root@ubuntu:~/c++_mode/01# ./test_struct_cpp
sizeof(A) = 24 4 4
sizeof(a) = 24 &a = 0x7ffe50ceee70
&a = 0x7ffe50ceee70, &b = 0x7ffe50ceee74, &c = 0x7ffe50ceee78 &d = 0x7ffe50ceee80 &e = 0x60105c
func addr : fun1=1 func2=2
sizeof(b) = 24 &b = 0x7ffe50ceee90
&a = 0x7ffe50ceee90, &b = 0x7ffe50ceee94, &c = 0x7ffe50ceee98 &d = 0x7ffe50ceeea0 &e = 0x60105c
root@ubuntu:~/c++_mode/01#
大小也是24,说明成员函数也不占用对象的空间,本来想直接打印成员函数地址,发现打印不了,怎么办?
勇气牛牛,不怕困难。
用gdb打印:
<A::fun1(int)>
(gdb) p a.fun1
$1 = {int (A * const, int)} 0x400776 <A::fun1(int)>
(gdb) p a.fun2
$2 = {int (A * const, int)} 0x400786 <A::fun2(int)>
(gdb) p b.fun1
$3 = {int (A * const, int)} 0x400776 <A::fun1(int)>
(gdb) p b.fun2
$4 = {int (A * const, int)} 0x400786 <A::fun2(int)>
发现不管那个对象调用fun1,地址都是一样的,并且这个地址是存储在代码区的,这种对象模型设计的就很节省内存。
接下来我们反编译看看,函数符号是怎么修饰的:
70: 0000000000400776 16 FUNC GLOBAL DEFAULT 14 _ZN1A4fun1Ei
71: 0000000000400786 16 FUNC GLOBAL DEFAULT 14 _ZN1A4fun2Ei
跟我们上面分析的是一样的,哟西哟西。
1.4.3 class成员函数
这个也不分析了,跟1.4.2是一样的,不信可以自己分析分析。
1.5 静态函数占的空间
竟然静态成员变量都不在类对象的空间里,那静态函数应该更不在类对象的空间里了,c语言就不分析了,反正也不支持,下面我们来分析分析:
#include <iostream>
#include <stdio.h>
using namespace std;
class A
{
public:
char a; // 4
int b; // 4
float c; // 8
double d; // 8
static int e;
int fun1(int a);
int fun2(int b);
static int sfun1(int a);
static int sfun2(int a);
};
int A::e = 0;
int A::fun1(int a)
{
return a;
}
int A::fun2(int b)
{
return b;
}
int A::sfun1(int a)
{
return a;
}
int A::sfun2(int b)
{
return b;
}
int main(int argc, char **argv)
{
printf("sizeof(A) = %ld %ld %ld\n", sizeof(A), sizeof(int), sizeof(float));
A a;
printf("sizeof(a) = %ld &a = %p\n", sizeof(a), &a);
printf("&a = %p, &b = %p, &c = %p &d = %p &e = %p\n", &(a.a), &(a.b), &(a.c), &(a.d), &(A::e));
printf("func addr : sfun1=%d sfunc2=%d\n", A::sfun1, A::sfun2);
A b;
printf("sizeof(b) = %ld &b = %p\n", sizeof(b), &b);
printf("&a = %p, &b = %p, &c = %p &d = %p &e = %p\n", &(b.a), &(b.b), &(b.c), &(b.d), &(A::e));
return 0;
}
编译运行:
root@ubuntu:~/c++_mode/01# ./test_class
sizeof(A) = 24 4 4
sizeof(a) = 24 &a = 0x7ffe32a18ae0
&a = 0x7ffe32a18ae0, &b = 0x7ffe32a18ae4, &c = 0x7ffe32a18ae8 &d = 0x7ffe32a18af0 &e = 0x60105c
func addr : sfun1=4196246 sfunc2=4196258
sizeof(b) = 24 &b = 0x7ffe32a18b00
&a = 0x7ffe32a18b00, &b = 0x7ffe32a18b04, &c = 0x7ffe32a18b08 &d = 0x7ffe32a18b10 &e = 0x60105c
root@ubuntu:~/c++_mode/01#
果然也不占类对象的空间,并且也是存储在代码区中的:
76: 0000000000400796 12 FUNC GLOBAL DEFAULT 14 _ZN1A5sfun1Ei
符号存储也是跟我们之前的一样。
c++的struct就不测试了。
1.6 虚函数占的空间
最后一个知识点,就是虚函数,在c++的类中是直接虚函数的,虚函数的作用主要是在多态上,现在不是详细分析的时候,反正看这个文章的,对c++都比较熟了,我们就直接来看看虚函数占多少个字节:
#include <iostream>
#include <stdio.h>
using namespace std;
class A
{
public:
char a; // 4
int b; // 4
float c; // 8
double d; // 8
static int e;
int fun1(int a);
int fun2(int b);
static int sfun1(int a);
static int sfun2(int a);
virtual int vfun1(int a); // 8
virtual int vfun2(int b);
};
int A::e = 0;
int A::fun1(int a)
{
return a;
}
int A::fun2(int b)
{
return b;
}
int A::sfun1(int a)
{
return a;
}
int A::sfun2(int b)
{
return b;
}
int A::vfun1(int a)
{
return a;
}
int A::vfun2(int b)
{
return b;
}
int main(int argc, char **argv)
{
printf("sizeof(A) = %ld %ld %ld\n", sizeof(A), sizeof(int), sizeof(float));
A a;
printf("sizeof(a) = %ld &a = %p\n", sizeof(a), &a);
printf("&a = %p, &b = %p, &c = %p &d = %p &e = %p\n", &(a.a), &(a.b), &(a.c), &(a.d), &(A::e));
printf("func addr : sfun1=%d sfunc2=%d\n", A::sfun1, A::sfun2);
A b;
printf("sizeof(b) = %ld &b = %p\n", sizeof(b), &b);
printf("&a = %p, &b = %p, &c = %p &d = %p &e = %p\n", &(b.a), &(b.b), &(b.c), &(b.d), &(A::e));
return 0;
}
编译运行:
root@ubuntu:~/c++_mode/01# ./test_class
sizeof(A) = 32 4 4
sizeof(a) = 32 &a = 0x7fff9ffe9b20
&a = 0x7fff9ffe9b28, &b = 0x7fff9ffe9b2c, &c = 0x7fff9ffe9b30 &d = 0x7fff9ffe9b38 &e = 0x6010b4
func addr : sfun1=4196358 sfunc2=4196370
sizeof(b) = 32 &b = 0x7fff9ffe9b40
&a = 0x7fff9ffe9b48, &b = 0x7fff9ffe9b4c, &c = 0x7fff9ffe9b50 &d = 0x7fff9ffe9b58 &e = 0x6010b4
root@ubuntu:~/c++_mode/01#
增加两个虚函数,大小比之前大了8个字节,刚好我是64位操作系统,刚好就是一个指针的大小,这个指针指向虚函数表的指针。
虚函数表的知识我们后面再解释把。
1.7 总结
最后引用一下,《深入探索c++对象模型》中的c++对象模型的图:
这个图就很好的总结了我们今天所说的内容:
- 成员变量在类对象里
- 成员函数不在类对象里,在代码段
- 静态成员变量也不在类对象里,在数据段
- 静态函数也不在类对象里,在代码段
- 如果类中有虚函数,就会产生一个指向虚函数表的指针。
最后,今天也是除夕,也祝大家新年快乐,明年继续卷,今年暂时不要卷了,好好过年,哈哈哈。