文章

sizeof与strlen

sizeof返回变量或类型占用字节数,strlen计算字符串长度,不包括终止符。

sizeof与strlen

sizeof与strlen

sizeof编译时操作符

作用:

用于获取类型或对象在内存中所占的字节数

用法:

1
2
sizeof(类型名)    // 获取某个类型的字节大小
sizeof(变量名)    // 获取某个对象(变量、数组等)的大小

特点:

  • 编译时决定 的操作,结果是一个 constexpr
  • 返回值类型是 size_t
  • 与内容无关,仅与 类型定义数组大小 有关。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;

int main() {
    int x = 10;
    char str1[] = "hello";
    char* str2 = "hello";

    struct MyStruct {
        char a;   // 1 字节
        int b;    // 4 字节
        char c;   // 1 字节
    };

    MyStruct s;

    cout << "sizeof(x) = " << sizeof(x) << endl;           // 输出 4
    cout << "sizeof(str1) = " << sizeof(str1) << endl;     // 输出 6
    cout << "sizeof(str2) = " << sizeof(str2) << endl;     // 输出 8(在64位系统中)
    cout << "sizeof(MyStruct) = " << sizeof(MyStruct) << endl; // 输出 12(字节对齐)
}

结构体 MyStruct 的内存布局分析:

1
2
3
4
5
6
7
struct MyStruct {
    char a;   // 占 1 字节,偏移 0
    padding   // 3 字节,对齐 int 到 4 字节边界
    int b;    // 占 4 字节,偏移 4
    char c;   // 占 1 字节,偏移 8
    padding   // 3 字节,使结构体总大小为 12(对齐到最大对齐单位)
};

总大小:

1 + 3(padding)+ 4 + 1 + 3(padding)= 12 字节

如果改为:

1
2
3
4
5
struct MyStruct2 {
    int b;
    char a;
    char c;
};

此时:

  • b 在偏移 0,占 4 字节;
  • ac 紧跟其后,占 1+1;
  • padding 2 字节,使结构体对齐到 4 字节边界。
1
sizeof(MyStruct2) == 8

strlen运行时函数

作用:

用于计算 C 风格字符串的实际长度(不包括 \0 终止符)。

用法:

1
strlen(const char* str);  // 参数必须是以'\0'结尾的C字符串

特点:

  • 是运行时函数,遍历字符串直到遇到 \0
  • 返回值类型也是 size_t
  • 与内存大小无关,只与字符串的内容有关。
  • 需要包含头文件:#include <cstring>

示例:

1
2
3
4
5
char str1[] = "hello";      // 长度是5,实际占6字节(包含'\0')
char* str2 = "hello";

std::cout << strlen(str1);  // 输出5
std::cout << strlen(str2);  // 输出5

对比表格

项目sizeofstrlen
类型编译时操作符运行时函数
返回值类型size_tsize_t
所测内容类型或对象的字节数字符串的实际长度(不含 ‘\0’)
与内容相关性无关有关
是否遍历内存
是否包含 \0包含(数组中)不包含
常用场景内存分配、结构体计算字符串处理

易错点

错误思维正确理解
sizeof(str) 就是字符串长度错 ❌,可能只是指针大小(8 字节)
strlen 可以用于任意类型错 ❌,只能用于 \0 结尾的 char*
sizeof 返回的是元素个数错 ❌,它返回的是字节数

例如:sizeof(arr) / sizeof(arr[0]) 才是数组长度(元素个数)

数组作为函数参数会退化为指针

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
using namespace std;

void printSize(int arr[]) {
    cout << "sizeof(arr) in function = " << sizeof(arr) << endl;
}

int main() {
    int arr[10];

    cout << "sizeof(arr) in main = " << sizeof(arr) << endl;  // 输出 40(10 * 4)
    printSize(arr);  // 实际上传递的是 int*
}

输出:

sizeof(arr) in main = 40
sizeof(arr) in function = 8 (在64位系统)或 4(32位系统)
  • main() 里,arr 是一个真正的数组,大小是 10 * sizeof(int),所以是 40。
  • printSize() 里,arr函数参数,它退化成了一个 int*sizeof(arr) 实际上就是 sizeof(int*),在 64 位系统上是 8。

函数签名中三种写法等价:

1
2
3
void func(int arr[]);
void func(int arr[10]);
void func(int* arr);

这三种在函数参数中是等价的,都会退化为指针 int\*

sizeof 一个空类

在 C++ 中,即使一个类是空的(即没有成员变量和成员函数),sizeof 这个空类的结果也不会是 0,而是 1。这是 C++ 语言标准所规定的行为。

原因:

C++ 需要确保每个对象在内存中有一个唯一的地址。如果 sizeof(Empty)0,那么创建多个该类的对象时,它们可能会被分配到相同的地址,从而违反了对象地址唯一性的要求。

示例代码:

1
2
3
4
5
6
7
8
#include <iostream>

class Empty {};

int main() {
    std::cout << sizeof(Empty) << std::endl;  // 输出 1
    return 0;
}

特别说明:

  • 编译器会为空类分配至少 1 字节空间,以确保不同对象的地址不同。
  • 如果你继承一个空类,情况可能会有所不同(尤其涉及 空基类优化 EBO, Empty Base Optimization),例如:
1
2
3
4
5
6
7
8
9
class Empty {};

class Derived : public Empty {
    int x;
};

int main() {
    std::cout << sizeof(Derived) << std::endl; // 通常输出 4,而不是 5
}

在这个例子中,Derived 继承自 Empty,但 Empty 并没有占用额外空间,说明编译器应用了 EBO

总结:

  • sizeof(空类) == 1,这是标准规定的。
  • 主要目的是为了让对象地址唯一。
  • 编译器可能在继承时优化掉空基类占用的空间。
本文由作者按照 CC BY 4.0 进行授权