MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C++ sizeof运算符和size_t类型详解

2021-07-317.5k 阅读

C++ sizeof运算符的基本概念

在C++编程中,sizeof是一个非常重要的运算符。它用于获取数据类型或者变量在内存中所占的字节数。sizeof运算符的操作数可以是数据类型(如intdouble等),也可以是变量、数组、指针等表达式。

其语法形式主要有两种:

  1. sizeof(type):这里type是具体的数据类型,例如sizeof(int)sizeof(double)
  2. sizeof expression:这里expression可以是变量、数组、指针等,例如int num = 10; sizeof(num)

下面通过一个简单的代码示例来展示sizeof运算符的基本使用:

#include <iostream>
int main() {
    int num = 10;
    double dbl = 3.14;
    char ch = 'a';

    std::cout << "Size of int: " << sizeof(int) << " bytes" << std::endl;
    std::cout << "Size of double: " << sizeof(double) << " bytes" << std::endl;
    std::cout << "Size of char: " << sizeof(char) << " bytes" << std::endl;

    std::cout << "Size of num: " << sizeof(num) << " bytes" << std::endl;
    std::cout << "Size of dbl: " << sizeof(dbl) << " bytes" << std::endl;
    std::cout << "Size of ch: " << sizeof(ch) << " bytes" << std::endl;

    return 0;
}

在上述代码中,我们分别使用sizeof获取了intdoublechar数据类型以及对应变量numdblch的大小。通常在32位或64位系统下,int一般占4个字节,double占8个字节,char占1个字节。

sizeof运算符与不同数据类型

整数类型

  1. char类型char类型用于存储单个字符,在C++中,char类型的大小始终为1字节。这是因为char类型主要是为了满足字符存储的需求,而一个字节(8位)足够表示基本的ASCII字符集。例如:
#include <iostream>
int main() {
    char ch = 'A';
    std::cout << "Size of char: " << sizeof(char) << " bytes" << std::endl;
    std::cout << "Size of ch: " << sizeof(ch) << " bytes" << std::endl;
    return 0;
}

无论在何种平台,输出都将是Size of char: 1 bytesSize of ch: 1 bytes

  1. 有符号整数类型:有符号整数类型包括shortintlonglong long。它们的大小在不同的系统平台上可能有所不同。一般来说,short至少为2字节,int至少为2字节,long至少为4字节,long long至少为8字节。在常见的32位和64位系统中,short通常是2字节,int通常是4字节,long在32位系统中是4字节,在64位系统中是8字节,long long始终是8字节。例如:
#include <iostream>
int main() {
    short s = 10;
    int i = 100;
    long l = 1000;
    long long ll = 1000000000000LL;

    std::cout << "Size of short: " << sizeof(short) << " bytes" << std::endl;
    std::cout << "Size of int: " << sizeof(int) << " bytes" << std::endl;
    std::cout << "Size of long: " << sizeof(long) << " bytes" << std::endl;
    std::cout << "Size of long long: " << sizeof(long long) << " bytes" << std::endl;

    return 0;
}

在常见的64位系统下,输出可能为Size of short: 2 bytesSize of int: 4 bytesSize of long: 8 bytesSize of long long: 8 bytes

  1. 无符号整数类型:无符号整数类型unsigned shortunsigned intunsigned longunsigned long long与对应的有符号整数类型大小相同。例如,unsigned intint在同一平台上大小一致,只是无符号整数类型只能表示非负整数,其取值范围是有符号整数类型取值范围的正数部分和零。例如:
#include <iostream>
int main() {
    unsigned short us = 10;
    unsigned int ui = 100;
    unsigned long ul = 1000;
    unsigned long long ull = 1000000000000ULL;

    std::cout << "Size of unsigned short: " << sizeof(unsigned short) << " bytes" << std::endl;
    std::cout << "Size of unsigned int: " << sizeof(unsigned int) << " bytes" << std::endl;
    std::cout << "Size of unsigned long: " << sizeof(unsigned long) << " bytes" << std::endl;
    std::cout << "Size of unsigned long long: " << sizeof(unsigned long long) << " bytes" << std::endl;

    return 0;
}

输出结果与对应的有符号整数类型大小相同。

浮点类型

  1. float类型float类型用于表示单精度浮点数,通常占用4个字节(32位)。它能够表示大约7位有效数字。例如:
#include <iostream>
int main() {
    float f = 3.14f;
    std::cout << "Size of float: " << sizeof(float) << " bytes" << std::endl;
    std::cout << "Size of f: " << sizeof(f) << " bytes" << std::endl;
    return 0;
}

输出为Size of float: 4 bytesSize of f: 4 bytes

  1. double类型double类型用于表示双精度浮点数,通常占用8个字节(64位)。它能够表示大约15 - 17位有效数字。例如:
#include <iostream>
int main() {
    double d = 3.141592653589793;
    std::cout << "Size of double: " << sizeof(double) << " bytes" << std::endl;
    std::cout << "Size of d: " << sizeof(d) << " bytes" << std::endl;
    return 0;
}

输出为Size of double: 8 bytesSize of d: 8 bytes

  1. long double类型long double类型用于表示扩展精度浮点数,其大小在不同平台上有所不同。在一些系统中,long doubledouble大小相同,都是8字节;在另一些系统中,long double可能占用10字节、12字节甚至16字节。例如:
#include <iostream>
int main() {
    long double ld = 3.141592653589793116L;
    std::cout << "Size of long double: " << sizeof(long double) << " bytes" << std::endl;
    std::cout << "Size of ld: " << sizeof(ld) << " bytes" << std::endl;
    return 0;
}

在某些系统上,输出可能是Size of long double: 12 bytesSize of ld: 12 bytes

指针类型

指针用于存储内存地址。在32位系统中,指针通常占用4个字节,因为32位系统的地址空间是2^32,需要4个字节(32位)来表示一个内存地址。在64位系统中,指针通常占用8个字节,因为64位系统的地址空间是2^64,需要8个字节(64位)来表示一个内存地址。例如:

#include <iostream>
int main() {
    int num = 10;
    int *ptr = &num;

    std::cout << "Size of int pointer: " << sizeof(ptr) << " bytes" << std::endl;
    return 0;
}

在64位系统下,输出为Size of int pointer: 8 bytes

sizeof运算符与数组

  1. 一维数组:对于一维数组,sizeof运算符返回整个数组占用的字节数。例如:
#include <iostream>
int main() {
    int arr1[5] = {1, 2, 3, 4, 5};
    char arr2[10] = "Hello";

    std::cout << "Size of arr1: " << sizeof(arr1) << " bytes" << std::endl;
    std::cout << "Size of arr2: " << sizeof(arr2) << " bytes" << std::endl;

    return 0;
}

在上述代码中,arr1是一个包含5个int类型元素的数组,由于int通常占4个字节,所以sizeof(arr1)5 * 4 = 20字节。arr2是一个包含10个char类型元素的数组,char占1个字节,所以sizeof(arr2)为10字节。

  1. 二维数组:对于二维数组,sizeof同样返回整个数组占用的字节数。二维数组可以看作是数组的数组。例如:
#include <iostream>
int main() {
    int arr[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };

    std::cout << "Size of arr: " << sizeof(arr) << " bytes" << std::endl;
    return 0;
}

这里arr是一个3行4列的二维数组,每个int元素占4个字节,所以整个数组占用3 * 4 * 4 = 48字节,sizeof(arr)输出为48字节。

  1. 数组作为函数参数:当数组作为函数参数传递时,它会退化为指针。这意味着在函数内部使用sizeof运算符获取的是指针的大小,而不是数组的实际大小。例如:
#include <iostream>
void printArraySize(int arr[]) {
    std::cout << "Size of arr inside function: " << sizeof(arr) << " bytes" << std::endl;
}
int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    std::cout << "Size of arr in main: " << sizeof(arr) << " bytes" << std::endl;
    printArraySize(arr);
    return 0;
}

main函数中,sizeof(arr)输出为5 * 4 = 20字节(假设int占4字节)。而在printArraySize函数中,sizeof(arr)输出的是指针的大小,在64位系统下通常为8字节。

sizeof运算符与结构体和联合体

结构体

  1. 结构体的内存布局:结构体是一种用户自定义的数据类型,它可以包含不同的数据类型成员。结构体的大小不仅仅是其成员大小的简单相加,还涉及到内存对齐的问题。内存对齐是为了提高内存访问效率,现代计算机系统通常要求数据存储的地址是某些特定值的倍数(例如4字节对齐、8字节对齐等)。例如:
#include <iostream>
struct MyStruct1 {
    char ch;
    int num;
};
struct MyStruct2 {
    int num;
    char ch;
};
int main() {
    std::cout << "Size of MyStruct1: " << sizeof(MyStruct1) << " bytes" << std::endl;
    std::cout << "Size of MyStruct2: " << sizeof(MyStruct2) << " bytes" << std::endl;
    return 0;
}

在上述代码中,MyStruct1先定义了一个char类型成员ch,占1字节,然后定义了一个int类型成员num,假设int占4字节。由于内存对齐,ch后面会填充3个字节,使得num的存储地址是4的倍数,所以sizeof(MyStruct1)为8字节。而MyStruct2先定义int类型成员num,再定义char类型成员chnum占4字节,ch占1字节,ch后面填充3字节,sizeof(MyStruct2)同样为8字节。

  1. 结构体嵌套:当结构体中包含其他结构体成员时,同样遵循内存对齐规则。例如:
#include <iostream>
struct InnerStruct {
    char ch;
    short s;
};
struct OuterStruct {
    InnerStruct inner;
    int num;
};
int main() {
    std::cout << "Size of InnerStruct: " << sizeof(InnerStruct) << " bytes" << std::endl;
    std::cout << "Size of OuterStruct: " << sizeof(OuterStruct) << " bytes" << std::endl;
    return 0;
}

InnerStruct中,ch占1字节,s占2字节,为了对齐,ch后面填充1字节,所以sizeof(InnerStruct)为4字节。在OuterStruct中,inner占4字节,num占4字节,sizeof(OuterStruct)为8字节。

联合体

联合体也是一种用户自定义的数据类型,它允许不同的数据类型共享相同的内存空间。联合体的大小是其最大成员的大小。例如:

#include <iostream>
union MyUnion {
    int num;
    char ch;
    double dbl;
};
int main() {
    std::cout << "Size of MyUnion: " << sizeof(MyUnion) << " bytes" << std::endl;
    return 0;
}

在上述代码中,MyUnion包含intchardouble类型成员,由于double类型大小最大,占8字节,所以sizeof(MyUnion)为8字节。

size_t类型

  1. 定义和用途size_t是C++标准库中定义的一种无符号整数类型,它用于表示内存中对象的大小(以字节为单位)。size_t通常是sizeof运算符的返回类型。其定义在<cstddef>头文件中。例如:
#include <iostream>
#include <cstddef>
int main() {
    int num = 10;
    size_t size = sizeof(num);
    std::cout << "Size of num: " << size << " bytes" << std::endl;
    return 0;
}

在上述代码中,sizeof(num)的返回值类型为size_t,我们将其赋值给size变量并输出。

  1. 与循环和数组索引size_t常用于循环和数组索引,因为它能够表示足够大的无符号整数,适用于处理大数组。例如:
#include <iostream>
#include <cstddef>
int main() {
    int arr[100];
    for (size_t i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i) {
        arr[i] = i;
    }
    for (size_t i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i) {
        std::cout << "arr[" << i << "] = " << arr[i] << std::endl;
    }
    return 0;
}

在上述代码中,我们使用size_t类型的变量i作为数组索引进行循环操作,确保能够处理大数组而不会出现溢出问题。

  1. 与函数参数和返回值:当函数需要处理对象大小相关的操作时,通常使用size_t作为参数或返回值类型。例如:
#include <iostream>
#include <cstddef>
size_t getArraySize(int arr[]) {
    // 这里实际上返回的是指针大小,仅作示例
    return sizeof(arr);
}
int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    size_t size = getArraySize(arr);
    std::cout << "Size of arr (as pointer in function): " << size << " bytes" << std::endl;
    return 0;
}

在上述代码中,getArraySize函数返回值类型为size_t,用于表示数组(实际是指针)的大小。

sizeof运算符的特殊情况和注意事项

  1. sizeof与函数调用sizeof运算符不会计算其操作数的值,因此即使操作数是一个函数调用,函数也不会被执行。例如:
#include <iostream>
int getValue() {
    std::cout << "getValue function called" << std::endl;
    return 10;
}
int main() {
    size_t size = sizeof(getValue());
    std::cout << "Size: " << size << " bytes" << std::endl;
    return 0;
}

在上述代码中,sizeof(getValue())不会调用getValue函数,所以不会输出getValue function called,而只会输出Size: 4 bytes(假设int占4字节)。

  1. sizeof与未定义行为:如果sizeof的操作数是一个未完全定义的类型(例如未定义大小的数组),可能会导致未定义行为。例如:
#include <iostream>
int main() {
    int arr[]; // 未定义大小的数组
    // size_t size = sizeof(arr); // 这会导致未定义行为
    return 0;
}

在上述代码中,尝试对未定义大小的数组使用sizeof会导致未定义行为,因此注释掉了这一行代码。

  1. sizeof与模板:在模板编程中,sizeof运算符可以用于获取模板参数类型的大小。例如:
#include <iostream>
#include <cstddef>
template <typename T>
size_t getTypeSize() {
    return sizeof(T);
}
int main() {
    size_t intSize = getTypeSize<int>();
    size_t doubleSize = getTypeSize<double>();
    std::cout << "Size of int: " << intSize << " bytes" << std::endl;
    std::cout << "Size of double: " << doubleSize << " bytes" << std::endl;
    return 0;
}

在上述代码中,getTypeSize模板函数使用sizeof获取模板参数类型的大小。

sizeof运算符的应用场景

  1. 内存分配和管理:在动态内存分配时,sizeof运算符用于确定所需分配的内存大小。例如,使用new运算符分配数组内存时:
#include <iostream>
#include <cstddef>
int main() {
    size_t size = 5;
    int *arr = new int[size];
    for (size_t i = 0; i < size; ++i) {
        arr[i] = i;
    }
    for (size_t i = 0; i < size; ++i) {
        std::cout << "arr[" << i << "] = " << arr[i] << std::endl;
    }
    delete[] arr;
    return 0;
}

在上述代码中,new int[size]分配了sizeint类型元素的内存空间,size通常是通过sizeof(int)与所需元素个数计算得出。

  1. 文件读写操作:在进行文件读写时,sizeof运算符用于确定要读写的数据块大小。例如,将数组数据写入文件:
#include <iostream>
#include <fstream>
#include <cstddef>
int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    std::ofstream file("data.bin", std::ios::binary);
    if (file.is_open()) {
        file.write(reinterpret_cast<const char*>(arr), sizeof(arr));
        file.close();
    }
    return 0;
}

在上述代码中,file.write函数使用sizeof(arr)确定要写入文件的数据块大小。

  1. 序列化和反序列化:在将数据结构转换为字节流(序列化)或从字节流恢复数据结构(反序列化)时,sizeof运算符用于确定每个数据成员的大小。例如:
#include <iostream>
#include <fstream>
#include <cstddef>
struct MyStruct {
    int num;
    char ch;
};
int main() {
    MyStruct obj = {10, 'a'};
    std::ofstream file("data.bin", std::ios::binary);
    if (file.is_open()) {
        file.write(reinterpret_cast<const char*>(&obj), sizeof(obj));
        file.close();
    }
    MyStruct newObj;
    std::ifstream inFile("data.bin", std::ios::binary);
    if (inFile.is_open()) {
        inFile.read(reinterpret_cast<char*>(&newObj), sizeof(newObj));
        inFile.close();
    }
    std::cout << "num: " << newObj.num << ", ch: " << newObj.ch << std::endl;
    return 0;
}

在上述代码中,sizeof(obj)用于确定序列化和反序列化时数据的大小。

总结sizeof运算符和size_t类型的要点

  1. sizeof运算符

    • 用于获取数据类型或变量在内存中所占的字节数,操作数可以是数据类型或表达式。
    • 不同数据类型的大小在不同平台上可能有所差异,但有一定的标准范围。
    • 数组作为函数参数传递时会退化为指针,sizeof获取的是指针大小。
    • 结构体和联合体的大小涉及内存对齐和成员大小的计算。
    • sizeof不会计算操作数的值,例如函数调用不会被执行。
  2. size_t类型

    • 是一种无符号整数类型,用于表示内存中对象的大小(以字节为单位),通常是sizeof运算符的返回类型。
    • 常用于循环和数组索引,以及函数参数和返回值,以处理对象大小相关的操作。

通过深入理解sizeof运算符和size_t类型,C++开发者能够更好地进行内存管理、文件操作、序列化等编程任务,提高程序的效率和可移植性。在实际编程中,应根据具体需求合理运用这两个重要的概念,避免因内存大小计算错误或类型不匹配导致的程序错误。