目录
一、引言
在C/C++编程中,sizeof()
操作符是一个看似简单但实则充满陷阱的工具。它用于计算对象或类型所占内存的字节数,但在不同场景下的行为却大相径庭。许多开发者,尤其是初学者,常常因为对其理解不够深入而写出错误的代码。本文将全面剖析sizeof()
在应用于指针、数组、结构体和类时的各种行为,揭示其中的常见错误,帮助大家完全掌握它。
二、sizeof()基础
2.1、sizeof的本质
sizeof()是C/C++中的一个操作符(operator),而非函数。它的返回值类型是size_t,在头文件中被定义为unsigned int类型。这个操作符在编译时而非运行时求值,这意味着它的结果在编译阶段就已经确定。
2.2、sizeof()基本语法
sizeof(类型) // 必须加括号
使用示例:
int a;
sizeof(int); // 正确
sizeof a; // 正确但不推荐
sizeof int; // 错误,类型必须加括号
三、指针与sizeof
3.1、指针的基本行为
指针在32位系统中占4字节,在64位系统中占8字节,无论它指向什么类型的数据。这是许多初学者容易混淆的地方。
char* ss = "0123456789";
sizeof(ss); // 32位系统返回4,64位系统返回8
sizeof(*ss); // 返回1,因为*ss是char类型
3.2、常见误区
误区一:认为sizeof(指针)
会返回指向内容的大小。
int* ptr = new int[100];
sizeof(ptr); // 返回4或8,而不是400
误区二:试图用sizeof
计算动态分配数组的长度。
char* p = malloc(100);
sizeof(p); // 仍然是4或8,无法得到分配的内存大小
四、数组与sizeof
4.1、数组的基本行为
当sizeof
应用于数组名时,它会返回整个数组占用的字节数,而不是指针的大小。
char ss[] = "0123456789"; // 包含'\0'共11个字符
sizeof(ss); // 返回11
4.2、数组长度计算技巧
常用计算数组元素个数的方法:
int arr[100];
int count = sizeof(arr) / sizeof(arr[0]); // 正确计算元素个数
4.3、常见陷阱
陷阱一:数组作为函数参数传递时会退化为指针。
void func(char arr[100]) {
sizeof(arr); // 返回指针大小,不是100
}
陷阱二:部分初始化数组时的误解。
int ss[100] = {1,2,3,4,5,6,7,8,9};
for(int i=0; i<sizeof(ss)/sizeof(int); i++) {
// 会循环100次,而不是9次
}
五、结构体与类中的sizeof
5.1 、内存对齐原则
·为了CPU访问效率,编译器会对结构体和类进行内存对齐(Data Alignment)。对齐规则如下:
-
每个成员的偏移量必须是该成员大小的整数倍
-
整个结构体的大小必须是最大成员大小的整数倍
struct Example {
char a; // 1字节
int b; // 4字节
short c; // 2字节
};
// sizeof(Example) 在大多数系统中为12
5.2、 类的大小计算
类的大小计算需要考虑:
-
非静态成员变量
-
内存对齐
-
虚函数表指针(如果有虚函数)
class X {
int i; // 4
int j; // 4
char k; // 1
};
// sizeof(X) 通常是12(考虑对齐)
5.3、继承与虚函数的影响
class Base {
virtual void foo() {}
};
class Derived : public Base {
int x;
};
// sizeof(Base) 通常包含虚表指针(4或8字节)
// sizeof(Derived) 包含基类部分和新增成员
六、特殊场景分析
6.1、函数与sizeof
sizeof
不能直接用于函数类型,但可以用于函数返回值:
short func() { return 0; }
sizeof(func()); // 返回sizeof(short)
sizeof(func); // 编译错误
6.2、 不完全类型
sizeof
不能用于不完全类型(incomplete type):
extern int arr[]; // 不完全类型
sizeof(arr); // 编译错误
6.3、位域
sizeof
不能用于位域成员:
struct {
unsigned int a : 4;
};
sizeof(a); // 错误
七、sizeof与strlen的区别
特性 | sizeof | strlen |
---|---|---|
类型 | 操作符 | 函数 |
参数 | 类型或变量 | 必须是char* |
计算时机 | 编译时 | 运行时 |
包含'\0' | 是 | 否 |
数组行为 | 返回整个数组大小 | 返回字符串长度 |
八、跨平台注意事项
-
指针大小:32位和64位系统不同
-
基本类型大小:不同平台可能有差异
-
对齐规则:不同编译器可能有不同实现
-
位域处理:实现定义行为
最佳实践:永远不要假设类型的大小,使用sizeof
获取实际大小。
九、实际应用技巧
9.1、安全的内存操作
// 安全的memset使用
SomeStruct s;
memset(&s, 0, sizeof(s));
9.2、 泛型编程中的应用
template<typename T, size_t N>
size_t array_size(T (&)[N]) {
return N;
}
9.3、调试与断言
static_assert(sizeof(int) == 4, "int must be 4 bytes");
十、总结
-
明确目的:确定你需要的是内存大小还是元素数量
-
区分指针和数组:数组名在大多数情况下会退化为指针
-
考虑对齐:结构体和类的大小可能大于成员总和
-
跨平台思维:不要假设类型大小
-
编译时检查:使用static_assert验证假设
-
文档化假设:对大小敏感的代码要添加注释
sizeof
是一个强大的工具,但只有深入理解它的行为,才能避免陷入各种陷阱,写出健壮可靠的代码。通过深入理解sizeof
的各种行为,开发者可以避免许多常见的错误,写出更加健壮和可移植的代码。