sizeof操作符和字节对齐(Byte Alignment)

Last Updated: 2023-12-12 04:52:35 Tuesday

-- TOC --

sizeof是C语言的一种编译期执行的单目操作符(unary operator),它并不是函数。sizeof操作符以字节形式给出了其操作数(operand)的存储大小,返回值类型为size_t。

sizeof操作符

sizeof操作数可以是一个表达式或类型名,操作数的存储大小由操作数的类型决定。

$ cat test.c
#include <stdio.h>

int main(void) {
    printf("sizeof(size_t):%zu\n", sizeof(size_t));
    return 0;
}
$ gcc test.c -o test
$ ./test
sizeof(size_t):8

sizeof返回size_t,打印size_t类型,使用%zu,C99。

sizeof后面不一定要使用括号,如果是一个变量,可以不用括号,如下。不过,一般我们都会使用统一使用括号!

int a = 0;
int sizeofa = sizeof a;

注意:sizeof操作符不能用于函数类型,不完全类型或位字段。不完全类型指具有未知存储大小的数据类型,如未知存储大小的数组类型、未知内容的结构或联合类型、void类型等。如sizeof(max),若此时变量max定义为int max(),或sizeof(char_v),若char_v定义为char char_v[MAX],且MAX未知,或sizeof(void),都不是正确形式。

sizeof一个变量,而不是sizeof这个变量的type,可以让代码更具可维护性。

用sizeof获取数组长度

这是一个很common的trick,用sizeof获取数组的长度:

int aa[] = {1,2,3,4,5,6,7,8};
printf("%d\n", sizeof(aa)/sizeof(int));

sizeof(aa)能够正确得到整个数组所占用的byte数,aa的类型为int [],其size在编译期可以确定。

C++中的sizeof...

获取variadic template parameter的个数。

字节对齐

我们在提到字节对齐的时候,一般都是在说结构体struct所占用的那块内存。

$ cat test.c
#include <stdio.h>

struct char_double{
    char c;
    double d;
};
typedef struct char_double cd;

int main(void) {
    char *p = NULL;
    printf("sizeof(pointer):%zu\n", sizeof(p));
    printf("sizeof(cd):%zu\n", sizeof(cd));
    return 0;
}
$ gcc test.c -o test
$ ./test
sizeof(pointer):8
sizeof(cd):16

结构体实际上只使用了9个byte,但是sizeof的结果是16,这就是字节对齐!字节对齐是为了提高代码访问内存的效率,但同时也可能会造成一些潜在的问题,特别是在网络编程处理报文的时候。

下面是关于gcc中如何设置结构体字节对齐的测试代码,同时也展示了sizeof的使用:

$ cat test.c
#include <stdio.h>

typedef struct test0{
    char c;
    double d;
} test0;

typedef struct test1{
    char c;
    double d;
} __attribute__((aligned(4))) test1;

typedef struct test2{
    char c;
    double d;
} __attribute__((packed)) test2;

typedef struct test3{
    char c;
    double d;
} __attribute__((aligned(16))) test3;

typedef struct aa{
    char a;
    char b;
} aa;

int main(void) {
    printf("sizeof(test0):%zu\n", sizeof(test0));
    printf("sizeof(test1):%zu\n", sizeof(test1));
    printf("sizeof(test2):%zu\n", sizeof(test2));
    printf("sizeof(test3):%zu\n", sizeof(test3));
    printf("sizeof(aa):%zu\n", sizeof(aa));
    return 0;
}
$ gcc test.c -o test
$ ./test
sizeof(test0):16
sizeof(test1):16
sizeof(test2):9
sizeof(test3):16
sizeof(aa):2

aligned(n)表示以n字节的方式对齐,n必须为power of 2,packed表示1字节对齐!

注意最后那个sizeof(aa),大小为2,不是8!!

节省内存的struct设计细节:一般情况下,在定义结构体的时候,内部成员建议按照从大到小的顺序书写,成员申明的顺序,也是它们在内存中布局的顺序,从大到小有利于节省内存。但这个规则并不绝对,在定义struct时,最好人工check一下最优的顺序是什么,原则就是尽量减少元素之间为了字节对齐而产生的padding。

更详的细字节对齐规则

规则一:结构体中元素是按照定义顺序一个个放到内存中的,但默认并非紧密排列。从结构体的首地址开始,每一个元素放置到内存中时,它都会认为内存是以它自己的大小来划分的,因此元素放置的位置一定会在自己大小的整数倍上开始(以结构体变量首地址为0计算)。

typedef struct AA{
    char a;
    char a2;
    int b;
    double c;
}AA;
// sizeof(AA) == 16
// a
// a2
//
//
// b
// b
// b
// b
// c
// c
// c
// c
// c
// c
// c
// c

规则二:在按照第一规则进行内存分配后,需要检查计算分配的内存大小是否为所有成员中size最大成员的size的整数倍,若不是,则补齐为它的整数倍。

typedef struct BB{
    char a;
    char a2;
    double b;
    int c;
}BB;
// sizeof(BB) == 24
// a
// a2
//
//
//
//
//
//
// b
// b
// b
// b
// b
// b
// b
// b
// c
// c
// c
// c
//
//
//
//

规则三:当存在结构体嵌套时,以上两条规则依然成立,只是size最大的成员,要在所有内嵌结构体的成员中确认。

typedef struct xyz{
    char a;
    int b;
    struct AA{
        long int x;
        char y;
    } aa;
    short c;
}xyz;
// sizeof(xyz) == 32

size最大的成员是内嵌结构体中的x,为8,因此要执行8字节对齐。同时,嵌套的struct AA要形成一个boundary,因此最后的sizeof值为32。

typedef struct xyz{
    char a;
    int b;
    struct AA{
        int x;
        char y;
        struct BB{
            long int z;
        } bb;
    } aa;
    char c;
}xyz;
// sizeof(xyz) == 32

按照bb中的z的size对齐。

字节对齐数,是结构体最小占用内存数

#include <iostream>
using namespace std;

struct alignas(64) xyz {
    char a;
    int b;
};

int main(void) {
    cout << sizeof(xyz) << endl;
    return 0;
}

输出:64

alignas关键词是C++11引入的,是一种可以跨平台的申明字节对齐的方式,最小有效值为8。

C++复杂对象的sizeof和字节对齐

#include <iostream>
#include <cstdio>
#include <string>
#include <unordered_map>
using namespace std;


struct xyz{
    int a;
    char b;
    string c;
    int d;
    unordered_map<int,int> e;
    char f;
};


int main(){
    xyz x;
    cout << "sizeof(xyz) = " << sizeof(xyz) << endl;
    printf("%p\n", &x.a);
    printf("%p\n", &x.b);
    printf("%p\n", &x.c);
    printf("%p\n", &x.d);
    printf("%p\n", &x.e);
    printf("%p\n", &x.f);
    return 0;
}

结构体xyz包含一个string对象和一个unordered_map对象,这些对象的定义,也是一个class或struct,sizeof和字节对齐的规则,应该与结构体嵌套一样。

输出:

sizeof(xyz) = 112
0x7ffe29dc0b80
0x7ffe29dc0b84
0x7ffe29dc0b88
0x7ffe29dc0ba8
0x7ffe29dc0bb0
0x7ffe29dc0be8

整体是4字节对齐。

有static成员时

class的static成员的存储区域,与其它成员不一样,因此static成员不占sizeof大小。更多:在C++的类中使用static申明

member type

与static成员一样,不占sizeof大小。

本文链接:https://cs.pynote.net/sf/c/202111197/

-- EOF --

-- MORE --