C 语言的骚操作

2024-08-24 10:55:47 浏览数 (2)

最近看别人代码突然看见一个操作让我感到很迷惑。代码如下:

代码语言:c复制
#define  offsetof(type, member) (size_t)&(((type*)0)->number)

如果稍微了解一下其他语言,比方说 Java 就会直到,将 0 强转为一个结构体类型指针,相当于一个空指针,空指针引用,这在 Java 中可是开发的心头大忌。

我开始也是怀疑这个是不是能够运行起来,就写了一段测试程序。

代码语言:c复制
#include <stdio.h>

typedef struct test{
    char ch;
    int number;
}*Test;

int main(){
    int a = (int)&(((Test)0)->number);
    printf("%dn", a);
    return 0;
}

上述代码只要熟悉 c 语言的基本都能看得懂,对代码进行编译时候编译也通过了。

编译代码编译代码

编译器只是对编译过程做出了警告,并没有报 error ,所以语法层面是可以编译成功的。

那么既然可以运行那么有人直到结果是什么吗?

在得出结果前,我们先看一下这段代码是干什么的,首先(Test)0 是将 0 强制转化为一个指向 test 结构体的指针。

然后 ((Test)0)->number 指向了结构体的 number,其实就是相当于指向了 number 的首地址。因为空指针的初始地址是 0 那么这个指针的地址就是这个 number ,在结构体中的地址偏移。

既然直到这段是在求 number 在结构体中的地址偏移,那么他的代码输出结果是什么?

这就要提到结构体的占用内存的方式。

我们直到 char 占用 1 个字节,int 占用 4 个字节,那么这个结构体是不是占用 5 个字节?

内存对齐

算法的性能可以用空间复杂度和时间复杂度来评估,而 C 语言结构体很多设计也是空间复杂度和时间复杂度之间的取舍,结构体在使用过程中并不是一个字段地址挨着一个字段地址访问,而是为了访问效率进行内存对齐的操作

一般内存对齐都是 4 字节对齐,所以上述结构体大概的一个占用内存结构如下:

内存占用内存占用

明白了上述内存对齐,那么number 的地址在结构体中的内存偏移我们就知道了,所以输出是 4。

但是如果我就是叛逆,我管你什么性能的,我看到空间浪费我就难受,对于穷孩子出身的我就算这饭吃了拉肚子我还是不扔怎么办?

设置内存对齐方式

内存对齐是编译器的默认的一种方式,如果想要禁止内存对齐或者自己设置内存对齐方式那么可以这样。

既然是编译器的一种设置方式,那么针对不同平台的语法也不一样,在 Linux 平台下使用 attribute((packed)) 和 attribute((aligned(4))) 来进行内存对齐,在结构体语言中就是

代码语言:c复制
typedef struct __attribute__((packed)) test {
    char ch;
    int number;
} *Test;

修改后输出就是 1 了

当然也可以自己设置对齐方式,比方说如果设置为 8

代码语言:c复制
typedef struct __attribute__((aligned(8))) test {
    char ch;
    int number;
} *Test;

这次输出什么呢? 很多人会认为是 8,但是实际大多可能是 4,这跟编译器有很大关系,编译器优化,默认对齐设置等等有关,所以自己设置对齐方式一般小于 4

Windows 平台也有优化选项

代码语言:c复制
#pragma pack(push, 1)
typedef struct test {
    char ch;
    int number;
} *Test;
#pragma pack(pop)

如果不加 push 后边也不同 pop ,不过这样选择就是对后边所有结构体都进行了内存对齐设置了。

另外如果只针对某一个成员变量设置对齐方式,那么可以这样写。

代码语言:c复制
typedef struct test {
    char ch;
    int number __declspec(align(1));  // 设置 number 成员的对齐为 1 字节
} *Test;

可以看到Windows下编译器对结构体控制粒度更细。

0 人点赞